From f432cfd73af163f769da4bf8cbc38bc8636d631b Mon Sep 17 00:00:00 2001 From: flow Date: Wed, 6 Jul 2022 15:56:03 -0300 Subject: [PATCH 001/273] change: put settings initialization in a separate function Signed-off-by: flow --- launcher/BaseInstance.cpp | 1 + launcher/BaseInstance.h | 9 ++ launcher/NullInstance.h | 4 + launcher/minecraft/MinecraftInstance.cpp | 104 +++++++++++++---------- launcher/minecraft/MinecraftInstance.h | 2 + launcher/settings/SettingsObject.h | 1 + 6 files changed, 75 insertions(+), 46 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 0efbdddc..1268b830 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -52,6 +52,7 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s : QObject() { m_settings = settings; + m_global_settings = globalSettings; m_rootDir = rootDir; m_settings->registerSetting("name", "Unnamed Instance"); diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 2a94dcc6..cd3d59f5 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -174,6 +174,11 @@ public: */ virtual SettingsObjectPtr settings() const; + /*! + * \brief Loads instance settings if they're not already loaded. + */ + virtual void loadSettingsIfNeeded() = 0; + /// returns a valid update task virtual Task::Ptr createUpdateTask(Net::Mode mode) = 0; @@ -285,7 +290,11 @@ protected slots: protected: /* data */ QString m_rootDir; + SettingsObjectPtr m_settings; + SettingsObjectWeakPtr m_global_settings; + bool m_settings_loaded = false; + // InstanceFlags m_flags; bool m_isRunning = false; shared_qobject_ptr m_launchProcess; diff --git a/launcher/NullInstance.h b/launcher/NullInstance.h index 9b0a9331..d40060f0 100644 --- a/launcher/NullInstance.h +++ b/launcher/NullInstance.h @@ -15,6 +15,10 @@ public: void saveNow() override { } + void loadSettingsIfNeeded() override + { + m_settings_loaded = true; + } QString getStatusbarDescription() override { return tr("Unknown instance type"); diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 889c6dde..debfa081 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -115,6 +115,21 @@ private: MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir) : BaseInstance(globalSettings, settings, rootDir) { + loadSettingsIfNeeded(); + + m_components.reset(new PackProfile(this)); +} + +void MinecraftInstance::saveNow() +{ + m_components->saveNow(); +} + +void MinecraftInstance::loadSettingsIfNeeded() +{ + if (m_settings_loaded) + return; + // Java Settings auto javaOverride = m_settings->registerSetting("OverrideJava", false); auto locationOverride = m_settings->registerSetting("OverrideJavaLocation", false); @@ -124,64 +139,61 @@ MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsO auto javaOrLocation = std::make_shared("JavaOrLocationOverride", javaOverride, locationOverride); auto javaOrArgs = std::make_shared("JavaOrArgsOverride", javaOverride, argsOverride); - m_settings->registerOverride(globalSettings->getSetting("JavaPath"), javaOrLocation); - m_settings->registerOverride(globalSettings->getSetting("JvmArgs"), javaOrArgs); - m_settings->registerOverride(globalSettings->getSetting("IgnoreJavaCompatibility"), javaOrLocation); + if (auto globalSettings = m_global_settings.lock()) { + m_settings->registerOverride(globalSettings->getSetting("JavaPath"), javaOrLocation); + m_settings->registerOverride(globalSettings->getSetting("JvmArgs"), javaOrArgs); + m_settings->registerOverride(globalSettings->getSetting("IgnoreJavaCompatibility"), javaOrLocation); - // special! - m_settings->registerPassthrough(globalSettings->getSetting("JavaTimestamp"), javaOrLocation); - m_settings->registerPassthrough(globalSettings->getSetting("JavaVersion"), javaOrLocation); - m_settings->registerPassthrough(globalSettings->getSetting("JavaArchitecture"), javaOrLocation); + // special! + m_settings->registerPassthrough(globalSettings->getSetting("JavaTimestamp"), javaOrLocation); + m_settings->registerPassthrough(globalSettings->getSetting("JavaVersion"), javaOrLocation); + m_settings->registerPassthrough(globalSettings->getSetting("JavaArchitecture"), javaOrLocation); - // Window Size - auto windowSetting = m_settings->registerSetting("OverrideWindow", false); - m_settings->registerOverride(globalSettings->getSetting("LaunchMaximized"), windowSetting); - m_settings->registerOverride(globalSettings->getSetting("MinecraftWinWidth"), windowSetting); - m_settings->registerOverride(globalSettings->getSetting("MinecraftWinHeight"), windowSetting); + // Window Size + auto windowSetting = m_settings->registerSetting("OverrideWindow", false); + m_settings->registerOverride(globalSettings->getSetting("LaunchMaximized"), windowSetting); + m_settings->registerOverride(globalSettings->getSetting("MinecraftWinWidth"), windowSetting); + m_settings->registerOverride(globalSettings->getSetting("MinecraftWinHeight"), windowSetting); - // Memory - auto memorySetting = m_settings->registerSetting("OverrideMemory", false); - m_settings->registerOverride(globalSettings->getSetting("MinMemAlloc"), memorySetting); - m_settings->registerOverride(globalSettings->getSetting("MaxMemAlloc"), memorySetting); - m_settings->registerOverride(globalSettings->getSetting("PermGen"), memorySetting); + // Memory + auto memorySetting = m_settings->registerSetting("OverrideMemory", false); + m_settings->registerOverride(globalSettings->getSetting("MinMemAlloc"), memorySetting); + m_settings->registerOverride(globalSettings->getSetting("MaxMemAlloc"), memorySetting); + m_settings->registerOverride(globalSettings->getSetting("PermGen"), memorySetting); - // Minecraft launch method - auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false); - m_settings->registerOverride(globalSettings->getSetting("MCLaunchMethod"), launchMethodOverride); + // Minecraft launch method + auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false); + m_settings->registerOverride(globalSettings->getSetting("MCLaunchMethod"), launchMethodOverride); - // Native library workarounds - auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false); - m_settings->registerOverride(globalSettings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride); - m_settings->registerOverride(globalSettings->getSetting("UseNativeGLFW"), nativeLibraryWorkaroundsOverride); + // Native library workarounds + auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false); + m_settings->registerOverride(globalSettings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride); + m_settings->registerOverride(globalSettings->getSetting("UseNativeGLFW"), nativeLibraryWorkaroundsOverride); - // Peformance related options - auto performanceOverride = m_settings->registerSetting("OverridePerformance", false); - m_settings->registerOverride(globalSettings->getSetting("EnableFeralGamemode"), performanceOverride); - m_settings->registerOverride(globalSettings->getSetting("EnableMangoHud"), performanceOverride); - m_settings->registerOverride(globalSettings->getSetting("UseDiscreteGpu"), performanceOverride); + // Peformance related options + auto performanceOverride = m_settings->registerSetting("OverridePerformance", false); + m_settings->registerOverride(globalSettings->getSetting("EnableFeralGamemode"), performanceOverride); + m_settings->registerOverride(globalSettings->getSetting("EnableMangoHud"), performanceOverride); + m_settings->registerOverride(globalSettings->getSetting("UseDiscreteGpu"), performanceOverride); - // Game time - auto gameTimeOverride = m_settings->registerSetting("OverrideGameTime", false); - m_settings->registerOverride(globalSettings->getSetting("ShowGameTime"), gameTimeOverride); - m_settings->registerOverride(globalSettings->getSetting("RecordGameTime"), gameTimeOverride); + // Game time + auto gameTimeOverride = m_settings->registerSetting("OverrideGameTime", false); + m_settings->registerOverride(globalSettings->getSetting("ShowGameTime"), gameTimeOverride); + m_settings->registerOverride(globalSettings->getSetting("RecordGameTime"), gameTimeOverride); + + // Miscellaneous + auto miscellaneousOverride = m_settings->registerSetting("OverrideMiscellaneous", false); + m_settings->registerOverride(globalSettings->getSetting("CloseAfterLaunch"), miscellaneousOverride); + m_settings->registerOverride(globalSettings->getSetting("QuitAfterGameStop"), miscellaneousOverride); + + m_settings->set("InstanceType", "OneSix"); + } // Join server on launch, this does not have a global override m_settings->registerSetting("JoinServerOnLaunch", false); m_settings->registerSetting("JoinServerOnLaunchAddress", ""); - // Miscellaneous - auto miscellaneousOverride = m_settings->registerSetting("OverrideMiscellaneous", false); - m_settings->registerOverride(globalSettings->getSetting("CloseAfterLaunch"), miscellaneousOverride); - m_settings->registerOverride(globalSettings->getSetting("QuitAfterGameStop"), miscellaneousOverride); - - m_settings->set("InstanceType", "OneSix"); - - m_components.reset(new PackProfile(this)); -} - -void MinecraftInstance::saveNow() -{ - m_components->saveNow(); + m_settings_loaded = true; } QString MinecraftInstance::typeName() const diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h index 05450d41..36d1d92d 100644 --- a/launcher/minecraft/MinecraftInstance.h +++ b/launcher/minecraft/MinecraftInstance.h @@ -20,6 +20,8 @@ public: virtual ~MinecraftInstance() {}; virtual void saveNow() override; + void loadSettingsIfNeeded() override; + // FIXME: remove QString typeName() const override; // FIXME: remove diff --git a/launcher/settings/SettingsObject.h b/launcher/settings/SettingsObject.h index 3d61e707..6200bc3a 100644 --- a/launcher/settings/SettingsObject.h +++ b/launcher/settings/SettingsObject.h @@ -25,6 +25,7 @@ class Setting; class SettingsObject; typedef std::shared_ptr SettingsObjectPtr; +typedef std::weak_ptr SettingsObjectWeakPtr; /*! * \brief The SettingsObject handles communicating settings between the application and a From 273cf3d5655a6b2973191bf5403bfbe9a9b8ba15 Mon Sep 17 00:00:00 2001 From: flow Date: Wed, 6 Jul 2022 17:17:54 -0300 Subject: [PATCH 002/273] feat: lazy-load MinecraftInstance settings Makes the startup go fast! vrum Signed-off-by: flow --- launcher/BaseInstance.cpp | 14 +++-- launcher/BaseInstance.h | 27 ++++++--- launcher/NullInstance.h | 6 +- launcher/minecraft/MinecraftInstance.cpp | 74 ++++++++++++------------ launcher/minecraft/MinecraftInstance.h | 14 ++--- 5 files changed, 73 insertions(+), 62 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 1268b830..c9cfef88 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -149,7 +149,7 @@ void BaseInstance::setManagedPack(const QString& type, const QString& id, const int BaseInstance::getConsoleMaxLines() const { - auto lineSetting = settings()->getSetting("ConsoleMaxLines"); + auto lineSetting = m_settings->getSetting("ConsoleMaxLines"); bool conversionOk = false; int maxLines = lineSetting->get().toInt(&conversionOk); if(!conversionOk) @@ -162,7 +162,7 @@ int BaseInstance::getConsoleMaxLines() const bool BaseInstance::shouldStopOnConsoleOverflow() const { - return settings()->get("ConsoleOverflowStop").toBool(); + return m_settings->get("ConsoleOverflowStop").toBool(); } void BaseInstance::iconUpdated(QString key) @@ -237,7 +237,7 @@ void BaseInstance::setRunning(bool running) int64_t BaseInstance::totalTimePlayed() const { - qint64 current = settings()->get("totalTimePlayed").toLongLong(); + qint64 current = m_settings->get("totalTimePlayed").toLongLong(); if(m_isRunning) { QDateTime timeNow = QDateTime::currentDateTime(); @@ -253,7 +253,7 @@ int64_t BaseInstance::lastTimePlayed() const QDateTime timeNow = QDateTime::currentDateTime(); return m_timeStarted.secsTo(timeNow); } - return settings()->get("lastTimePlayed").toLongLong(); + return m_settings->get("lastTimePlayed").toLongLong(); } void BaseInstance::resetTimePlayed() @@ -272,8 +272,10 @@ QString BaseInstance::instanceRoot() const return m_rootDir; } -SettingsObjectPtr BaseInstance::settings() const +SettingsObjectPtr BaseInstance::settings() { + loadSpecificSettings(); + return m_settings; } @@ -340,7 +342,7 @@ QString BaseInstance::windowTitle() const } // FIXME: why is this here? move it to MinecraftInstance!!! -QStringList BaseInstance::extraArguments() const +QStringList BaseInstance::extraArguments() { return Commandline::splitArgs(settings()->get("JvmArgs").toString()); } diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index cd3d59f5..3af104e9 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -154,7 +154,7 @@ public: return level; }; - virtual QStringList extraArguments() const; + virtual QStringList extraArguments(); /// Traits. Normally inside the version, depends on instance implementation. virtual QSet traits() const = 0; @@ -170,14 +170,18 @@ public: /*! * \brief Gets this instance's settings object. * This settings object stores instance-specific settings. + * + * Note that this method is not const. + * It may call loadSpecificSettings() to ensure those are loaded. + * * \return A pointer to this instance's settings object. */ - virtual SettingsObjectPtr settings() const; + virtual SettingsObjectPtr settings(); /*! - * \brief Loads instance settings if they're not already loaded. + * \brief Loads settings specific to an instance type if they're not already loaded. */ - virtual void loadSettingsIfNeeded() = 0; + virtual void loadSpecificSettings() = 0; /// returns a valid update task virtual Task::Ptr createUpdateTask(Net::Mode mode) = 0; @@ -211,7 +215,7 @@ public: virtual QString instanceConfigFolder() const = 0; /// get variables this instance exports - virtual QMap getVariables() const = 0; + virtual QMap getVariables() = 0; virtual QString typeName() const = 0; @@ -273,6 +277,11 @@ public: protected: void changeStatus(Status newStatus); + SettingsObjectPtr globalSettings() const { return m_global_settings.lock(); }; + + bool isSpecificSettingsLoaded() const { return m_specific_settings_loaded; } + void setSpecificSettingsLoaded(bool loaded) { m_specific_settings_loaded = loaded; } + signals: /*! * \brief Signal emitted when properties relevant to the instance view change @@ -290,11 +299,7 @@ protected slots: protected: /* data */ QString m_rootDir; - SettingsObjectPtr m_settings; - SettingsObjectWeakPtr m_global_settings; - bool m_settings_loaded = false; - // InstanceFlags m_flags; bool m_isRunning = false; shared_qobject_ptr m_launchProcess; @@ -305,6 +310,10 @@ private: /* data */ bool m_crashed = false; bool m_hasUpdate = false; bool m_hasBrokenVersion = false; + + SettingsObjectWeakPtr m_global_settings; + bool m_specific_settings_loaded = false; + }; Q_DECLARE_METATYPE(shared_qobject_ptr) diff --git a/launcher/NullInstance.h b/launcher/NullInstance.h index d40060f0..53e64a05 100644 --- a/launcher/NullInstance.h +++ b/launcher/NullInstance.h @@ -15,9 +15,9 @@ public: void saveNow() override { } - void loadSettingsIfNeeded() override + void loadSpecificSettings() override { - m_settings_loaded = true; + setSpecificSettingsLoaded(true); } QString getStatusbarDescription() override { @@ -47,7 +47,7 @@ public: { return QProcessEnvironment(); } - QMap getVariables() const override + QMap getVariables() override { return QMap(); } diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index debfa081..d981ecc9 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -115,8 +115,6 @@ private: MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr settings, const QString &rootDir) : BaseInstance(globalSettings, settings, rootDir) { - loadSettingsIfNeeded(); - m_components.reset(new PackProfile(this)); } @@ -125,9 +123,9 @@ void MinecraftInstance::saveNow() m_components->saveNow(); } -void MinecraftInstance::loadSettingsIfNeeded() +void MinecraftInstance::loadSpecificSettings() { - if (m_settings_loaded) + if (isSpecificSettingsLoaded()) return; // Java Settings @@ -139,52 +137,52 @@ void MinecraftInstance::loadSettingsIfNeeded() auto javaOrLocation = std::make_shared("JavaOrLocationOverride", javaOverride, locationOverride); auto javaOrArgs = std::make_shared("JavaOrArgsOverride", javaOverride, argsOverride); - if (auto globalSettings = m_global_settings.lock()) { - m_settings->registerOverride(globalSettings->getSetting("JavaPath"), javaOrLocation); - m_settings->registerOverride(globalSettings->getSetting("JvmArgs"), javaOrArgs); - m_settings->registerOverride(globalSettings->getSetting("IgnoreJavaCompatibility"), javaOrLocation); + if (auto global_settings = globalSettings()) { + m_settings->registerOverride(global_settings->getSetting("JavaPath"), javaOrLocation); + m_settings->registerOverride(global_settings->getSetting("JvmArgs"), javaOrArgs); + m_settings->registerOverride(global_settings->getSetting("IgnoreJavaCompatibility"), javaOrLocation); // special! - m_settings->registerPassthrough(globalSettings->getSetting("JavaTimestamp"), javaOrLocation); - m_settings->registerPassthrough(globalSettings->getSetting("JavaVersion"), javaOrLocation); - m_settings->registerPassthrough(globalSettings->getSetting("JavaArchitecture"), javaOrLocation); + m_settings->registerPassthrough(global_settings->getSetting("JavaTimestamp"), javaOrLocation); + m_settings->registerPassthrough(global_settings->getSetting("JavaVersion"), javaOrLocation); + m_settings->registerPassthrough(global_settings->getSetting("JavaArchitecture"), javaOrLocation); // Window Size auto windowSetting = m_settings->registerSetting("OverrideWindow", false); - m_settings->registerOverride(globalSettings->getSetting("LaunchMaximized"), windowSetting); - m_settings->registerOverride(globalSettings->getSetting("MinecraftWinWidth"), windowSetting); - m_settings->registerOverride(globalSettings->getSetting("MinecraftWinHeight"), windowSetting); + m_settings->registerOverride(global_settings->getSetting("LaunchMaximized"), windowSetting); + m_settings->registerOverride(global_settings->getSetting("MinecraftWinWidth"), windowSetting); + m_settings->registerOverride(global_settings->getSetting("MinecraftWinHeight"), windowSetting); // Memory auto memorySetting = m_settings->registerSetting("OverrideMemory", false); - m_settings->registerOverride(globalSettings->getSetting("MinMemAlloc"), memorySetting); - m_settings->registerOverride(globalSettings->getSetting("MaxMemAlloc"), memorySetting); - m_settings->registerOverride(globalSettings->getSetting("PermGen"), memorySetting); + m_settings->registerOverride(global_settings->getSetting("MinMemAlloc"), memorySetting); + m_settings->registerOverride(global_settings->getSetting("MaxMemAlloc"), memorySetting); + m_settings->registerOverride(global_settings->getSetting("PermGen"), memorySetting); // Minecraft launch method auto launchMethodOverride = m_settings->registerSetting("OverrideMCLaunchMethod", false); - m_settings->registerOverride(globalSettings->getSetting("MCLaunchMethod"), launchMethodOverride); + m_settings->registerOverride(global_settings->getSetting("MCLaunchMethod"), launchMethodOverride); // Native library workarounds auto nativeLibraryWorkaroundsOverride = m_settings->registerSetting("OverrideNativeWorkarounds", false); - m_settings->registerOverride(globalSettings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride); - m_settings->registerOverride(globalSettings->getSetting("UseNativeGLFW"), nativeLibraryWorkaroundsOverride); + m_settings->registerOverride(global_settings->getSetting("UseNativeOpenAL"), nativeLibraryWorkaroundsOverride); + m_settings->registerOverride(global_settings->getSetting("UseNativeGLFW"), nativeLibraryWorkaroundsOverride); // Peformance related options auto performanceOverride = m_settings->registerSetting("OverridePerformance", false); - m_settings->registerOverride(globalSettings->getSetting("EnableFeralGamemode"), performanceOverride); - m_settings->registerOverride(globalSettings->getSetting("EnableMangoHud"), performanceOverride); - m_settings->registerOverride(globalSettings->getSetting("UseDiscreteGpu"), performanceOverride); + m_settings->registerOverride(global_settings->getSetting("EnableFeralGamemode"), performanceOverride); + m_settings->registerOverride(global_settings->getSetting("EnableMangoHud"), performanceOverride); + m_settings->registerOverride(global_settings->getSetting("UseDiscreteGpu"), performanceOverride); // Game time auto gameTimeOverride = m_settings->registerSetting("OverrideGameTime", false); - m_settings->registerOverride(globalSettings->getSetting("ShowGameTime"), gameTimeOverride); - m_settings->registerOverride(globalSettings->getSetting("RecordGameTime"), gameTimeOverride); + m_settings->registerOverride(global_settings->getSetting("ShowGameTime"), gameTimeOverride); + m_settings->registerOverride(global_settings->getSetting("RecordGameTime"), gameTimeOverride); // Miscellaneous auto miscellaneousOverride = m_settings->registerSetting("OverrideMiscellaneous", false); - m_settings->registerOverride(globalSettings->getSetting("CloseAfterLaunch"), miscellaneousOverride); - m_settings->registerOverride(globalSettings->getSetting("QuitAfterGameStop"), miscellaneousOverride); + m_settings->registerOverride(global_settings->getSetting("CloseAfterLaunch"), miscellaneousOverride); + m_settings->registerOverride(global_settings->getSetting("QuitAfterGameStop"), miscellaneousOverride); m_settings->set("InstanceType", "OneSix"); } @@ -193,7 +191,9 @@ void MinecraftInstance::loadSettingsIfNeeded() m_settings->registerSetting("JoinServerOnLaunch", false); m_settings->registerSetting("JoinServerOnLaunchAddress", ""); - m_settings_loaded = true; + qDebug() << "Instance-type specific settings were loaded!"; + + setSpecificSettingsLoaded(true); } QString MinecraftInstance::typeName() const @@ -320,7 +320,7 @@ QDir MinecraftInstance::versionsPath() const return QDir::current().absoluteFilePath("versions"); } -QStringList MinecraftInstance::getClassPath() const +QStringList MinecraftInstance::getClassPath() { QStringList jars, nativeJars; auto javaArchitecture = settings()->get("JavaArchitecture").toString(); @@ -335,7 +335,7 @@ QString MinecraftInstance::getMainClass() const return profile->getMainClass(); } -QStringList MinecraftInstance::getNativeJars() const +QStringList MinecraftInstance::getNativeJars() { QStringList jars, nativeJars; auto javaArchitecture = settings()->get("JavaArchitecture").toString(); @@ -344,7 +344,7 @@ QStringList MinecraftInstance::getNativeJars() const return nativeJars; } -QStringList MinecraftInstance::extraArguments() const +QStringList MinecraftInstance::extraArguments() { auto list = BaseInstance::extraArguments(); auto version = getPackProfile(); @@ -370,7 +370,7 @@ QStringList MinecraftInstance::extraArguments() const return list; } -QStringList MinecraftInstance::javaArguments() const +QStringList MinecraftInstance::javaArguments() { QStringList args; @@ -427,7 +427,7 @@ QStringList MinecraftInstance::javaArguments() const return args; } -QMap MinecraftInstance::getVariables() const +QMap MinecraftInstance::getVariables() { QMap out; out.insert("INST_NAME", name()); @@ -951,9 +951,9 @@ shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPt process->appendStep(new CreateGameFolders(pptr)); } - if (!serverToJoin && m_settings->get("JoinServerOnLaunch").toBool()) + if (!serverToJoin && settings()->get("JoinServerOnLaunch").toBool()) { - QString fullAddress = m_settings->get("JoinServerOnLaunchAddress").toString(); + QString fullAddress = settings()->get("JoinServerOnLaunchAddress").toString(); serverToJoin.reset(new MinecraftServerTarget(MinecraftServerTarget::parse(fullAddress))); } @@ -1061,10 +1061,10 @@ shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPt QString MinecraftInstance::launchMethod() { - return m_settings->get("MCLaunchMethod").toString(); + return settings()->get("MCLaunchMethod").toString(); } -JavaVersion MinecraftInstance::getJavaVersion() const +JavaVersion MinecraftInstance::getJavaVersion() { return JavaVersion(settings()->get("JavaVersion").toString()); } diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h index 36d1d92d..6eadedfd 100644 --- a/launcher/minecraft/MinecraftInstance.h +++ b/launcher/minecraft/MinecraftInstance.h @@ -20,7 +20,7 @@ public: virtual ~MinecraftInstance() {}; virtual void saveNow() override; - void loadSettingsIfNeeded() override; + void loadSpecificSettings() override; // FIXME: remove QString typeName() const override; @@ -81,15 +81,15 @@ public: ////// Launch stuff ////// Task::Ptr createUpdateTask(Net::Mode mode) override; shared_qobject_ptr createLaunchTask(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) override; - QStringList extraArguments() const override; + QStringList extraArguments() override; QStringList verboseDescription(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) override; QList getJarMods() const; QString createLaunchScript(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin); /// get arguments passed to java - QStringList javaArguments() const; + QStringList javaArguments(); /// get variables for launch command variable substitution/environment - QMap getVariables() const override; + QMap getVariables() override; /// create an environment for launching processes QProcessEnvironment createEnvironment() override; @@ -105,16 +105,16 @@ public: QString getStatusbarDescription() override; // FIXME: remove - virtual QStringList getClassPath() const; + virtual QStringList getClassPath(); // FIXME: remove - virtual QStringList getNativeJars() const; + virtual QStringList getNativeJars(); // FIXME: remove virtual QString getMainClass() const; // FIXME: remove virtual QStringList processMinecraftArgs(AuthSessionPtr account, MinecraftServerTargetPtr serverToJoin) const; - virtual JavaVersion getJavaVersion() const; + virtual JavaVersion getJavaVersion(); protected: QMap createCensorFilterFromSession(AuthSessionPtr session); From e0ae631d59103cd32d758dec76e55b3a40b86be2 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Mon, 23 May 2022 17:22:21 +0100 Subject: [PATCH 003/273] ATLauncher: Add modes to install task and display appropriate message This will display the update/reinstall message for the installation method currently in use.. --- .../atlauncher/ATLPackInstallTask.cpp | 27 ++++++++++++++++--- .../atlauncher/ATLPackInstallTask.h | 9 ++++++- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index b4936bd8..5b91cd21 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -60,12 +60,13 @@ namespace ATLauncher { static Meta::VersionPtr getComponentVersion(const QString& uid, const QString& version); -PackInstallTask::PackInstallTask(UserInteractionSupport *support, QString packName, QString version) +PackInstallTask::PackInstallTask(UserInteractionSupport *support, QString packName, QString version, InstallMode installMode) { m_support = support; m_pack_name = packName; m_pack_safe_name = packName.replace(QRegularExpression("[^A-Za-z0-9]"), ""); m_version_name = version; + m_install_mode = installMode; } bool PackInstallTask::abort() @@ -117,9 +118,27 @@ void PackInstallTask::onDownloadSucceeded() } m_version = version; - // Display install message if one exists - if (!m_version.messages.install.isEmpty()) - m_support->displayMessage(m_version.messages.install); + // Derived from the installation mode + QString message; + + switch (m_install_mode) { + case InstallMode::Reinstall: + case InstallMode::Update: + message = m_version.messages.update; + break; + + case InstallMode::Install: + message = m_version.messages.install; + break; + + default: + emitFailed(tr("Unsupported installation mode")); + break; + } + + // Display message if one exists + if (!message.isEmpty()) + m_support->displayMessage(message); auto ver = getComponentVersion("net.minecraft", m_version.minecraft); if (!ver) { diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.h b/launcher/modplatform/atlauncher/ATLPackInstallTask.h index f55873e9..8127f2e2 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.h +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.h @@ -50,6 +50,12 @@ namespace ATLauncher { +enum class InstallMode { + Install, + Reinstall, + Update, +}; + class UserInteractionSupport { public: @@ -75,7 +81,7 @@ class PackInstallTask : public InstanceTask Q_OBJECT public: - explicit PackInstallTask(UserInteractionSupport *support, QString packName, QString version); + explicit PackInstallTask(UserInteractionSupport *support, QString packName, QString version, InstallMode installMode = InstallMode::Install); virtual ~PackInstallTask(){} bool canAbort() const override { return true; } @@ -117,6 +123,7 @@ private: NetJob::Ptr jobPtr; QByteArray response; + InstallMode m_install_mode; QString m_pack_name; QString m_pack_safe_name; QString m_version_name; From 9e69b8fe1bf8b325f6c386b7578408da4b775177 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Sat, 21 May 2022 19:11:37 +0100 Subject: [PATCH 004/273] ATLauncher: Parse keeps and deletes from pack manifests --- .../atlauncher/ATLPackManifest.cpp | 64 +++++++++++++++++++ .../modplatform/atlauncher/ATLPackManifest.h | 23 +++++++ 2 files changed, 87 insertions(+) diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.cpp b/launcher/modplatform/atlauncher/ATLPackManifest.cpp index 3af02a09..5a458f4e 100644 --- a/launcher/modplatform/atlauncher/ATLPackManifest.cpp +++ b/launcher/modplatform/atlauncher/ATLPackManifest.cpp @@ -224,6 +224,64 @@ static void loadVersionExtraArguments(ATLauncher::PackVersionExtraArguments& a, a.depends = Json::ensureString(obj, "depends", ""); } +static void loadVersionKeep(ATLauncher::VersionKeep& k, QJsonObject& obj) +{ + k.base = Json::requireString(obj, "base"); + k.target = Json::requireString(obj, "target"); +} + +static void loadVersionKeeps(ATLauncher::VersionKeeps& k, QJsonObject& obj) +{ + if (obj.contains("files")) { + auto files = Json::requireArray(obj, "files"); + for (const auto keepRaw : files) { + auto keepObj = Json::requireObject(keepRaw); + ATLauncher::VersionKeep keep; + loadVersionKeep(keep, keepObj); + k.files.append(keep); + } + } + + if (obj.contains("folders")) { + auto folders = Json::requireArray(obj, "folders"); + for (const auto keepRaw : folders) { + auto keepObj = Json::requireObject(keepRaw); + ATLauncher::VersionKeep keep; + loadVersionKeep(keep, keepObj); + k.folders.append(keep); + } + } +} + +static void loadVersionDelete(ATLauncher::VersionDelete& d, QJsonObject& obj) +{ + d.base = Json::requireString(obj, "base"); + d.target = Json::requireString(obj, "target"); +} + +static void loadVersionDeletes(ATLauncher::VersionDeletes& d, QJsonObject& obj) +{ + if (obj.contains("files")) { + auto files = Json::requireArray(obj, "files"); + for (const auto deleteRaw : files) { + auto deleteObj = Json::requireObject(deleteRaw); + ATLauncher::VersionDelete versionDelete; + loadVersionDelete(versionDelete, deleteObj); + d.files.append(versionDelete); + } + } + + if (obj.contains("folders")) { + auto folders = Json::requireArray(obj, "folders"); + for (const auto deleteRaw : folders) { + auto deleteObj = Json::requireObject(deleteRaw); + ATLauncher::VersionDelete versionDelete; + loadVersionDelete(versionDelete, deleteObj); + d.folders.append(versionDelete); + } + } +} + void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj) { v.version = Json::requireString(obj, "version"); @@ -284,4 +342,10 @@ void ATLauncher::loadVersion(PackVersion & v, QJsonObject & obj) auto messages = Json::ensureObject(obj, "messages"); loadVersionMessages(v.messages, messages); + + auto keeps = Json::ensureObject(obj, "keeps"); + loadVersionKeeps(v.keeps, keeps); + + auto deletes = Json::ensureObject(obj, "deletes"); + loadVersionDeletes(v.deletes, deletes); } diff --git a/launcher/modplatform/atlauncher/ATLPackManifest.h b/launcher/modplatform/atlauncher/ATLPackManifest.h index 43510c50..571c976d 100644 --- a/launcher/modplatform/atlauncher/ATLPackManifest.h +++ b/launcher/modplatform/atlauncher/ATLPackManifest.h @@ -150,6 +150,26 @@ struct VersionMessages QString update; }; +struct VersionKeep { + QString base; + QString target; +}; + +struct VersionKeeps { + QVector files; + QVector folders; +}; + +struct VersionDelete { + QString base; + QString target; +}; + +struct VersionDeletes { + QVector files; + QVector folders; +}; + struct PackVersionMainClass { QString mainClass; @@ -178,6 +198,9 @@ struct PackVersion QMap colours; QMap warnings; VersionMessages messages; + + VersionKeeps keeps; + VersionDeletes deletes; }; void loadVersion(PackVersion & v, QJsonObject & obj); From a7fc23dd96981b1bc2449b5bf32f1913b45ecbc8 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Wed, 25 May 2022 23:09:24 +0100 Subject: [PATCH 005/273] ATLauncher: Reset existing directory if required --- .../atlauncher/ATLPackInstallTask.cpp | 117 ++++++++++++++++++ .../atlauncher/ATLPackInstallTask.h | 1 + 2 files changed, 118 insertions(+) diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 5b91cd21..01c82baa 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -120,15 +120,18 @@ void PackInstallTask::onDownloadSucceeded() // Derived from the installation mode QString message; + bool resetDirectory; switch (m_install_mode) { case InstallMode::Reinstall: case InstallMode::Update: message = m_version.messages.update; + resetDirectory = true; break; case InstallMode::Install: message = m_version.messages.install; + resetDirectory = false; break; default: @@ -147,6 +150,10 @@ void PackInstallTask::onDownloadSucceeded() } minecraftVersion = ver; + if (resetDirectory) { + deleteExistingFiles(); + } + if(m_version.noConfigs) { downloadMods(); } @@ -162,6 +169,116 @@ void PackInstallTask::onDownloadFailed(QString reason) emitFailed(reason); } +void PackInstallTask::deleteExistingFiles() +{ + setStatus(tr("Deleting existing files...")); + + // Setup defaults, as per https://wiki.atlauncher.com/pack-admin/xml/delete + VersionDeletes deletes; + deletes.folders.append(VersionDelete{ "root", "mods%s%" }); + deletes.folders.append(VersionDelete{ "root", "configs%s%" }); + deletes.folders.append(VersionDelete{ "root", "bin%s%" }); + + // Setup defaults, as per https://wiki.atlauncher.com/pack-admin/xml/keep + VersionKeeps keeps; + keeps.files.append(VersionKeep{ "root", "mods%s%PortalGunSounds.pak" }); + keeps.folders.append(VersionKeep{ "root", "mods%s%rei_minimap%s%" }); + keeps.folders.append(VersionKeep{ "root", "mods%s%VoxelMods%s%" }); + keeps.files.append(VersionKeep{ "root", "config%s%NEI.cfg" }); + keeps.files.append(VersionKeep{ "root", "options.txt" }); + keeps.files.append(VersionKeep{ "root", "servers.dat" }); + + // Merge with version deletes and keeps + for (const auto& item : m_version.deletes.files) + deletes.files.append(item); + for (const auto& item : m_version.deletes.folders) + deletes.folders.append(item); + for (const auto& item : m_version.keeps.files) + keeps.files.append(item); + for (const auto& item : m_version.keeps.folders) + keeps.folders.append(item); + + auto getPathForBase = [this](const QString& base) { + auto minecraftPath = FS::PathCombine(m_stagingPath, "minecraft"); + + if (base == "root") { + return minecraftPath; + } + else if (base == "config") { + return FS::PathCombine(minecraftPath, "config"); + } + else { + qWarning() << "Unrecognised base path" << base; + return minecraftPath; + } + }; + + auto convertToSystemPath = [](const QString& path) { + auto t = path; + t.replace("%s%", QDir::separator()); + return t; + }; + + auto shouldKeep = [keeps, getPathForBase, convertToSystemPath](const QString& fullPath) { + for (const auto& item : keeps.files) { + auto basePath = getPathForBase(item.base); + auto targetPath = convertToSystemPath(item.target); + auto path = FS::PathCombine(basePath, targetPath); + + if (fullPath == path) { + return true; + } + } + + for (const auto& item : keeps.folders) { + auto basePath = getPathForBase(item.base); + auto targetPath = convertToSystemPath(item.target); + auto path = FS::PathCombine(basePath, targetPath); + + if (fullPath.startsWith(path)) { + return true; + } + } + + return false; + }; + + // Keep track of files to delete + QSet filesToDelete; + + for (const auto& item : deletes.files) { + auto basePath = getPathForBase(item.base); + auto targetPath = convertToSystemPath(item.target); + auto fullPath = FS::PathCombine(basePath, targetPath); + + if (shouldKeep(fullPath)) + continue; + + filesToDelete.insert(fullPath); + } + + for (const auto& item : deletes.folders) { + auto basePath = getPathForBase(item.base); + auto targetPath = convertToSystemPath(item.target); + auto fullPath = FS::PathCombine(basePath, targetPath); + + QDirIterator it(fullPath, QDirIterator::Subdirectories); + while (it.hasNext()) { + auto path = it.next(); + + if (shouldKeep(path)) + continue; + + filesToDelete.insert(path); + } + } + + // Delete the files + for (const auto& item : filesToDelete) { + QFile::remove(item); + } +} + QString PackInstallTask::getDirForModType(ModType type, QString raw) { switch (type) { diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.h b/launcher/modplatform/atlauncher/ATLPackInstallTask.h index 8127f2e2..d5ecae34 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.h +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.h @@ -105,6 +105,7 @@ private: bool createLibrariesComponent(QString instanceRoot, std::shared_ptr profile); bool createPackComponent(QString instanceRoot, std::shared_ptr profile); + void deleteExistingFiles(); void installConfigs(); void extractConfigs(); void downloadMods(); From bf560f4594e4c17b493725f1ac98f0becfff5c2e Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Sat, 28 May 2022 22:07:13 +0100 Subject: [PATCH 006/273] ATLauncher: Move the UI support implementation into it's own class This will allow it to be used in multiple locations. --- launcher/CMakeLists.txt | 2 + .../pages/modplatform/atlauncher/AtlPage.cpp | 59 ++---------- .../ui/pages/modplatform/atlauncher/AtlPage.h | 6 +- .../AtlUserInteractionSupportImpl.cpp | 95 +++++++++++++++++++ .../AtlUserInteractionSupportImpl.h | 56 +++++++++++ 5 files changed, 160 insertions(+), 58 deletions(-) create mode 100644 launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp create mode 100644 launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index ec8e84c9..eeb786a6 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -741,6 +741,8 @@ SET(LAUNCHER_SOURCES ui/pages/modplatform/atlauncher/AtlOptionalModDialog.h ui/pages/modplatform/atlauncher/AtlPage.cpp ui/pages/modplatform/atlauncher/AtlPage.h + ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp + ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h ui/pages/modplatform/ftb/FtbFilterModel.cpp ui/pages/modplatform/ftb/FtbFilterModel.h diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp index 8de5211c..7901b90b 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp @@ -37,13 +37,12 @@ #include "AtlPage.h" #include "ui_AtlPage.h" -#include "modplatform/atlauncher/ATLPackInstallTask.h" +#include "BuildConfig.h" #include "AtlOptionalModDialog.h" +#include "AtlUserInteractionSupportImpl.h" +#include "modplatform/atlauncher/ATLPackInstallTask.h" #include "ui/dialogs/NewInstanceDialog.h" -#include "ui/dialogs/VersionSelectDialog.h" - -#include #include @@ -117,7 +116,9 @@ void AtlPage::suggestCurrent() return; } - dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ATLauncher::PackInstallTask(this, selected.name, selectedVersion)); + auto uiSupport = new AtlUserInteractionSupportImpl(this); + dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ATLauncher::PackInstallTask(uiSupport, selected.name, selectedVersion)); + auto editedLogoName = selected.safeName; auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(selected.safeName.toLower()); listModel->getLogo(selected.safeName, url, [this, editedLogoName](QString logo) @@ -172,51 +173,3 @@ void AtlPage::onVersionSelectionChanged(QString data) selectedVersion = data; suggestCurrent(); } - -QVector AtlPage::chooseOptionalMods(ATLauncher::PackVersion version, QVector mods) -{ - AtlOptionalModDialog optionalModDialog(this, version, mods); - optionalModDialog.exec(); - return optionalModDialog.getResult(); -} - -QString AtlPage::chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) { - VersionSelectDialog vselect(vlist.get(), "Choose Version", APPLICATION->activeWindow(), false); - if (minecraftVersion != Q_NULLPTR) { - vselect.setExactFilter(BaseVersionList::ParentVersionRole, minecraftVersion); - vselect.setEmptyString(tr("No versions are currently available for Minecraft %1").arg(minecraftVersion)); - } - else { - vselect.setEmptyString(tr("No versions are currently available")); - } - vselect.setEmptyErrorString(tr("Couldn't load or download the version lists!")); - - // select recommended build - for (int i = 0; i < vlist->versions().size(); i++) { - auto version = vlist->versions().at(i); - auto reqs = version->requires(); - - // filter by minecraft version, if the loader depends on a certain version. - if (minecraftVersion != Q_NULLPTR) { - auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Meta::Require &req) { - return req.uid == "net.minecraft"; - }); - if (iter == reqs.end()) continue; - if (iter->equalsVersion != minecraftVersion) continue; - } - - // first recommended build we find, we use. - if (version->isRecommended()) { - vselect.setCurrentVersion(version->descriptor()); - break; - } - } - - vselect.exec(); - return vselect.selectedVersion()->descriptor(); -} - -void AtlPage::displayMessage(QString message) -{ - QMessageBox::information(this, tr("Installing"), message); -} diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.h b/launcher/ui/pages/modplatform/atlauncher/AtlPage.h index aa6d5da1..1b3b15c1 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.h +++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.h @@ -52,7 +52,7 @@ namespace Ui class NewInstanceDialog; -class AtlPage : public QWidget, public BasePage, public ATLauncher::UserInteractionSupport +class AtlPage : public QWidget, public BasePage { Q_OBJECT @@ -83,10 +83,6 @@ public: private: void suggestCurrent(); - QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) override; - QVector chooseOptionalMods(ATLauncher::PackVersion version, QVector mods) override; - void displayMessage(QString message) override; - private slots: void triggerSearch(); diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp new file mode 100644 index 00000000..03196685 --- /dev/null +++ b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.cpp @@ -0,0 +1,95 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 Jamie Mansfield + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2020-2021 Jamie Mansfield + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "AtlUserInteractionSupportImpl.h" + +#include "AtlOptionalModDialog.h" +#include "ui/dialogs/VersionSelectDialog.h" + +AtlUserInteractionSupportImpl::AtlUserInteractionSupportImpl(QWidget *parent) : m_parent(parent) +{ +} + +QVector AtlUserInteractionSupportImpl::chooseOptionalMods(ATLauncher::PackVersion version, QVector mods) +{ + AtlOptionalModDialog optionalModDialog(m_parent, version, mods); + optionalModDialog.exec(); + return optionalModDialog.getResult(); +} + +QString AtlUserInteractionSupportImpl::chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) +{ + VersionSelectDialog vselect(vlist.get(), "Choose Version", m_parent, false); + if (minecraftVersion != nullptr) { + vselect.setExactFilter(BaseVersionList::ParentVersionRole, minecraftVersion); + vselect.setEmptyString(tr("No versions are currently available for Minecraft %1").arg(minecraftVersion)); + } + else { + vselect.setEmptyString(tr("No versions are currently available")); + } + vselect.setEmptyErrorString(tr("Couldn't load or download the version lists!")); + + // select recommended build + for (int i = 0; i < vlist->versions().size(); i++) { + auto version = vlist->versions().at(i); + auto reqs = version->requires(); + + // filter by minecraft version, if the loader depends on a certain version. + if (minecraftVersion != nullptr) { + auto iter = std::find_if(reqs.begin(), reqs.end(), [](const Meta::Require& req) { + return req.uid == "net.minecraft"; + }); + if (iter == reqs.end()) + continue; + if (iter->equalsVersion != minecraftVersion) + continue; + } + + // first recommended build we find, we use. + if (version->isRecommended()) { + vselect.setCurrentVersion(version->descriptor()); + break; + } + } + + vselect.exec(); + return vselect.selectedVersion()->descriptor(); +} + +void AtlUserInteractionSupportImpl::displayMessage(QString message) +{ + QMessageBox::information(m_parent, tr("Installing"), message); +} diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h new file mode 100644 index 00000000..aa22fc73 --- /dev/null +++ b/launcher/ui/pages/modplatform/atlauncher/AtlUserInteractionSupportImpl.h @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 Jamie Mansfield + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2020-2021 Jamie Mansfield + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include "modplatform/atlauncher/ATLPackInstallTask.h" + +class AtlUserInteractionSupportImpl : public QObject, public ATLauncher::UserInteractionSupport { + Q_OBJECT + +public: + AtlUserInteractionSupportImpl(QWidget* parent); + +private: + QString chooseVersion(Meta::VersionListPtr vlist, QString minecraftVersion) override; + QVector chooseOptionalMods(ATLauncher::PackVersion version, QVector mods) override; + void displayMessage(QString message) override; + +private: + QWidget* m_parent; + +}; From 31fd92e071888ed25f254b204e2f969b028c1927 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Sun, 10 Jul 2022 19:10:05 +0100 Subject: [PATCH 007/273] DCO Remediation Commit for e0ae631 I, Jamie Mansfield , hereby add my Signed-off-by to this commit: e0ae631d59103cd32d758dec76e55b3a40b86be2 Signed-off-by: Jamie Mansfield From 2810413112002acdb7114f8038c7102f3203d554 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Sun, 10 Jul 2022 19:10:46 +0100 Subject: [PATCH 008/273] DCO Remediation Commit for 9e69b8f I, Jamie Mansfield , hereby add my Signed-off-by to this commit: 9e69b8fe1bf8b325f6c386b7578408da4b775177 Signed-off-by: Jamie Mansfield From aed7963d11cf2fda255cffb3eb7a379fb09a040d Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Sun, 10 Jul 2022 19:13:34 +0100 Subject: [PATCH 009/273] DCO Remediation Commit for a7fc23d I, Jamie Mansfield , hereby add my Signed-off-by to this commit: a7fc23dd96981b1bc2449b5bf32f1913b45ecbc8 Signed-off-by: Jamie Mansfield From 10f27250eea76931293a9345f75059239526e4f9 Mon Sep 17 00:00:00 2001 From: Jamie Mansfield Date: Sun, 10 Jul 2022 19:14:05 +0100 Subject: [PATCH 010/273] DCO Remediation Commit for bf560f4 I, Jamie Mansfield , hereby add my Signed-off-by to this commit: bf560f4594e4c17b493725f1ac98f0becfff5c2e Signed-off-by: Jamie Mansfield From 509f7bd018f48c289526dc7a7be7009aed14f6b9 Mon Sep 17 00:00:00 2001 From: flow Date: Tue, 12 Jul 2022 21:15:59 -0300 Subject: [PATCH 011/273] fix: move time record overrides to BaseInstance This is needed so that we can show time stats in the UI without having to load all type-specific settings, which would make all the previous changes useless :c This is apparently done with console settings too, so I don't think there's a problem doing this too :> Signed-off-by: flow --- launcher/BaseInstance.cpp | 6 ++++++ launcher/minecraft/MinecraftInstance.cpp | 5 ----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index c9cfef88..35c26eb7 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -58,10 +58,16 @@ BaseInstance::BaseInstance(SettingsObjectPtr globalSettings, SettingsObjectPtr s m_settings->registerSetting("name", "Unnamed Instance"); m_settings->registerSetting("iconKey", "default"); m_settings->registerSetting("notes", ""); + m_settings->registerSetting("lastLaunchTime", 0); m_settings->registerSetting("totalTimePlayed", 0); m_settings->registerSetting("lastTimePlayed", 0); + // Game time override + auto gameTimeOverride = m_settings->registerSetting("OverrideGameTime", false); + m_settings->registerOverride(globalSettings->getSetting("ShowGameTime"), gameTimeOverride); + m_settings->registerOverride(globalSettings->getSetting("RecordGameTime"), gameTimeOverride); + // NOTE: Sometimees InstanceType is already registered, as it was used to identify the type of // a locally stored instance if (!m_settings->getSetting("InstanceType")) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index d981ecc9..fa6535b0 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -174,11 +174,6 @@ void MinecraftInstance::loadSpecificSettings() m_settings->registerOverride(global_settings->getSetting("EnableMangoHud"), performanceOverride); m_settings->registerOverride(global_settings->getSetting("UseDiscreteGpu"), performanceOverride); - // Game time - auto gameTimeOverride = m_settings->registerSetting("OverrideGameTime", false); - m_settings->registerOverride(global_settings->getSetting("ShowGameTime"), gameTimeOverride); - m_settings->registerOverride(global_settings->getSetting("RecordGameTime"), gameTimeOverride); - // Miscellaneous auto miscellaneousOverride = m_settings->registerSetting("OverrideMiscellaneous", false); m_settings->registerOverride(global_settings->getSetting("CloseAfterLaunch"), miscellaneousOverride); From 33e34ebb83bdf423a4a60b373559f5923929c216 Mon Sep 17 00:00:00 2001 From: kumquat-ir <66188216+kumquat-ir@users.noreply.github.com> Date: Sat, 16 Jul 2022 19:14:54 -0400 Subject: [PATCH 012/273] Add "Open All" button to blocked mods dialog Signed-off-by: kumquat-ir <66188216+kumquat-ir@users.noreply.github.com> --- launcher/CMakeLists.txt | 6 ++-- launcher/InstanceImportTask.cpp | 9 ++++-- launcher/ui/dialogs/BlockedModsDialog.cpp | 28 +++++++++++++++++++ launcher/ui/dialogs/BlockedModsDialog.h | 22 +++++++++++++++ ...rollMessageBox.ui => BlockedModsDialog.ui} | 10 +++---- launcher/ui/dialogs/ScrollMessageBox.cpp | 15 ---------- launcher/ui/dialogs/ScrollMessageBox.h | 20 ------------- 7 files changed, 64 insertions(+), 46 deletions(-) create mode 100644 launcher/ui/dialogs/BlockedModsDialog.cpp create mode 100644 launcher/ui/dialogs/BlockedModsDialog.h rename launcher/ui/dialogs/{ScrollMessageBox.ui => BlockedModsDialog.ui} (89%) delete mode 100644 launcher/ui/dialogs/ScrollMessageBox.cpp delete mode 100644 launcher/ui/dialogs/ScrollMessageBox.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index ecdeaac0..384f72f6 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -834,8 +834,8 @@ SET(LAUNCHER_SOURCES ui/dialogs/SkinUploadDialog.h ui/dialogs/ModDownloadDialog.cpp ui/dialogs/ModDownloadDialog.h - ui/dialogs/ScrollMessageBox.cpp - ui/dialogs/ScrollMessageBox.h + ui/dialogs/BlockedModsDialog.cpp + ui/dialogs/BlockedModsDialog.h # GUI - widgets ui/widgets/Common.cpp @@ -940,7 +940,7 @@ qt_wrap_ui(LAUNCHER_UI ui/dialogs/LoginDialog.ui ui/dialogs/EditAccountDialog.ui ui/dialogs/ReviewMessageBox.ui - ui/dialogs/ScrollMessageBox.ui + ui/dialogs/BlockedModsDialog.ui ) qt_add_resources(LAUNCHER_RESOURCES diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 14e1cd47..de0afc96 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -60,7 +60,7 @@ #include "net/ChecksumValidator.h" #include "ui/dialogs/CustomMessageBox.h" -#include "ui/dialogs/ScrollMessageBox.h" +#include "ui/dialogs/BlockedModsDialog.h" #include @@ -396,21 +396,24 @@ void InstanceImportTask::processFlame() auto results = m_modIdResolver->getResults(); //first check for blocked mods QString text; + QList urls; auto anyBlocked = false; for(const auto& result: results.files.values()) { if (!result.resolved || result.url.isEmpty()) { text += QString("%1: %2
").arg(result.fileName, result.websiteUrl); + urls.append(QUrl(result.websiteUrl)); anyBlocked = true; } } if(anyBlocked) { qWarning() << "Blocked mods found, displaying mod list"; - auto message_dialog = new ScrollMessageBox(m_parent, + auto message_dialog = new BlockedModsDialog(m_parent, tr("Blocked mods found"), tr("The following mods were blocked on third party launchers.
" "You will need to manually download them and add them to the modpack"), - text); + text, + urls); message_dialog->setModal(true); if (message_dialog->exec()) { diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp new file mode 100644 index 00000000..de698509 --- /dev/null +++ b/launcher/ui/dialogs/BlockedModsDialog.cpp @@ -0,0 +1,28 @@ +#include "BlockedModsDialog.h" +#include "ui_BlockedModsDialog.h" +#include "qpushbutton.h" +#include +#include + + +BlockedModsDialog::BlockedModsDialog(QWidget *parent, const QString &title, const QString &text, const QString &body, const QList &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); + + this->setWindowTitle(title); + ui->label->setText(text); + ui->textBrowser->setText(body); +} + +BlockedModsDialog::~BlockedModsDialog() { + delete ui; +} + +void BlockedModsDialog::openAll() { + for(auto &url : urls) { + QDesktopServices::openUrl(url); + } +} diff --git a/launcher/ui/dialogs/BlockedModsDialog.h b/launcher/ui/dialogs/BlockedModsDialog.h new file mode 100644 index 00000000..5f5bd61b --- /dev/null +++ b/launcher/ui/dialogs/BlockedModsDialog.h @@ -0,0 +1,22 @@ +#pragma once + +#include + + +QT_BEGIN_NAMESPACE +namespace Ui { class BlockedModsDialog; } +QT_END_NAMESPACE + +class BlockedModsDialog : public QDialog { +Q_OBJECT + +public: + BlockedModsDialog(QWidget *parent, const QString &title, const QString &text, const QString &body, const QList &urls); + + ~BlockedModsDialog() override; + +private: + Ui::BlockedModsDialog *ui; + const QList &urls; + void openAll(); +}; diff --git a/launcher/ui/dialogs/ScrollMessageBox.ui b/launcher/ui/dialogs/BlockedModsDialog.ui similarity index 89% rename from launcher/ui/dialogs/ScrollMessageBox.ui rename to launcher/ui/dialogs/BlockedModsDialog.ui index 299d2ecc..f4ae95b6 100644 --- a/launcher/ui/dialogs/ScrollMessageBox.ui +++ b/launcher/ui/dialogs/BlockedModsDialog.ui @@ -1,7 +1,7 @@ - ScrollMessageBox - + BlockedModsDialog + 0 @@ -11,7 +11,7 @@ - ScrollMessageBox + BlockedModsDialog @@ -51,7 +51,7 @@ buttonBox accepted() - ScrollMessageBox + BlockedModsDialog accept() @@ -67,7 +67,7 @@ buttonBox rejected() - ScrollMessageBox + BlockedModsDialog reject() diff --git a/launcher/ui/dialogs/ScrollMessageBox.cpp b/launcher/ui/dialogs/ScrollMessageBox.cpp deleted file mode 100644 index afdc4bae..00000000 --- a/launcher/ui/dialogs/ScrollMessageBox.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include "ScrollMessageBox.h" -#include "ui_ScrollMessageBox.h" - - -ScrollMessageBox::ScrollMessageBox(QWidget *parent, const QString &title, const QString &text, const QString &body) : - QDialog(parent), ui(new Ui::ScrollMessageBox) { - ui->setupUi(this); - this->setWindowTitle(title); - ui->label->setText(text); - ui->textBrowser->setText(body); -} - -ScrollMessageBox::~ScrollMessageBox() { - delete ui; -} diff --git a/launcher/ui/dialogs/ScrollMessageBox.h b/launcher/ui/dialogs/ScrollMessageBox.h deleted file mode 100644 index 84aa253a..00000000 --- a/launcher/ui/dialogs/ScrollMessageBox.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include - - -QT_BEGIN_NAMESPACE -namespace Ui { class ScrollMessageBox; } -QT_END_NAMESPACE - -class ScrollMessageBox : public QDialog { -Q_OBJECT - -public: - ScrollMessageBox(QWidget *parent, const QString &title, const QString &text, const QString &body); - - ~ScrollMessageBox() override; - -private: - Ui::ScrollMessageBox *ui; -}; From ec87a8ddfc2e6c23ab4c1003f6cafa685ce233d8 Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 16 Jul 2022 20:29:03 -0300 Subject: [PATCH 013/273] fix: add expiration time to cache entries This is to prevent problems where the cache entry would still be used way after the remote resource got updated. The limit is hardcoded for 1 week, which I think is a reasonable time, but this could be further tweaked. Signed-off-by: flow --- launcher/net/HttpMetaCache.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/launcher/net/HttpMetaCache.cpp b/launcher/net/HttpMetaCache.cpp index 4d86c0b8..769f162b 100644 --- a/launcher/net/HttpMetaCache.cpp +++ b/launcher/net/HttpMetaCache.cpp @@ -44,6 +44,11 @@ #include +/** Maximum time to hold a cache entry + * = 1 week in milliseconds + */ +#define TIME_TO_EXPIRE 1*7*24*60*60*1000 + auto MetaEntry::getFullPath() -> QString { // FIXME: make local? @@ -121,6 +126,15 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex SaveEventually(); } + // Get rid of old entries, to prevent cache problems + auto current_time = QDateTime::currentMSecsSinceEpoch(); + auto remote_time = QDateTime::fromString(entry->remote_changed_timestamp).toMSecsSinceEpoch(); + if (current_time - remote_time < TIME_TO_EXPIRE) { + qWarning() << "Removing cache entry because of old age!"; + selected_base.entry_list.remove(resource_path); + return staleEntry(base, resource_path); + } + // entry passed all the checks we cared about. entry->basePath = getBasePath(base); return entry; @@ -240,6 +254,8 @@ void HttpMetaCache::SaveNow() if (m_index_file.isNull()) return; + qDebug() << "[HttpMetaCache]" << "Saving metacache with" << m_entries.size() << "entries"; + QJsonObject toplevel; Json::writeString(toplevel, "version", "1"); From c8a72c876da69de669d5616dd7ff755f157baf99 Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 16 Jul 2022 20:36:14 -0300 Subject: [PATCH 014/273] fix: add missing HttpMetaCache entry for CF mods Signed-off-by: flow --- launcher/Application.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 2bd91fd7..41be6a5b 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -864,6 +864,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_metacache->addBase("ModpacksCHPacks", QDir("cache/ModpacksCHPacks").absolutePath()); m_metacache->addBase("TechnicPacks", QDir("cache/TechnicPacks").absolutePath()); m_metacache->addBase("FlamePacks", QDir("cache/FlamePacks").absolutePath()); + m_metacache->addBase("FlameMods", QDir("cache/FlameMods").absolutePath()); m_metacache->addBase("ModrinthPacks", QDir("cache/ModrinthPacks").absolutePath()); m_metacache->addBase("root", QDir::currentPath()); m_metacache->addBase("translations", QDir("translations").absolutePath()); From 6a1d611fd1db1b1ef6cc54178a8564c13ed4d2ab Mon Sep 17 00:00:00 2001 From: kumquat-ir <66188216+kumquat-ir@users.noreply.github.com> Date: Sun, 17 Jul 2022 02:40:27 -0400 Subject: [PATCH 015/273] Restore ScrollMessageBox Signed-off-by: kumquat-ir <66188216+kumquat-ir@users.noreply.github.com> --- launcher/CMakeLists.txt | 3 + launcher/ui/dialogs/ScrollMessageBox.cpp | 15 +++++ launcher/ui/dialogs/ScrollMessageBox.h | 20 ++++++ launcher/ui/dialogs/ScrollMessageBox.ui | 84 ++++++++++++++++++++++++ 4 files changed, 122 insertions(+) create mode 100644 launcher/ui/dialogs/ScrollMessageBox.cpp create mode 100644 launcher/ui/dialogs/ScrollMessageBox.h create mode 100644 launcher/ui/dialogs/ScrollMessageBox.ui diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 384f72f6..a970581f 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -834,6 +834,8 @@ SET(LAUNCHER_SOURCES ui/dialogs/SkinUploadDialog.h ui/dialogs/ModDownloadDialog.cpp ui/dialogs/ModDownloadDialog.h + ui/dialogs/ScrollMessageBox.cpp + ui/dialogs/ScrollMessageBox.h ui/dialogs/BlockedModsDialog.cpp ui/dialogs/BlockedModsDialog.h @@ -940,6 +942,7 @@ qt_wrap_ui(LAUNCHER_UI ui/dialogs/LoginDialog.ui ui/dialogs/EditAccountDialog.ui ui/dialogs/ReviewMessageBox.ui + ui/dialogs/ScrollMessageBox.ui ui/dialogs/BlockedModsDialog.ui ) diff --git a/launcher/ui/dialogs/ScrollMessageBox.cpp b/launcher/ui/dialogs/ScrollMessageBox.cpp new file mode 100644 index 00000000..afdc4bae --- /dev/null +++ b/launcher/ui/dialogs/ScrollMessageBox.cpp @@ -0,0 +1,15 @@ +#include "ScrollMessageBox.h" +#include "ui_ScrollMessageBox.h" + + +ScrollMessageBox::ScrollMessageBox(QWidget *parent, const QString &title, const QString &text, const QString &body) : + QDialog(parent), ui(new Ui::ScrollMessageBox) { + ui->setupUi(this); + this->setWindowTitle(title); + ui->label->setText(text); + ui->textBrowser->setText(body); +} + +ScrollMessageBox::~ScrollMessageBox() { + delete ui; +} diff --git a/launcher/ui/dialogs/ScrollMessageBox.h b/launcher/ui/dialogs/ScrollMessageBox.h new file mode 100644 index 00000000..84aa253a --- /dev/null +++ b/launcher/ui/dialogs/ScrollMessageBox.h @@ -0,0 +1,20 @@ +#pragma once + +#include + + +QT_BEGIN_NAMESPACE +namespace Ui { class ScrollMessageBox; } +QT_END_NAMESPACE + +class ScrollMessageBox : public QDialog { +Q_OBJECT + +public: + ScrollMessageBox(QWidget *parent, const QString &title, const QString &text, const QString &body); + + ~ScrollMessageBox() override; + +private: + Ui::ScrollMessageBox *ui; +}; diff --git a/launcher/ui/dialogs/ScrollMessageBox.ui b/launcher/ui/dialogs/ScrollMessageBox.ui new file mode 100644 index 00000000..299d2ecc --- /dev/null +++ b/launcher/ui/dialogs/ScrollMessageBox.ui @@ -0,0 +1,84 @@ + + + ScrollMessageBox + + + + 0 + 0 + 400 + 455 + + + + ScrollMessageBox + + + + + + + + + Qt::RichText + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + true + + + true + + + + + + + + + buttonBox + accepted() + ScrollMessageBox + accept() + + + 199 + 425 + + + 199 + 227 + + + + + buttonBox + rejected() + ScrollMessageBox + reject() + + + 199 + 425 + + + 199 + 227 + + + + + From be78afeee54a19cd0115e1d2ec63f3a3201a3940 Mon Sep 17 00:00:00 2001 From: kumquat-ir <66188216+kumquat-ir@users.noreply.github.com> Date: Mon, 18 Jul 2022 14:03:06 -0400 Subject: [PATCH 016/273] qtcreator moment Signed-off-by: kumquat-ir <66188216+kumquat-ir@users.noreply.github.com> --- launcher/ui/dialogs/BlockedModsDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/dialogs/BlockedModsDialog.cpp b/launcher/ui/dialogs/BlockedModsDialog.cpp index de698509..fe87b517 100644 --- a/launcher/ui/dialogs/BlockedModsDialog.cpp +++ b/launcher/ui/dialogs/BlockedModsDialog.cpp @@ -1,6 +1,6 @@ #include "BlockedModsDialog.h" #include "ui_BlockedModsDialog.h" -#include "qpushbutton.h" +#include #include #include From c666c3e2513c47552e91142925b2edabf1fd04ee Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Wed, 20 Jul 2022 14:21:44 +0200 Subject: [PATCH 017/273] refactor!: bump to C++17 and C17 Signed-off-by: Sefa Eyeoglu --- CMakeLists.txt | 7 +- COPYING.md | 28 - launcher/CMakeLists.txt | 1 - launcher/InstanceImportTask.h | 6 +- launcher/MMCZip.cpp | 16 +- launcher/MMCZip.h | 8 +- launcher/minecraft/World.cpp | 8 +- launcher/minecraft/World.h | 6 +- .../atlauncher/ATLPackInstallTask.h | 6 +- .../modplatform/legacy_ftb/PackInstallTask.h | 6 +- .../technic/SingleZipPackInstallTask.h | 6 +- libraries/README.md | 8 - libraries/optional-bare/CMakeLists.txt | 5 - libraries/optional-bare/LICENSE.txt | 23 - libraries/optional-bare/README.md | 5 - .../optional-bare/include/nonstd/optional | 508 ------------------ 16 files changed, 34 insertions(+), 613 deletions(-) delete mode 100644 libraries/optional-bare/CMakeLists.txt delete mode 100644 libraries/optional-bare/LICENSE.txt delete mode 100644 libraries/optional-bare/README.md delete mode 100644 libraries/optional-bare/include/nonstd/optional diff --git a/CMakeLists.txt b/CMakeLists.txt index 33c53b82..bc906ad4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,10 +29,10 @@ set(CMAKE_JAVA_TARGET_OUTPUT_DIR ${PROJECT_BINARY_DIR}/jars) ######## Set compiler flags ######## set(CMAKE_CXX_STANDARD_REQUIRED true) set(CMAKE_C_STANDARD_REQUIRED true) -set(CMAKE_CXX_STANDARD 11) -set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_C_STANDARD 17) include(GenerateExportHeader) -set(CMAKE_CXX_FLAGS "-Wall -pedantic -D_GLIBCXX_USE_CXX11_ABI=0 -fstack-protector-strong --param=ssp-buffer-size=4 ${CMAKE_CXX_FLAGS}") +set(CMAKE_CXX_FLAGS "-Wall -pedantic -fstack-protector-strong --param=ssp-buffer-size=4 ${CMAKE_CXX_FLAGS}") if(UNIX AND APPLE) set(CMAKE_CXX_FLAGS "-stdlib=libc++ ${CMAKE_CXX_FLAGS}") endif() @@ -319,7 +319,6 @@ endif() add_subdirectory(libraries/rainbow) # Qt extension for colors add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions add_subdirectory(libraries/classparser) # class parser library -add_subdirectory(libraries/optional-bare) add_subdirectory(libraries/tomlc99) # toml parser add_subdirectory(libraries/katabasis) # An OAuth2 library that tried to do too much add_subdirectory(libraries/gamemode) diff --git a/COPYING.md b/COPYING.md index 3f9608ff..1dd18e17 100644 --- a/COPYING.md +++ b/COPYING.md @@ -295,34 +295,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# optional-bare - - Code from https://github.com/martinmoene/optional-bare/ - - Boost Software License - Version 1.0 - August 17th, 2003 - - Permission is hereby granted, free of charge, to any person or organization - obtaining a copy of the software and accompanying documentation covered by - this license (the "Software") to use, reproduce, display, distribute, - execute, and transmit the Software, and to prepare derivative works of the - Software, and to permit third-parties to whom the Software is furnished to - do so, all subject to the following: - - The copyright notices in the Software and this entire statement, including - the above license grant, this restriction and the following disclaimer, - must be included in all copies of the Software, in whole or in part, and - all derivative works of the Software, unless such copies or derivative - works are solely in the form of machine-executable object code generated by - a source language processor. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT - SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE - FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, - ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. - # tomlc99 MIT License diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 3c9aee6a..b540dcab 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -987,7 +987,6 @@ target_link_libraries(Launcher_logic Launcher_murmur2 nbt++ ${ZLIB_LIBRARIES} - optional-bare tomlc99 BuildConfig Katabasis diff --git a/launcher/InstanceImportTask.h b/launcher/InstanceImportTask.h index b67d48f3..48ba2161 100644 --- a/launcher/InstanceImportTask.h +++ b/launcher/InstanceImportTask.h @@ -44,7 +44,7 @@ #include "QObjectPtr.h" #include "modplatform/flame/PackManifest.h" -#include +#include class QuaZip; namespace Flame @@ -90,8 +90,8 @@ private: /* data */ QString m_archivePath; bool m_downloadRequired = false; std::unique_ptr m_packZip; - QFuture> m_extractFuture; - QFutureWatcher> m_extractFutureWatcher; + QFuture> m_extractFuture; + QFutureWatcher> m_extractFutureWatcher; QVector m_blockedMods; enum class ModpackType{ Unknown, diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index 1627ee07..71cca03b 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -267,7 +267,7 @@ bool MMCZip::findFilesInZip(QuaZip * zip, const QString & what, QStringList & re // ours -nonstd::optional MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QString &target) +std::optional MMCZip::extractSubDir(QuaZip *zip, const QString & subdir, const QString &target) { QDir directory(target); QStringList extracted; @@ -276,7 +276,7 @@ nonstd::optional MMCZip::extractSubDir(QuaZip *zip, const QString & auto numEntries = zip->getEntriesCount(); if(numEntries < 0) { qWarning() << "Failed to enumerate files in archive"; - return nonstd::nullopt; + return std::nullopt; } else if(numEntries == 0) { qDebug() << "Extracting empty archives seems odd..."; @@ -285,7 +285,7 @@ nonstd::optional MMCZip::extractSubDir(QuaZip *zip, const QString & else if (!zip->goToFirstFile()) { qWarning() << "Failed to seek to first file in zip"; - return nonstd::nullopt; + return std::nullopt; } do @@ -322,7 +322,7 @@ nonstd::optional MMCZip::extractSubDir(QuaZip *zip, const QString & { qWarning() << "Failed to extract file" << original_name << "to" << absFilePath; JlCompress::removeFile(extracted); - return nonstd::nullopt; + return std::nullopt; } extracted.append(absFilePath); @@ -340,7 +340,7 @@ bool MMCZip::extractRelFile(QuaZip *zip, const QString &file, const QString &tar } // ours -nonstd::optional MMCZip::extractDir(QString fileCompressed, QString dir) +std::optional MMCZip::extractDir(QString fileCompressed, QString dir) { QuaZip zip(fileCompressed); if (!zip.open(QuaZip::mdUnzip)) @@ -351,13 +351,13 @@ nonstd::optional MMCZip::extractDir(QString fileCompressed, QString return QStringList(); } qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();; - return nonstd::nullopt; + return std::nullopt; } return MMCZip::extractSubDir(&zip, "", dir); } // ours -nonstd::optional MMCZip::extractDir(QString fileCompressed, QString subdir, QString dir) +std::optional MMCZip::extractDir(QString fileCompressed, QString subdir, QString dir) { QuaZip zip(fileCompressed); if (!zip.open(QuaZip::mdUnzip)) @@ -368,7 +368,7 @@ nonstd::optional MMCZip::extractDir(QString fileCompressed, QString return QStringList(); } qWarning() << "Could not open archive for unzipping:" << fileCompressed << "Error:" << zip.getZipError();; - return nonstd::nullopt; + return std::nullopt; } return MMCZip::extractSubDir(&zip, subdir, dir); } diff --git a/launcher/MMCZip.h b/launcher/MMCZip.h index 7f43d158..ce9775bd 100644 --- a/launcher/MMCZip.h +++ b/launcher/MMCZip.h @@ -42,7 +42,7 @@ #include #include -#include +#include namespace MMCZip { @@ -95,7 +95,7 @@ namespace MMCZip /** * Extract a subdirectory from an archive */ - nonstd::optional extractSubDir(QuaZip *zip, const QString & subdir, const QString &target); + std::optional extractSubDir(QuaZip *zip, const QString & subdir, const QString &target); bool extractRelFile(QuaZip *zip, const QString & file, const QString &target); @@ -106,7 +106,7 @@ namespace MMCZip * \param dir The directory to extract to, the current directory if left empty. * \return The list of the full paths of the files extracted, empty on failure. */ - nonstd::optional extractDir(QString fileCompressed, QString dir); + std::optional extractDir(QString fileCompressed, QString dir); /** * Extract a subdirectory from an archive @@ -116,7 +116,7 @@ namespace MMCZip * \param dir The directory to extract to, the current directory if left empty. * \return The list of the full paths of the files extracted, empty on failure. */ - nonstd::optional extractDir(QString fileCompressed, QString subdir, QString dir); + std::optional extractDir(QString fileCompressed, QString subdir, QString dir); /** * Extract a single file from an archive into a directory diff --git a/launcher/minecraft/World.cpp b/launcher/minecraft/World.cpp index dfcb43d8..90fcf337 100644 --- a/launcher/minecraft/World.cpp +++ b/launcher/minecraft/World.cpp @@ -53,12 +53,12 @@ #include -#include +#include -using nonstd::optional; -using nonstd::nullopt; +using std::optional; +using std::nullopt; -GameType::GameType(nonstd::optional original): +GameType::GameType(std::optional original): original(original) { if(!original) { diff --git a/launcher/minecraft/World.h b/launcher/minecraft/World.h index 0f587620..8327253a 100644 --- a/launcher/minecraft/World.h +++ b/launcher/minecraft/World.h @@ -16,11 +16,11 @@ #pragma once #include #include -#include +#include struct GameType { GameType() = default; - GameType (nonstd::optional original); + GameType (std::optional original); QString toTranslatedString() const; QString toLogString() const; @@ -33,7 +33,7 @@ struct GameType { Adventure, Spectator } type = Unknown; - nonstd::optional original; + std::optional original; }; class World diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.h b/launcher/modplatform/atlauncher/ATLPackInstallTask.h index f55873e9..992ba9c5 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.h +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.h @@ -46,7 +46,7 @@ #include "minecraft/PackProfile.h" #include "meta/Version.h" -#include +#include namespace ATLauncher { @@ -131,8 +131,8 @@ private: Meta::VersionPtr minecraftVersion; QMap componentsToInstall; - QFuture> m_extractFuture; - QFutureWatcher> m_extractFutureWatcher; + QFuture> m_extractFuture; + QFutureWatcher> m_extractFutureWatcher; QFuture m_modExtractFuture; QFutureWatcher m_modExtractFutureWatcher; diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.h b/launcher/modplatform/legacy_ftb/PackInstallTask.h index a7395220..da4c0da5 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.h +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.h @@ -10,7 +10,7 @@ #include "net/NetJob.h" -#include +#include namespace LegacyFTB { @@ -46,8 +46,8 @@ private: /* data */ shared_qobject_ptr m_network; bool abortable = false; std::unique_ptr m_packZip; - QFuture> m_extractFuture; - QFutureWatcher> m_extractFutureWatcher; + QFuture> m_extractFuture; + QFutureWatcher> m_extractFutureWatcher; NetJob::Ptr netJobContainer; QString archivePath; diff --git a/launcher/modplatform/technic/SingleZipPackInstallTask.h b/launcher/modplatform/technic/SingleZipPackInstallTask.h index 4d1fcbff..981ccf8a 100644 --- a/launcher/modplatform/technic/SingleZipPackInstallTask.h +++ b/launcher/modplatform/technic/SingleZipPackInstallTask.h @@ -24,7 +24,7 @@ #include #include -#include +#include namespace Technic { @@ -57,8 +57,8 @@ private: QString m_archivePath; NetJob::Ptr m_filesNetJob; std::unique_ptr m_packZip; - QFuture> m_extractFuture; - QFutureWatcher> m_extractFutureWatcher; + QFuture> m_extractFuture; + QFutureWatcher> m_extractFutureWatcher; }; } // namespace Technic diff --git a/libraries/README.md b/libraries/README.md index 946e34d8..37f53385 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -155,14 +155,6 @@ Canonical implementation of the murmur2 hash, taken from [SMHasher](https://gith Public domain (the author disclaimed the copyright). -## optional-bare - -A simple single-file header-only version of a C++17-like optional for default-constructible, copyable types, for C++98 and later. - -Imported from: https://github.com/martinmoene/optional-bare/commit/0bb1d183bcee1e854c4ea196b533252c51f98b81 - -Boost Software License - Version 1.0 - ## quazip A zip manipulation library, forked for MultiMC's use. diff --git a/libraries/optional-bare/CMakeLists.txt b/libraries/optional-bare/CMakeLists.txt deleted file mode 100644 index 952df6e2..00000000 --- a/libraries/optional-bare/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -cmake_minimum_required(VERSION 3.9.4) -project(optional-bare) - -add_library(optional-bare INTERFACE) -target_include_directories(optional-bare INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include") diff --git a/libraries/optional-bare/LICENSE.txt b/libraries/optional-bare/LICENSE.txt deleted file mode 100644 index 36b7cd93..00000000 --- a/libraries/optional-bare/LICENSE.txt +++ /dev/null @@ -1,23 +0,0 @@ -Boost Software License - Version 1.0 - August 17th, 2003 - -Permission is hereby granted, free of charge, to any person or organization -obtaining a copy of the software and accompanying documentation covered by -this license (the "Software") to use, reproduce, display, distribute, -execute, and transmit the Software, and to prepare derivative works of the -Software, and to permit third-parties to whom the Software is furnished to -do so, all subject to the following: - -The copyright notices in the Software and this entire statement, including -the above license grant, this restriction and the following disclaimer, -must be included in all copies of the Software, in whole or in part, and -all derivative works of the Software, unless such copies or derivative -works are solely in the form of machine-executable object code generated by -a source language processor. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT -SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE -FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, -ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. diff --git a/libraries/optional-bare/README.md b/libraries/optional-bare/README.md deleted file mode 100644 index e29ff7c1..00000000 --- a/libraries/optional-bare/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# optional bare - -A simple single-file header-only version of a C++17-like optional for default-constructible, copyable types, for C++98 and later. - -Imported from: https://github.com/martinmoene/optional-bare/commit/0bb1d183bcee1e854c4ea196b533252c51f98b81 diff --git a/libraries/optional-bare/include/nonstd/optional b/libraries/optional-bare/include/nonstd/optional deleted file mode 100644 index ecbfa030..00000000 --- a/libraries/optional-bare/include/nonstd/optional +++ /dev/null @@ -1,508 +0,0 @@ -// -// Copyright 2017-2019 by Martin Moene -// -// https://github.com/martinmoene/optional-bare -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) - -#ifndef NONSTD_OPTIONAL_BARE_HPP -#define NONSTD_OPTIONAL_BARE_HPP - -#define optional_bare_MAJOR 1 -#define optional_bare_MINOR 1 -#define optional_bare_PATCH 0 - -#define optional_bare_VERSION optional_STRINGIFY(optional_bare_MAJOR) "." optional_STRINGIFY(optional_bare_MINOR) "." optional_STRINGIFY(optional_bare_PATCH) - -#define optional_STRINGIFY( x ) optional_STRINGIFY_( x ) -#define optional_STRINGIFY_( x ) #x - -// optional-bare configuration: - -#define optional_OPTIONAL_DEFAULT 0 -#define optional_OPTIONAL_NONSTD 1 -#define optional_OPTIONAL_STD 2 - -#if !defined( optional_CONFIG_SELECT_OPTIONAL ) -# define optional_CONFIG_SELECT_OPTIONAL ( optional_HAVE_STD_OPTIONAL ? optional_OPTIONAL_STD : optional_OPTIONAL_NONSTD ) -#endif - -// Control presence of exception handling (try and auto discover): - -#ifndef optional_CONFIG_NO_EXCEPTIONS -# if _MSC_VER -# include // for _HAS_EXCEPTIONS -# endif -# if _MSC_VER -# include // for _HAS_EXCEPTIONS -# endif -# if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || (_HAS_EXCEPTIONS) -# define optional_CONFIG_NO_EXCEPTIONS 0 -# else -# define optional_CONFIG_NO_EXCEPTIONS 1 -# endif -#endif - -// C++ language version detection (C++20 is speculative): -// Note: VC14.0/1900 (VS2015) lacks too much from C++14. - -#ifndef optional_CPLUSPLUS -# if defined(_MSVC_LANG ) && !defined(__clang__) -# define optional_CPLUSPLUS (_MSC_VER == 1900 ? 201103L : _MSVC_LANG ) -# else -# define optional_CPLUSPLUS __cplusplus -# endif -#endif - -#define optional_CPP98_OR_GREATER ( optional_CPLUSPLUS >= 199711L ) -#define optional_CPP11_OR_GREATER ( optional_CPLUSPLUS >= 201103L ) -#define optional_CPP14_OR_GREATER ( optional_CPLUSPLUS >= 201402L ) -#define optional_CPP17_OR_GREATER ( optional_CPLUSPLUS >= 201703L ) -#define optional_CPP20_OR_GREATER ( optional_CPLUSPLUS >= 202000L ) - -// C++ language version (represent 98 as 3): - -#define optional_CPLUSPLUS_V ( optional_CPLUSPLUS / 100 - (optional_CPLUSPLUS > 200000 ? 2000 : 1994) ) - -// Use C++17 std::optional if available and requested: - -#if optional_CPP17_OR_GREATER && defined(__has_include ) -# if __has_include( ) -# define optional_HAVE_STD_OPTIONAL 1 -# else -# define optional_HAVE_STD_OPTIONAL 0 -# endif -#else -# define optional_HAVE_STD_OPTIONAL 0 -#endif - -#define optional_USES_STD_OPTIONAL ( (optional_CONFIG_SELECT_OPTIONAL == optional_OPTIONAL_STD) || ((optional_CONFIG_SELECT_OPTIONAL == optional_OPTIONAL_DEFAULT) && optional_HAVE_STD_OPTIONAL) ) - -// -// Using std::optional: -// - -#if optional_USES_STD_OPTIONAL - -#include -#include - -namespace nonstd { - - using std::in_place; - using std::in_place_type; - using std::in_place_index; - using std::in_place_t; - using std::in_place_type_t; - using std::in_place_index_t; - - using std::optional; - using std::bad_optional_access; - using std::hash; - - using std::nullopt; - using std::nullopt_t; - - using std::operator==; - using std::operator!=; - using std::operator<; - using std::operator<=; - using std::operator>; - using std::operator>=; - using std::make_optional; - using std::swap; -} - -#else // optional_USES_STD_OPTIONAL - -#include - -#if ! optional_CONFIG_NO_EXCEPTIONS -# include -#endif - -namespace nonstd { namespace optional_bare { - -// type for nullopt - -struct nullopt_t -{ - struct init{}; - nullopt_t( init ) {} -}; - -// extra parenthesis to prevent the most vexing parse: - -const nullopt_t nullopt(( nullopt_t::init() )); - -// optional access error. - -#if ! optional_CONFIG_NO_EXCEPTIONS - -class bad_optional_access : public std::logic_error -{ -public: - explicit bad_optional_access() - : logic_error( "bad optional access" ) {} -}; - -#endif // optional_CONFIG_NO_EXCEPTIONS - -// Simplistic optional: requires T to be default constructible, copyable. - -template< typename T > -class optional -{ -private: - typedef void (optional::*safe_bool)() const; - -public: - typedef T value_type; - - optional() - : has_value_( false ) - {} - - optional( nullopt_t ) - : has_value_( false ) - {} - - optional( T const & arg ) - : has_value_( true ) - , value_ ( arg ) - {} - - template< class U > - optional( optional const & other ) - : has_value_( other.has_value() ) - , value_ ( other.value() ) - {} - - optional & operator=( nullopt_t ) - { - reset(); - return *this; - } - - template< class U > - optional & operator=( optional const & other ) - { - has_value_ = other.has_value(); - value_ = other.value(); - return *this; - } - - void swap( optional & rhs ) - { - using std::swap; - if ( has_value() == true && rhs.has_value() == true ) { swap( **this, *rhs ); } - else if ( has_value() == false && rhs.has_value() == true ) { initialize( *rhs ); rhs.reset(); } - else if ( has_value() == true && rhs.has_value() == false ) { rhs.initialize( **this ); reset(); } - } - - // observers - - value_type const * operator->() const - { - return assert( has_value() ), - &value_; - } - - value_type * operator->() - { - return assert( has_value() ), - &value_; - } - - value_type const & operator*() const - { - return assert( has_value() ), - value_; - } - - value_type & operator*() - { - return assert( has_value() ), - value_; - } - -#if optional_CPP11_OR_GREATER - explicit operator bool() const - { - return has_value(); - } -#else - operator safe_bool() const - { - return has_value() ? &optional::this_type_does_not_support_comparisons : 0; - } -#endif - - bool has_value() const - { - return has_value_; - } - - value_type const & value() const - { -#if optional_CONFIG_NO_EXCEPTIONS - assert( has_value() ); -#else - if ( ! has_value() ) - throw bad_optional_access(); -#endif - return value_; - } - - value_type & value() - { -#if optional_CONFIG_NO_EXCEPTIONS - assert( has_value() ); -#else - if ( ! has_value() ) - throw bad_optional_access(); -#endif - return value_; - } - - template< class U > - value_type value_or( U const & v ) const - { - return has_value() ? value() : static_cast( v ); - } - - // modifiers - - void reset() - { - has_value_ = false; - } - -private: - void this_type_does_not_support_comparisons() const {} - - template< typename V > - void initialize( V const & value ) - { - assert( ! has_value() ); - value_ = value; - has_value_ = true; - } - -private: - bool has_value_; - value_type value_; -}; - -// Relational operators - -template< typename T, typename U > -inline bool operator==( optional const & x, optional const & y ) -{ - return bool(x) != bool(y) ? false : bool(x) == false ? true : *x == *y; -} - -template< typename T, typename U > -inline bool operator!=( optional const & x, optional const & y ) -{ - return !(x == y); -} - -template< typename T, typename U > -inline bool operator<( optional const & x, optional const & y ) -{ - return (!y) ? false : (!x) ? true : *x < *y; -} - -template< typename T, typename U > -inline bool operator>( optional const & x, optional const & y ) -{ - return (y < x); -} - -template< typename T, typename U > -inline bool operator<=( optional const & x, optional const & y ) -{ - return !(y < x); -} - -template< typename T, typename U > -inline bool operator>=( optional const & x, optional const & y ) -{ - return !(x < y); -} - -// Comparison with nullopt - -template< typename T > -inline bool operator==( optional const & x, nullopt_t ) -{ - return (!x); -} - -template< typename T > -inline bool operator==( nullopt_t, optional const & x ) -{ - return (!x); -} - -template< typename T > -inline bool operator!=( optional const & x, nullopt_t ) -{ - return bool(x); -} - -template< typename T > -inline bool operator!=( nullopt_t, optional const & x ) -{ - return bool(x); -} - -template< typename T > -inline bool operator<( optional const &, nullopt_t ) -{ - return false; -} - -template< typename T > -inline bool operator<( nullopt_t, optional const & x ) -{ - return bool(x); -} - -template< typename T > -inline bool operator<=( optional const & x, nullopt_t ) -{ - return (!x); -} - -template< typename T > -inline bool operator<=( nullopt_t, optional const & ) -{ - return true; -} - -template< typename T > -inline bool operator>( optional const & x, nullopt_t ) -{ - return bool(x); -} - -template< typename T > -inline bool operator>( nullopt_t, optional const & ) -{ - return false; -} - -template< typename T > -inline bool operator>=( optional const &, nullopt_t ) -{ - return true; -} - -template< typename T > -inline bool operator>=( nullopt_t, optional const & x ) -{ - return (!x); -} - -// Comparison with T - -template< typename T, typename U > -inline bool operator==( optional const & x, U const & v ) -{ - return bool(x) ? *x == v : false; -} - -template< typename T, typename U > -inline bool operator==( U const & v, optional const & x ) -{ - return bool(x) ? v == *x : false; -} - -template< typename T, typename U > -inline bool operator!=( optional const & x, U const & v ) -{ - return bool(x) ? *x != v : true; -} - -template< typename T, typename U > -inline bool operator!=( U const & v, optional const & x ) -{ - return bool(x) ? v != *x : true; -} - -template< typename T, typename U > -inline bool operator<( optional const & x, U const & v ) -{ - return bool(x) ? *x < v : true; -} - -template< typename T, typename U > -inline bool operator<( U const & v, optional const & x ) -{ - return bool(x) ? v < *x : false; -} - -template< typename T, typename U > -inline bool operator<=( optional const & x, U const & v ) -{ - return bool(x) ? *x <= v : true; -} - -template< typename T, typename U > -inline bool operator<=( U const & v, optional const & x ) -{ - return bool(x) ? v <= *x : false; -} - -template< typename T, typename U > -inline bool operator>( optional const & x, U const & v ) -{ - return bool(x) ? *x > v : false; -} - -template< typename T, typename U > -inline bool operator>( U const & v, optional const & x ) -{ - return bool(x) ? v > *x : true; -} - -template< typename T, typename U > -inline bool operator>=( optional const & x, U const & v ) -{ - return bool(x) ? *x >= v : false; -} - -template< typename T, typename U > -inline bool operator>=( U const & v, optional const & x ) -{ - return bool(x) ? v >= *x : true; -} - -// Specialized algorithms - -template< typename T > -void swap( optional & x, optional & y ) -{ - x.swap( y ); -} - -// Convenience function to create an optional. - -template< typename T > -inline optional make_optional( T const & v ) -{ - return optional( v ); -} - -} // namespace optional-bare - -using namespace optional_bare; - -} // namespace nonstd - -#endif // optional_USES_STD_OPTIONAL - -#endif // NONSTD_OPTIONAL_BARE_HPP From ab6e1b112b05a1fcad962d15c087ed9ae746bf87 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 17 Jul 2022 11:24:12 -0300 Subject: [PATCH 018/273] change(cache): use cache-specific http headers for their lifetime This uses the 'Age', 'Cache-Control' and 'Expires' HTTP headers to more accurately set up the cache lifetime, falling back to a static 1-week time if they're not present in the response. Signed-off-by: flow --- launcher/net/HttpMetaCache.cpp | 14 ++++++------- launcher/net/HttpMetaCache.h | 10 +++++++++ launcher/net/MetaCacheSink.cpp | 38 +++++++++++++++++++++++++++++++++- 3 files changed, 53 insertions(+), 9 deletions(-) diff --git a/launcher/net/HttpMetaCache.cpp b/launcher/net/HttpMetaCache.cpp index 769f162b..deb2780b 100644 --- a/launcher/net/HttpMetaCache.cpp +++ b/launcher/net/HttpMetaCache.cpp @@ -44,11 +44,6 @@ #include -/** Maximum time to hold a cache entry - * = 1 week in milliseconds - */ -#define TIME_TO_EXPIRE 1*7*24*60*60*1000 - auto MetaEntry::getFullPath() -> QString { // FIXME: make local? @@ -127,9 +122,8 @@ auto HttpMetaCache::resolveEntry(QString base, QString resource_path, QString ex } // Get rid of old entries, to prevent cache problems - auto current_time = QDateTime::currentMSecsSinceEpoch(); - auto remote_time = QDateTime::fromString(entry->remote_changed_timestamp).toMSecsSinceEpoch(); - if (current_time - remote_time < TIME_TO_EXPIRE) { + auto current_time = QDateTime::currentSecsSinceEpoch(); + if (entry->isExpired(current_time - ( file_last_changed / 1000 ))) { qWarning() << "Removing cache entry because of old age!"; selected_base.entry_list.remove(resource_path); return staleEntry(base, resource_path); @@ -235,6 +229,8 @@ void HttpMetaCache::Load() 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->current_age = Json::ensureDouble(element_obj, "current_age"); + foo->max_age = Json::ensureDouble(element_obj, "max_age"); // presumed innocent until closer examination foo->stale = false; @@ -275,6 +271,8 @@ void HttpMetaCache::SaveNow() 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)); + entryObj.insert("current_age", QJsonValue(double(entry->current_age))); + entryObj.insert("max_age", QJsonValue(double(entry->max_age))); entriesArr.append(entryObj); } } diff --git a/launcher/net/HttpMetaCache.h b/launcher/net/HttpMetaCache.h index e944b3d5..df3549e8 100644 --- a/launcher/net/HttpMetaCache.h +++ b/launcher/net/HttpMetaCache.h @@ -64,6 +64,14 @@ class MetaEntry { auto getMD5Sum() -> QString { return md5sum; } void setMD5Sum(QString md5sum) { this->md5sum = md5sum; } + auto getCurrentAge() -> qint64 { return current_age; } + void setCurrentAge(qint64 age) { current_age = age; } + + auto getMaximumAge() -> qint64 { return max_age; } + void setMaximumAge(qint64 age) { max_age = age; } + + bool isExpired(qint64 offset) { return current_age >= max_age - offset; }; + protected: QString baseId; QString basePath; @@ -72,6 +80,8 @@ class MetaEntry { 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 stale = true; }; diff --git a/launcher/net/MetaCacheSink.cpp b/launcher/net/MetaCacheSink.cpp index f86dd870..ab0c9fcb 100644 --- a/launcher/net/MetaCacheSink.cpp +++ b/launcher/net/MetaCacheSink.cpp @@ -36,11 +36,16 @@ #include "MetaCacheSink.h" #include #include -#include "FileSystem.h" #include "Application.h" namespace Net { +/** Maximum time to hold a cache entry + * = 1 week in seconds + */ +#define MAX_TIME_TO_EXPIRE 1*7*24*60*60 + + MetaCacheSink::MetaCacheSink(MetaEntryPtr entry, ChecksumValidator * md5sum) :Net::FileSink(entry->getFullPath()), m_entry(entry), m_md5Node(md5sum) { @@ -88,6 +93,37 @@ Task::State MetaCacheSink::finalizeCache(QNetworkReply & reply) } m_entry->setLocalChangedTimestamp(output_file_info.lastModified().toUTC().toMSecsSinceEpoch()); + + { // Cache lifetime + if (reply.hasRawHeader("Cache-Control")) { + auto cache_control_header = reply.rawHeader("Cache-Control"); + // qDebug() << "[MetaCache] Parsing 'Cache-Control' header with" << cache_control_header; + + QRegularExpression max_age_expr("max-age=([0-9]+)"); + qint64 max_age = max_age_expr.match(cache_control_header).captured(1).toLongLong(); + m_entry->setMaximumAge(max_age); + + } else if (reply.hasRawHeader("Expires")) { + auto expires_header = reply.rawHeader("Expires"); + // qDebug() << "[MetaCache] Parsing 'Expires' header with" << expires_header; + + qint64 max_age = QDateTime::fromString(expires_header).toSecsSinceEpoch() - QDateTime::currentSecsSinceEpoch(); + m_entry->setMaximumAge(max_age); + } else { + m_entry->setMaximumAge(MAX_TIME_TO_EXPIRE); + } + + if (reply.hasRawHeader("Age")) { + auto age_header = reply.rawHeader("Age"); + // qDebug() << "[MetaCache] Parsing 'Age' header with" << age_header; + + qint64 current_age = age_header.toLongLong(); + m_entry->setCurrentAge(current_age); + } else { + m_entry->setCurrentAge(0); + } + } + m_entry->setStale(false); APPLICATION->metacache()->updateEntry(m_entry); From 813dfbd2d38e910ef018927c58662db1e4c45e52 Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 23 Jul 2022 12:37:45 -0300 Subject: [PATCH 019/273] fix: hide the entire performance tab on instance settings in non-Linux "just woke up after a major release has just been made" coding style Signed-off-by: flow --- launcher/ui/pages/instance/InstanceSettingsPage.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index fcc110de..f11cf992 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp @@ -349,7 +349,7 @@ void InstanceSettingsPage::loadSettings() ui->useDiscreteGpuCheck->setChecked(m_settings->get("UseDiscreteGpu").toBool()); #if !defined(Q_OS_LINUX) - ui->perfomanceGroupBox->setVisible(false); + ui->settingsTabs->setTabVisible(ui->settingsTabs->indexOf(ui->performancePage), false); #endif // Miscellanous From 1157436a24cc219295512bbcf6b14f590969510d Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 23 Jul 2022 14:21:28 -0300 Subject: [PATCH 020/273] fix: sigsegv when trying to use jar mods Signed-off-by: flow --- launcher/MMCZip.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index 1627ee07..8518e606 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -141,9 +141,10 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const QSet addedFiles; // Modify the jar - for (auto i = mods.constEnd(); i != mods.constBegin(); --i) + // This needs to be done in reverse-order to ensure we respect the loading order of components + for (auto i = mods.crbegin(); i != mods.crend(); i++) { - const Mod* mod = *i; + const auto* mod = *i; // do not merge disabled mods. if (!mod->enabled()) continue; From bfa824ee71cc1a2221106cb884fcb16a0d997cd9 Mon Sep 17 00:00:00 2001 From: jopejoe1 <34899572+jopejoe1@users.noreply.github.com> Date: Sat, 23 Jul 2022 23:18:48 +0200 Subject: [PATCH 021/273] Fix broken url in readme Signed-off-by: jopejoe1 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b9c23fec..27a32a56 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ The translation effort for PolyMC is hosted on [Weblate](https://hosted.weblate. ## Download information -To modify download information or change packaging information send a pull request or issue to the website [Here](https://github.com/PolyMC/polymc.github.io/blob/master/src/download.md) +To modify download information or change packaging information send a pull request or issue to the website [Here](https://github.com/PolyMC/polymc.github.io/tree/master/src/download) ## Forking/Redistributing/Custom builds policy From 3aa2003951eee6a25a43fc8a78480f1512306429 Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 23 Jul 2022 23:50:56 -0300 Subject: [PATCH 022/273] fix: filter in external resource pages not working For some reason, using setFilterFixedString() doesn't seem to update the QRegularExpression object with a new value, instead leaving it empty. It updates QRegExp just fine, so maybe that's an Qt bug? o.O Anyway, using regex in the filter is kinda cool actually :D Signed-off-by: flow --- launcher/ui/pages/instance/ExternalResourcesPage.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp index d06f412b..69c20309 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.cpp +++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp @@ -32,13 +32,13 @@ class SortProxy : public QSortFilterProxyModel { const auto& mod = model->at(source_row); - if (mod.name().contains(filterRegularExpression())) + if (filterRegularExpression().match(mod.name()).hasMatch()) return true; - if (mod.description().contains(filterRegularExpression())) + if (filterRegularExpression().match(mod.description()).hasMatch()) return true; for (auto& author : mod.authors()) { - if (author.contains(filterRegularExpression())) { + if (filterRegularExpression().match(author).hasMatch()) { return true; } } @@ -182,7 +182,7 @@ void ExternalResourcesPage::retranslate() void ExternalResourcesPage::filterTextChanged(const QString& newContents) { m_viewFilter = newContents; - m_filterModel->setFilterFixedString(m_viewFilter); + m_filterModel->setFilterRegularExpression(m_viewFilter); } void ExternalResourcesPage::runningStateChanged(bool running) From cfda8dbb2b0aeded851d45465cc3ea4b6901229c Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 23 Jul 2022 23:11:09 -0300 Subject: [PATCH 023/273] refactor: use QIODevice instead of a whole QByteArray for hash calc. This allows Qt to do its thing and optimize the data gathering from the JAR. Signed-off-by: flow --- launcher/modplatform/ModIndex.cpp | 36 +++++++++++++------------------ launcher/modplatform/ModIndex.h | 4 +++- 2 files changed, 18 insertions(+), 22 deletions(-) diff --git a/launcher/modplatform/ModIndex.cpp b/launcher/modplatform/ModIndex.cpp index 3c4b7887..34fd9f30 100644 --- a/launcher/modplatform/ModIndex.cpp +++ b/launcher/modplatform/ModIndex.cpp @@ -19,6 +19,8 @@ #include "modplatform/ModIndex.h" #include +#include +#include namespace ModPlatform { @@ -53,34 +55,26 @@ auto ProviderCapabilities::hashType(Provider p) -> QStringList } return {}; } -auto ProviderCapabilities::hash(Provider p, QByteArray& data, QString type) -> QByteArray + +auto ProviderCapabilities::hash(Provider p, QIODevice* device, QString type) -> QString { + QCryptographicHash::Algorithm algo = QCryptographicHash::Sha1; switch (p) { case Provider::MODRINTH: { - // NOTE: Data is the result of reading the entire JAR file! - - // If 'type' was specified, we use that - if (!type.isEmpty() && hashType(p).contains(type)) { - if (type == "sha512") - return QCryptographicHash::hash(data, QCryptographicHash::Sha512); - else if (type == "sha1") - return QCryptographicHash::hash(data, QCryptographicHash::Sha1); - } - - return QCryptographicHash::hash(data, QCryptographicHash::Sha512); + algo = (type == "sha1") ? QCryptographicHash::Sha1 : QCryptographicHash::Sha512; + break; } case Provider::FLAME: - // If 'type' was specified, we use that - if (!type.isEmpty() && hashType(p).contains(type)) { - if(type == "sha1") - return QCryptographicHash::hash(data, QCryptographicHash::Sha1); - else if (type == "md5") - return QCryptographicHash::hash(data, QCryptographicHash::Md5); - } - + algo = (type == "sha1") ? QCryptographicHash::Sha1 : QCryptographicHash::Md5; break; } - return {}; + + QCryptographicHash hash(algo); + if(!hash.addData(device)) + qCritical() << "Failed to read JAR to create hash!"; + + Q_ASSERT(hash.result().length() == hash.hashLength(algo)); + return { hash.result().toHex() }; } } // namespace ModPlatform diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index dc297d03..89fe1c5c 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -24,6 +24,8 @@ #include #include +class QIODevice; + namespace ModPlatform { enum class Provider { @@ -36,7 +38,7 @@ class ProviderCapabilities { auto name(Provider) -> const char*; auto readableName(Provider) -> QString; auto hashType(Provider) -> QStringList; - auto hash(Provider, QByteArray&, QString type = "") -> QByteArray; + auto hash(Provider, QIODevice*, QString type = "") -> QString; }; struct ModpackAuthor { From 15ec1abb6a3acb77b36f14d3ddccc97a8df8c8e1 Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 23 Jul 2022 23:13:53 -0300 Subject: [PATCH 024/273] feat: use QIODevice for calcuating the JAR hash on Modrinth Signed-off-by: flow --- launcher/modplatform/EnsureMetadataTask.cpp | 30 ++++++++++++------- .../modrinth/ModrinthCheckUpdate.cpp | 8 +++-- 2 files changed, 24 insertions(+), 14 deletions(-) diff --git a/launcher/modplatform/EnsureMetadataTask.cpp b/launcher/modplatform/EnsureMetadataTask.cpp index 60c54c4e..a5c9cbca 100644 --- a/launcher/modplatform/EnsureMetadataTask.cpp +++ b/launcher/modplatform/EnsureMetadataTask.cpp @@ -50,21 +50,29 @@ EnsureMetadataTask::EnsureMetadataTask(QList& mods, QDir dir, ModPlatform: QString EnsureMetadataTask::getHash(Mod* mod) { /* Here we create a mapping hash -> mod, because we need that relationship to parse the API routes */ - QByteArray jar_data; - try { - jar_data = FS::read(mod->fileinfo().absoluteFilePath()); - } catch (FS::FileSystemException& e) { - qCritical() << QString("Failed to open / read JAR file of %1").arg(mod->name()); - qCritical() << QString("Reason: ") << e.cause(); - + if (mod->type() == Mod::MOD_FOLDER) return {}; - } + QString result; switch (m_provider) { case ModPlatform::Provider::MODRINTH: { - auto hash_type = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH).first(); + QFile file(mod->fileinfo().absoluteFilePath()); - return QString(ProviderCaps.hash(ModPlatform::Provider::MODRINTH, jar_data, hash_type).toHex()); + try { + file.open(QFile::ReadOnly); + } catch (FS::FileSystemException& e) { + qCritical() << QString("Failed to open JAR file of %1").arg(mod->name()); + qCritical() << QString("Reason: ") << e.cause(); + + return {}; + } + + auto hash_type = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH).first(); + result = ProviderCaps.hash(ModPlatform::Provider::MODRINTH, &file, hash_type); + + file.close(); + + break; } case ModPlatform::Provider::FLAME: { QByteArray jar_data_treated; @@ -78,7 +86,7 @@ QString EnsureMetadataTask::getHash(Mod* mod) } } - return {}; + return result; } bool EnsureMetadataTask::abort() diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp index 79d8edf7..f4898591 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp @@ -46,17 +46,19 @@ void ModrinthCheckUpdate::executeTask() if (mod->metadata()->hash_format != best_hash_type) { QByteArray jar_data; + QFile file(mod->fileinfo().absoluteFilePath()); try { - jar_data = FS::read(mod->fileinfo().absoluteFilePath()); + file.open(QFile::ReadOnly); } catch (FS::FileSystemException& e) { - qCritical() << QString("Failed to open / read JAR file of %1").arg(mod->name()); + qCritical() << QString("Failed to open JAR file of %1").arg(mod->name()); qCritical() << QString("Reason: ") << e.cause(); failed(e.what()); return; } - hash = QString(ProviderCaps.hash(ModPlatform::Provider::MODRINTH, jar_data, best_hash_type).toHex()); + hash = ProviderCaps.hash(ModPlatform::Provider::MODRINTH, &file, best_hash_type); + file.close(); } hashes.append(hash); From f95bcf45ad0e537a124f5aa0c7b9e97a78811289 Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 23 Jul 2022 23:14:49 -0300 Subject: [PATCH 025/273] feat(libs): add incremental version of murmurhash2 calculation This does two passes for a given file, which is kinda slow, but I don't know how else to get the size excluding the filtered ones :< Signed-off-by: flow --- libraries/murmur2/src/MurmurHash2.cpp | 148 +++++++++++++++----------- libraries/murmur2/src/MurmurHash2.h | 40 +++---- 2 files changed, 106 insertions(+), 82 deletions(-) diff --git a/libraries/murmur2/src/MurmurHash2.cpp b/libraries/murmur2/src/MurmurHash2.cpp index 3e52e6d1..b625efb1 100644 --- a/libraries/murmur2/src/MurmurHash2.cpp +++ b/libraries/murmur2/src/MurmurHash2.cpp @@ -1,86 +1,110 @@ //----------------------------------------------------------------------------- // MurmurHash2 was written by Austin Appleby, and is placed in the public // domain. The author hereby disclaims copyright to this source code. - -// Note - This code makes a few assumptions about how your machine behaves - - -// 1. We can read a 4-byte value from any address without crashing -// 2. sizeof(int) == 4 - -// And it has a few limitations - - -// 1. It will not work incrementally. -// 2. It will not produce the same results on little-endian and big-endian -// machines. +// +// This was modified as to possibilitate it's usage incrementally. +// Those modifications are also placed in the public domain, and the author of +// such modifications hereby disclaims copyright to this source code. #include "MurmurHash2.h" //----------------------------------------------------------------------------- -// Platform-specific functions and macros -// Microsoft Visual Studio +// 'm' and 'r' are mixing constants generated offline. +// They're not really 'magic', they just happen to work well. +const uint32_t m = 0x5bd1e995; +const int r = 24; -#if defined(_MSC_VER) - -#define BIG_CONSTANT(x) (x) - -// Other compilers - -#else // defined(_MSC_VER) - -#define BIG_CONSTANT(x) (x##LLU) - -#endif // !defined(_MSC_VER) - -//----------------------------------------------------------------------------- - -uint64_t MurmurHash2 ( const void* key, int len, uint32_t seed ) +uint32_t MurmurHash2(std::ifstream&& file_stream, std::size_t buffer_size, std::function filter_out) { - // 'm' and 'r' are mixing constants generated offline. - // They're not really 'magic', they just happen to work well. + auto* buffer = new char[buffer_size]; + char data[4]; - const uint32_t m = 0x5bd1e995; - const int r = 24; + int read = 0; + uint32_t size = 0; - // Initialize the hash to a 'random' value + // We need the size without the filtered out characters before actually calculating the hash, + // to setup the initial value for the hash. + do { + file_stream.read(buffer, buffer_size); + read = file_stream.gcount(); + for (int i = 0; i < read; i++) { + if (!filter_out(buffer[i])) + size += 1; + } + } while (!file_stream.eof()); - uint32_t h = seed ^ len; + file_stream.clear(); + file_stream.seekg(0, file_stream.beg); - // Mix 4 bytes at a time into the hash - const auto* data = (const unsigned char*) key; - while(len >= 4) - { - uint32_t k = *(uint32_t*)data; + int index = 0; - k *= m; - k ^= k >> r; - k *= m; + // This forces a seed of 1. + IncrementalHashInfo info{ (uint32_t)1 ^ size, (uint32_t)size }; + do { + file_stream.read(buffer, buffer_size); + read = file_stream.gcount(); + for (int i = 0; i < read; i++) { + char c = buffer[i]; - h *= m; - h ^= k; + if (filter_out(c)) + continue; - data += 4*sizeof(char); - len -= 4; - } + data[index] = c; + index = (index + 1) % 4; - // Handle the last few bytes of the input array + // Mix 4 bytes at a time into the hash + if (index == 0) + FourBytes_MurmurHash2((unsigned char*)&data, info); + } + } while (!file_stream.eof()); - switch(len) - { - case 3: h ^= data[2] << 16; - case 2: h ^= data[1] << 8; - case 1: h ^= data[0]; - h *= m; - }; + // Do one last bit shuffle in the hash + FourBytes_MurmurHash2((unsigned char*)&data, info); - // Do a few final mixes of the hash to ensure the last few - // bytes are well-incorporated. + delete[] buffer; - h ^= h >> 13; - h *= m; - h ^= h >> 15; + file_stream.close(); + return info.h; +} - return h; -} +void FourBytes_MurmurHash2(const unsigned char* data, IncrementalHashInfo& prev) +{ + if (prev.len >= 4) { + // Not the final mix + uint32_t k = *(uint32_t*)data; + + k *= m; + k ^= k >> r; + k *= m; + + prev.h *= m; + prev.h ^= k; + + prev.len -= 4; + } else { + // The final mix + + // Handle the last few bytes of the input array + switch (prev.len) { + case 3: + prev.h ^= data[2] << 16; + case 2: + prev.h ^= data[1] << 8; + case 1: + prev.h ^= data[0]; + prev.h *= m; + }; + + // Do a few final mixes of the hash to ensure the last few + // bytes are well-incorporated. + + prev.h ^= prev.h >> 13; + prev.h *= m; + prev.h ^= prev.h >> 15; + + prev.len = 0; + } +} //----------------------------------------------------------------------------- diff --git a/libraries/murmur2/src/MurmurHash2.h b/libraries/murmur2/src/MurmurHash2.h index c7b83bca..acea27ea 100644 --- a/libraries/murmur2/src/MurmurHash2.h +++ b/libraries/murmur2/src/MurmurHash2.h @@ -1,30 +1,30 @@ //----------------------------------------------------------------------------- -// MurmurHash2 was written by Austin Appleby, and is placed in the public -// domain. The author hereby disclaims copyright to this source code. +// The original MurmurHash2 was written by Austin Appleby, and is placed in the +// public domain. The author hereby disclaims copyright to this source code. +// +// This was modified as to possibilitate it's usage incrementally. +// Those modifications are also placed in the public domain, and the author of +// such modifications hereby disclaims copyright to this source code. #pragma once -//----------------------------------------------------------------------------- -// Platform-specific functions and macros +#include +#include -// Microsoft Visual Studio - -#if defined(_MSC_VER) && (_MSC_VER < 1600) - -typedef unsigned char uint8_t; -typedef unsigned int uint32_t; -typedef unsigned __int64 uint64_t; - -// Other compilers - -#else // defined(_MSC_VER) - -#include - -#endif // !defined(_MSC_VER) +#include //----------------------------------------------------------------------------- -uint64_t MurmurHash2 ( const void* key, int len, uint32_t seed = 1 ); +uint32_t MurmurHash2( + std::ifstream&& file_stream, + std::size_t buffer_size = 4096, + std::function filter_out = [](char) { return true; }); + +struct IncrementalHashInfo { + uint32_t h; + uint32_t len; +}; + +void FourBytes_MurmurHash2(const unsigned char* data, IncrementalHashInfo& prev); //----------------------------------------------------------------------------- From b1763353ea0fd2d1924e3560f0a674cb6260721b Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 23 Jul 2022 23:16:28 -0300 Subject: [PATCH 026/273] feat: do incremental calculation of CF's hash Signed-off-by: flow --- launcher/modplatform/EnsureMetadataTask.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/launcher/modplatform/EnsureMetadataTask.cpp b/launcher/modplatform/EnsureMetadataTask.cpp index a5c9cbca..617cbe18 100644 --- a/launcher/modplatform/EnsureMetadataTask.cpp +++ b/launcher/modplatform/EnsureMetadataTask.cpp @@ -75,14 +75,13 @@ QString EnsureMetadataTask::getHash(Mod* mod) break; } case ModPlatform::Provider::FLAME: { - QByteArray jar_data_treated; - for (char c : jar_data) { + auto should_filter_out = [](char c) { // CF-specific - if (!(c == 9 || c == 10 || c == 13 || c == 32)) - jar_data_treated.push_back(c); - } + return (c == 9 || c == 10 || c == 13 || c == 32); + }; - return QString::number(MurmurHash2(jar_data_treated, jar_data_treated.length())); + std::ifstream file_stream(mod->fileinfo().absoluteFilePath().toStdString(), std::ifstream::binary); + result = QString::number(MurmurHash2(std::move(file_stream), 4096, should_filter_out)); } } From 631a93bcd83272b3e59ca6a06aaa18a1a6b03167 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 24 Jul 2022 15:11:41 -0300 Subject: [PATCH 027/273] refactor: add a HashUtils place for hashing stuff Signed-off-by: flow --- launcher/CMakeLists.txt | 2 + launcher/modplatform/helpers/HashUtils.cpp | 81 +++++++++++++++++++ launcher/modplatform/helpers/HashUtils.h | 47 +++++++++++ .../modrinth/ModrinthCheckUpdate.cpp | 41 +++++----- 4 files changed, 152 insertions(+), 19 deletions(-) create mode 100644 launcher/modplatform/helpers/HashUtils.cpp create mode 100644 launcher/modplatform/helpers/HashUtils.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index a4a1315d..3811ceaa 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -494,6 +494,8 @@ set(API_SOURCES modplatform/modrinth/ModrinthAPI.cpp modplatform/helpers/NetworkModAPI.h modplatform/helpers/NetworkModAPI.cpp + modplatform/helpers/HashUtils.h + modplatform/helpers/HashUtils.cpp ) set(FTB_SOURCES diff --git a/launcher/modplatform/helpers/HashUtils.cpp b/launcher/modplatform/helpers/HashUtils.cpp new file mode 100644 index 00000000..a7bbaba5 --- /dev/null +++ b/launcher/modplatform/helpers/HashUtils.cpp @@ -0,0 +1,81 @@ +#include "HashUtils.h" + +#include +#include + +#include "FileSystem.h" + +#include + +namespace Hashing { + +static ModPlatform::ProviderCapabilities ProviderCaps; + +Hasher::Ptr createHasher(QString file_path, ModPlatform::Provider provider) +{ + switch (provider) { + case ModPlatform::Provider::MODRINTH: + return createModrinthHasher(file_path); + case ModPlatform::Provider::FLAME: + return createFlameHasher(file_path); + default: + qCritical() << "[Hashing]" + << "Unrecognized mod platform!"; + return nullptr; + } +} + +Hasher::Ptr createModrinthHasher(QString file_path) +{ + return new ModrinthHasher(file_path); +} + +Hasher::Ptr createFlameHasher(QString file_path) +{ + return new FlameHasher(file_path); +} + +void ModrinthHasher::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; + } + + auto hash_type = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH).first(); + m_hash = ProviderCaps.hash(ModPlatform::Provider::MODRINTH, &file, hash_type); + + file.close(); + + if (m_hash.isEmpty()) { + emitFailed("Empty hash!"); + } else { + emitSucceeded(); + } +} + +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); + // 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)); + + if (m_hash.isEmpty()) { + emitFailed("Empty hash!"); + } else { + emitSucceeded(); + } +} + +} // namespace Hashing diff --git a/launcher/modplatform/helpers/HashUtils.h b/launcher/modplatform/helpers/HashUtils.h new file mode 100644 index 00000000..38fddf03 --- /dev/null +++ b/launcher/modplatform/helpers/HashUtils.h @@ -0,0 +1,47 @@ +#pragma once + +#include + +#include "modplatform/ModIndex.h" +#include "tasks/Task.h" + +namespace Hashing { + +class Hasher : public Task { + public: + using Ptr = shared_qobject_ptr; + + Hasher(QString file_path) : m_path(std::move(file_path)) {} + + /* We can't really abort this task, but we can say we aborted and finish our thing quickly :) */ + bool abort() override { return true; } + + void executeTask() override = 0; + + QString getResult() const { return m_hash; }; + QString getPath() const { return m_path; }; + + protected: + QString m_hash; + QString m_path; +}; + +class FlameHasher : public Hasher { + public: + FlameHasher(QString file_path) : Hasher(file_path) { setObjectName(QString("FlameHasher: %1").arg(file_path)); } + + void executeTask() override; +}; + +class ModrinthHasher : public Hasher { + public: + ModrinthHasher(QString file_path) : Hasher(file_path) { setObjectName(QString("ModrinthHasher: %1").arg(file_path)); } + + void executeTask() override; +}; + +Hasher::Ptr createHasher(QString file_path, ModPlatform::Provider provider); +Hasher::Ptr createFlameHasher(QString file_path); +Hasher::Ptr createModrinthHasher(QString file_path); + +} // namespace Hashing diff --git a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp index f4898591..e2d27547 100644 --- a/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp +++ b/launcher/modplatform/modrinth/ModrinthCheckUpdate.cpp @@ -2,11 +2,14 @@ #include "ModrinthAPI.h" #include "ModrinthPackIndex.h" -#include "FileSystem.h" #include "Json.h" #include "ModDownloadTask.h" +#include "modplatform/helpers/HashUtils.h" + +#include "tasks/ConcurrentTask.h" + static ModrinthAPI api; static ModPlatform::ProviderCapabilities ProviderCaps; @@ -32,6 +35,8 @@ void ModrinthCheckUpdate::executeTask() // Create all hashes QStringList hashes; auto best_hash_type = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH).first(); + + ConcurrentTask hashing_task(this, "MakeModrinthHashesTask", 10); for (auto* mod : m_mods) { if (!mod->enabled()) { emit checkFailed(mod, tr("Disabled mods won't be updated, to prevent mod duplication issues!")); @@ -44,27 +49,25 @@ void ModrinthCheckUpdate::executeTask() // need to generate a new hash if the current one is innadequate // (though it will rarely happen, if at all) if (mod->metadata()->hash_format != best_hash_type) { - QByteArray jar_data; - - QFile file(mod->fileinfo().absoluteFilePath()); - try { - file.open(QFile::ReadOnly); - } catch (FS::FileSystemException& e) { - qCritical() << QString("Failed to open JAR file of %1").arg(mod->name()); - qCritical() << QString("Reason: ") << e.cause(); - - failed(e.what()); - return; - } - - hash = ProviderCaps.hash(ModPlatform::Provider::MODRINTH, &file, best_hash_type); - file.close(); + auto hash_task = Hashing::createModrinthHasher(mod->fileinfo().absoluteFilePath()); + connect(hash_task.get(), &Task::succeeded, [&] { + QString hash (hash_task->getResult()); + hashes.append(hash); + mappings.insert(hash, mod); + }); + connect(hash_task.get(), &Task::failed, [this, hash_task] { failed("Failed to generate hash"); }); + hashing_task.addTask(hash_task); + } else { + hashes.append(hash); + mappings.insert(hash, mod); } - - hashes.append(hash); - mappings.insert(hash, mod); } + QEventLoop loop; + connect(&hashing_task, &Task::finished, [&loop]{ loop.quit(); }); + hashing_task.start(); + loop.exec(); + auto* response = new QByteArray(); auto job = api.latestVersions(hashes, best_hash_type, m_game_versions, m_loaders, response); From 24c034ff6a15ed8fb8321f410e2003d83deead46 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 24 Jul 2022 15:12:29 -0300 Subject: [PATCH 028/273] change(libs): use a 4MiB buffer by default in murmur2 hashing Signed-off-by: flow --- libraries/murmur2/src/MurmurHash2.h | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/libraries/murmur2/src/MurmurHash2.h b/libraries/murmur2/src/MurmurHash2.h index acea27ea..dc2c9681 100644 --- a/libraries/murmur2/src/MurmurHash2.h +++ b/libraries/murmur2/src/MurmurHash2.h @@ -15,10 +15,13 @@ //----------------------------------------------------------------------------- +#define KiB 1024 +#define MiB 1024*KiB + uint32_t MurmurHash2( std::ifstream&& file_stream, - std::size_t buffer_size = 4096, - std::function filter_out = [](char) { return true; }); + std::size_t buffer_size = 4*MiB, + std::function filter_out = [](char) { return false; }); struct IncrementalHashInfo { uint32_t h; From e6f2a3893a6c9b3c0a2b94ffde41a7dbee1accb0 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 24 Jul 2022 15:13:42 -0300 Subject: [PATCH 029/273] refactor+feat: improve code separation in ensure metadata ... and avoid calculating the same hash multiple times Signed-off-by: flow --- launcher/modplatform/EnsureMetadataTask.cpp | 99 +++++++++------------ launcher/modplatform/EnsureMetadataTask.h | 21 +++-- 2 files changed, 58 insertions(+), 62 deletions(-) diff --git a/launcher/modplatform/EnsureMetadataTask.cpp b/launcher/modplatform/EnsureMetadataTask.cpp index 617cbe18..1f49d7c9 100644 --- a/launcher/modplatform/EnsureMetadataTask.cpp +++ b/launcher/modplatform/EnsureMetadataTask.cpp @@ -3,89 +3,72 @@ #include #include -#include "FileSystem.h" #include "Json.h" + #include "minecraft/mod/Mod.h" #include "minecraft/mod/tasks/LocalModUpdateTask.h" + #include "modplatform/flame/FlameAPI.h" #include "modplatform/flame/FlameModIndex.h" #include "modplatform/modrinth/ModrinthAPI.h" #include "modplatform/modrinth/ModrinthPackIndex.h" + #include "net/NetJob.h" -#include "tasks/MultipleOptionsTask.h" static ModPlatform::ProviderCapabilities ProviderCaps; static ModrinthAPI modrinth_api; static FlameAPI flame_api; -EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::Provider prov) : Task(nullptr), m_index_dir(dir), m_provider(prov) +EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::Provider prov) + : Task(nullptr), m_index_dir(dir), m_provider(prov), m_hashing_task(nullptr), m_current_task(nullptr) { - auto hash = getHash(mod); - if (hash.isEmpty()) - emitFail(mod); - else - m_mods.insert(hash, mod); + auto hash_task = createNewHash(mod); + connect(hash_task.get(), &Task::succeeded, [this, hash_task, mod] { m_mods.insert(hash_task->getResult(), mod); }); + connect(hash_task.get(), &Task::failed, [this, hash_task, mod] { emitFail(mod, RemoveFromList::No); }); + hash_task->start(); } EnsureMetadataTask::EnsureMetadataTask(QList& mods, QDir dir, ModPlatform::Provider prov) - : Task(nullptr), m_index_dir(dir), m_provider(prov) + : Task(nullptr), m_index_dir(dir), m_provider(prov), m_current_task(nullptr) { + m_hashing_task = new ConcurrentTask(this, "MakeHashesTask", 10); for (auto* mod : mods) { - if (!mod->valid()) { - emitFail(mod); + auto hash_task = createNewHash(mod); + if (!hash_task) continue; - } - - auto hash = getHash(mod); - if (hash.isEmpty()) { - emitFail(mod); - continue; - } - - m_mods.insert(hash, mod); + connect(hash_task.get(), &Task::succeeded, [this, hash_task, mod] { m_mods.insert(hash_task->getResult(), mod); }); + connect(hash_task.get(), &Task::failed, [this, hash_task, mod] { emitFail(mod, RemoveFromList::No); }); + m_hashing_task->addTask(hash_task); } } -QString EnsureMetadataTask::getHash(Mod* mod) +Hashing::Hasher::Ptr EnsureMetadataTask::createNewHash(Mod* mod) { - /* Here we create a mapping hash -> mod, because we need that relationship to parse the API routes */ - if (mod->type() == Mod::MOD_FOLDER) - return {}; + if (!mod->valid() || mod->type() == Mod::MOD_FOLDER) + return nullptr; - QString result; - switch (m_provider) { - case ModPlatform::Provider::MODRINTH: { - QFile file(mod->fileinfo().absoluteFilePath()); - - try { - file.open(QFile::ReadOnly); - } catch (FS::FileSystemException& e) { - qCritical() << QString("Failed to open JAR file of %1").arg(mod->name()); - qCritical() << QString("Reason: ") << e.cause(); - - return {}; - } - - auto hash_type = ProviderCaps.hashType(ModPlatform::Provider::MODRINTH).first(); - result = ProviderCaps.hash(ModPlatform::Provider::MODRINTH, &file, hash_type); - - file.close(); + return Hashing::createHasher(mod->fileinfo().absoluteFilePath(), m_provider); +} +QString EnsureMetadataTask::getExistingHash(Mod* mod) +{ + // Check for already computed hashes + // (linear on the number of mods vs. linear on the size of the mod's JAR) + auto it = m_mods.keyValueBegin(); + while (it != m_mods.keyValueEnd()) { + if ((*it).second == mod) break; - } - case ModPlatform::Provider::FLAME: { - auto should_filter_out = [](char c) { - // CF-specific - return (c == 9 || c == 10 || c == 13 || c == 32); - }; - - std::ifstream file_stream(mod->fileinfo().absoluteFilePath().toStdString(), std::ifstream::binary); - result = QString::number(MurmurHash2(std::move(file_stream), 4096, should_filter_out)); - } + it++; } - return result; + // We already have the hash computed + if (it != m_mods.keyValueEnd()) { + return (*it).first; + } + + // No existing hash + return {}; } bool EnsureMetadataTask::abort() @@ -185,20 +168,22 @@ void EnsureMetadataTask::executeTask() version_task->start(); } -void EnsureMetadataTask::emitReady(Mod* m) +void EnsureMetadataTask::emitReady(Mod* m, RemoveFromList remove) { qDebug() << QString("Generated metadata for %1").arg(m->name()); emit metadataReady(m); - m_mods.remove(getHash(m)); + if (remove == RemoveFromList::Yes) + m_mods.remove(getExistingHash(m)); } -void EnsureMetadataTask::emitFail(Mod* m) +void EnsureMetadataTask::emitFail(Mod* m, RemoveFromList remove) { qDebug() << QString("Failed to generate metadata for %1").arg(m->name()); emit metadataFailed(m); - m_mods.remove(getHash(m)); + if (remove == RemoveFromList::Yes) + m_mods.remove(getExistingHash(m)); } // Modrinth diff --git a/launcher/modplatform/EnsureMetadataTask.h b/launcher/modplatform/EnsureMetadataTask.h index 79db6976..13319266 100644 --- a/launcher/modplatform/EnsureMetadataTask.h +++ b/launcher/modplatform/EnsureMetadataTask.h @@ -1,12 +1,14 @@ #pragma once #include "ModIndex.h" -#include "tasks/SequentialTask.h" #include "net/NetJob.h" +#include "modplatform/helpers/HashUtils.h" + +#include "tasks/ConcurrentTask.h" + class Mod; class QDir; -class MultipleOptionsTask; class EnsureMetadataTask : public Task { Q_OBJECT @@ -17,6 +19,8 @@ class EnsureMetadataTask : public Task { ~EnsureMetadataTask() = default; + Task::Ptr getHashingTask() { return m_hashing_task; } + public slots: bool abort() override; protected slots: @@ -31,10 +35,16 @@ class EnsureMetadataTask : public Task { auto flameProjectsTask() -> NetJob::Ptr; // Helpers - void emitReady(Mod*); - void emitFail(Mod*); + enum class RemoveFromList { + Yes, + No + }; + void emitReady(Mod*, RemoveFromList = RemoveFromList::Yes); + void emitFail(Mod*, RemoveFromList = RemoveFromList::Yes); - auto getHash(Mod*) -> QString; + // Hashes and stuff + auto createNewHash(Mod*) -> Hashing::Hasher::Ptr; + auto getExistingHash(Mod*) -> QString; private slots: void modrinthCallback(ModPlatform::IndexedPack& pack, ModPlatform::IndexedVersion& ver, Mod*); @@ -50,5 +60,6 @@ class EnsureMetadataTask : public Task { ModPlatform::Provider m_provider; QHash m_temp_versions; + ConcurrentTask* m_hashing_task; NetJob* m_current_task; }; From 00520b6a0e0ca3987cd4f7eef6e712c61fbb89f8 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 24 Jul 2022 15:14:31 -0300 Subject: [PATCH 030/273] feat: add hashing tasks to the sequential task in ModUpdateDialog Signed-off-by: flow --- launcher/ui/dialogs/ModUpdateDialog.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/launcher/ui/dialogs/ModUpdateDialog.cpp b/launcher/ui/dialogs/ModUpdateDialog.cpp index d73c8ebb..936fd99d 100644 --- a/launcher/ui/dialogs/ModUpdateDialog.cpp +++ b/launcher/ui/dialogs/ModUpdateDialog.cpp @@ -270,6 +270,10 @@ auto ModUpdateDialog::ensureMetadata() -> bool connect(modrinth_task, &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) { onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::Provider::MODRINTH); }); + + if (modrinth_task->getHashingTask()) + seq.addTask(modrinth_task->getHashingTask()); + seq.addTask(modrinth_task); } @@ -279,6 +283,10 @@ auto ModUpdateDialog::ensureMetadata() -> bool connect(flame_task, &EnsureMetadataTask::metadataFailed, [this, &should_try_others](Mod* candidate) { onMetadataFailed(candidate, should_try_others.find(candidate->internal_id()).value(), ModPlatform::Provider::FLAME); }); + + if (flame_task->getHashingTask()) + seq.addTask(flame_task->getHashingTask()); + seq.addTask(flame_task); } From a9e8ed5087fb559352d3ef14b575718181ffaa32 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 24 Jul 2022 16:19:25 -0300 Subject: [PATCH 031/273] fix: pump events and do a queued start for concurrent tasks Heavy workloads can consume a ton of time doing their stuff, and starve the event loop out of events. This adds an event processing call after every concurrent task has been completed, to decrease the event loop stravation on such loads. Signed-off-by: flow --- launcher/tasks/ConcurrentTask.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp index b88cfb13..ab7cbd03 100644 --- a/launcher/tasks/ConcurrentTask.cpp +++ b/launcher/tasks/ConcurrentTask.cpp @@ -1,10 +1,11 @@ #include "ConcurrentTask.h" #include +#include ConcurrentTask::ConcurrentTask(QObject* parent, QString task_name, int max_concurrent) : Task(parent), m_name(task_name), m_total_max_size(max_concurrent) -{} +{ setObjectName(task_name); } ConcurrentTask::~ConcurrentTask() { @@ -36,8 +37,9 @@ void ConcurrentTask::executeTask() { m_total_size = m_queue.size(); - for (int i = 0; i < m_total_max_size; i++) - startNext(); + for (int i = 0; i < m_total_max_size; i++) { + QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection); + } } bool ConcurrentTask::abort() @@ -91,6 +93,8 @@ void ConcurrentTask::startNext() setStepStatus(next->isMultiStep() ? next->getStepStatus() : next->getStatus()); updateState(); + QCoreApplication::processEvents(); + next->start(); } From a495d9eca51ff23b771c60c36ebbc554f8d06836 Mon Sep 17 00:00:00 2001 From: txtsd Date: Tue, 26 Jul 2022 22:00:40 +0530 Subject: [PATCH 032/273] chore: Normalize sentence in readme Signed-off-by: txtsd --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 27a32a56..69639e5b 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ The translation effort for PolyMC is hosted on [Weblate](https://hosted.weblate. ## Download information -To modify download information or change packaging information send a pull request or issue to the website [Here](https://github.com/PolyMC/polymc.github.io/tree/master/src/download) +To modify download information or change packaging information send a pull request or issue to the website [here](https://github.com/PolyMC/polymc.github.io/tree/master/src/download). ## Forking/Redistributing/Custom builds policy From 1a6cb9ee99a35c0edd2b2bd1041daab895b90182 Mon Sep 17 00:00:00 2001 From: flow Date: Mon, 25 Jul 2022 13:59:56 -0300 Subject: [PATCH 033/273] chore: add some debugging prints in AccountList Signed-off-by: flow --- launcher/minecraft/auth/AccountList.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/launcher/minecraft/auth/AccountList.cpp b/launcher/minecraft/auth/AccountList.cpp index 2b851e18..b3b57c74 100644 --- a/launcher/minecraft/auth/AccountList.cpp +++ b/launcher/minecraft/auth/AccountList.cpp @@ -109,8 +109,10 @@ QStringList AccountList::profileNames() const { void AccountList::addAccount(const MinecraftAccountPtr account) { - // NOTE: Do not allow adding something that's already there - if(m_accounts.contains(account)) { + // NOTE: Do not allow adding something that's already there. We shouldn't let it continue + // because of the signal / slot connections after this. + if (m_accounts.contains(account)) { + qDebug() << "Tried to add account that's already on the accounts list!"; return; } @@ -123,6 +125,8 @@ void AccountList::addAccount(const MinecraftAccountPtr account) if(profileId.size()) { auto existingAccount = findAccountByProfileId(profileId); if(existingAccount != -1) { + qDebug() << "Replacing old account with a new one with the same profile ID!"; + MinecraftAccountPtr existingAccountPtr = m_accounts[existingAccount]; m_accounts[existingAccount] = account; if(m_defaultAccount == existingAccountPtr) { @@ -138,9 +142,12 @@ void AccountList::addAccount(const MinecraftAccountPtr account) // if we don't have this profileId yet, add the account to the end int row = m_accounts.count(); + qDebug() << "Inserting account at index" << row; + beginInsertRows(QModelIndex(), row, row); m_accounts.append(account); endInsertRows(); + onListChanged(); } From 6fe55a79f18ac03c919c1ecbadde19fb2c82681b Mon Sep 17 00:00:00 2001 From: flow Date: Tue, 26 Jul 2022 12:53:28 -0300 Subject: [PATCH 034/273] fix: use const qualifier for operator==() and allow other comparisons This fixes an implicit behavior changed by C++17. Signed-off-by: flow --- launcher/QObjectPtr.h | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/launcher/QObjectPtr.h b/launcher/QObjectPtr.h index 57974939..173dc5e7 100644 --- a/launcher/QObjectPtr.h +++ b/launcher/QObjectPtr.h @@ -77,10 +77,12 @@ public: { return m_ptr; } - bool operator==(const shared_qobject_ptr& other) { + template + bool operator==(const shared_qobject_ptr& other) const { return m_ptr == other.m_ptr; } - bool operator!=(const shared_qobject_ptr& other) { + template + bool operator!=(const shared_qobject_ptr& other) const { return m_ptr != other.m_ptr; } From 75a7ea55d433a38c2e14f8be67a7e3fc27bba092 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Thu, 2 Jun 2022 21:23:20 +0200 Subject: [PATCH 035/273] feat: implement mod resolving for FTB Signed-off-by: Sefa Eyeoglu --- .../modpacksch/FTBPackInstallTask.cpp | 94 ++++++++++++++++++- .../modpacksch/FTBPackInstallTask.h | 12 ++- .../modpacksch/FTBPackManifest.cpp | 5 +- .../modplatform/modpacksch/FTBPackManifest.h | 7 ++ launcher/ui/pages/modplatform/ftb/FtbPage.cpp | 2 +- 5 files changed, 113 insertions(+), 7 deletions(-) diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp index cac432cd..c888ef22 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp @@ -40,25 +40,32 @@ #include "Json.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" +#include "modplatform/flame/PackManifest.h" #include "net/ChecksumValidator.h" +#include "net/Upload.h" #include "settings/INISettingsObject.h" #include "BuildConfig.h" #include "Application.h" +#include "ui/dialogs/ScrollMessageBox.h" namespace ModpacksCH { -PackInstallTask::PackInstallTask(Modpack pack, QString version) +PackInstallTask::PackInstallTask(Modpack pack, QString version, QWidget* parent) { m_pack = pack; m_version_name = version; + m_parent = parent; } bool PackInstallTask::abort() { if(abortable) { - return jobPtr->abort(); + if (modIdResolver) + return modIdResolver->abort(); + else if (jobPtr) + return jobPtr->abort(); } return false; } @@ -118,7 +125,7 @@ void PackInstallTask::onDownloadSucceeded() } m_version = version; - downloadPack(); + resolveMods(); } void PackInstallTask::onDownloadFailed(QString reason) @@ -127,13 +134,92 @@ void PackInstallTask::onDownloadFailed(QString reason) emitFailed(reason); } +void PackInstallTask::resolveMods() +{ + setStatus(tr("Resolving mods...")); + + Flame::Manifest manifest; + indexFileIdMap.clear(); + int index = 0; + for(auto file : m_version.files) { + if(!file.serverOnly && file.url.isEmpty()) { + if(file.curseforge.file <= 0) + emitFailed("Invalid manifest"); // TODO better error + + Flame::File f; + f.projectId = file.curseforge.project; + f.fileId = file.curseforge.file; + f.hash = file.sha1; + + + manifest.files.insert(f.fileId, f); + indexFileIdMap.insert(index, f.fileId); + } + index++; + } + + modIdResolver = new Flame::FileResolvingTask(APPLICATION->network(), manifest); + + connect(modIdResolver.get(), &Flame::FileResolvingTask::succeeded, this, [&]() + { + abortable = false; + + //first check for blocked mods + QString text; + auto anyBlocked = false; + + Flame::Manifest results = modIdResolver->getResults(); + for(int index : indexFileIdMap.keys()) + { + int fileId = indexFileIdMap[index]; + Flame::File foo = results.files[fileId]; + VersionFile &bar = m_version.files[index]; + if (!foo.resolved || foo.url.isEmpty()) + { + QString type = bar.type; + type[0] = type[0].toUpper(); + text += QString("%1: %2 - %3
").arg(type, bar.name, foo.websiteUrl); + anyBlocked = true; + } else { + bar.url = foo.url.toString(); + } + } + if(anyBlocked) { + qWarning() << "Blocked files found, displaying file list"; + + auto message_dialog = new ScrollMessageBox(m_parent, + tr("Blocked files found"), + tr("The following files are not available for download in third party launchers.
" + "You will need to manually download them and add them to the instance."), + text); + message_dialog->setModal(true); + message_dialog->show(); + connect(message_dialog, &QDialog::rejected, [&]() { + modIdResolver.reset(); + emitFailed("Canceled"); + }); + connect(message_dialog, &QDialog::accepted, [&]() { + modIdResolver.reset(); + downloadPack(); + }); + } else { + modIdResolver.reset(); + downloadPack(); + } + }); + connect(modIdResolver.get(), &Flame::FileResolvingTask::failed, this, &PackInstallTask::onDownloadFailed); + + modIdResolver->start(); + abortable = true; +} + void PackInstallTask::downloadPack() { setStatus(tr("Downloading mods...")); jobPtr = new NetJob(tr("Mod download"), APPLICATION->network()); for(auto file : m_version.files) { - if(file.serverOnly) continue; + if(file.serverOnly || file.url.isEmpty()) continue; QFileInfo fileName(file.name); auto cacheName = fileName.completeBaseName() + "-" + file.sha1 + "." + fileName.suffix(); diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.h b/launcher/modplatform/modpacksch/FTBPackInstallTask.h index ff59b695..a85f360a 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.h +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.h @@ -19,9 +19,13 @@ #include "FTBPackManifest.h" +#include "QObjectPtr.h" +#include "modplatform/flame/FileResolvingTask.h" #include "InstanceTask.h" #include "net/NetJob.h" +#include + namespace ModpacksCH { class PackInstallTask : public InstanceTask @@ -29,7 +33,7 @@ class PackInstallTask : public InstanceTask Q_OBJECT public: - explicit PackInstallTask(Modpack pack, QString version); + explicit PackInstallTask(Modpack pack, QString version, QWidget* parent = nullptr); virtual ~PackInstallTask(){} bool canAbort() const override { return true; } @@ -43,6 +47,7 @@ private slots: void onDownloadFailed(QString reason); private: + void resolveMods(); void downloadPack(); void install(); @@ -50,6 +55,9 @@ private: bool abortable = false; NetJob::Ptr jobPtr; + shared_qobject_ptr modIdResolver; + QMap indexFileIdMap; + QByteArray response; Modpack m_pack; @@ -58,6 +66,8 @@ private: QMap filesToCopy; + //FIXME: nuke + QWidget* m_parent; }; } diff --git a/launcher/modplatform/modpacksch/FTBPackManifest.cpp b/launcher/modplatform/modpacksch/FTBPackManifest.cpp index e2d47a5b..a8c0f6b8 100644 --- a/launcher/modplatform/modpacksch/FTBPackManifest.cpp +++ b/launcher/modplatform/modpacksch/FTBPackManifest.cpp @@ -127,13 +127,16 @@ static void loadVersionFile(ModpacksCH::VersionFile & a, QJsonObject & obj) a.path = Json::requireString(obj, "path"); a.name = Json::requireString(obj, "name"); a.version = Json::requireString(obj, "version"); - a.url = Json::requireString(obj, "url"); + a.url = Json::ensureString(obj, "url"); // optional a.sha1 = Json::requireString(obj, "sha1"); a.size = Json::requireInteger(obj, "size"); a.clientOnly = Json::requireBoolean(obj, "clientonly"); a.serverOnly = Json::requireBoolean(obj, "serveronly"); a.optional = Json::requireBoolean(obj, "optional"); a.updated = Json::requireInteger(obj, "updated"); + auto curseforgeObj = Json::ensureObject(obj, "curseforge"); // optional + a.curseforge.project = Json::ensureInteger(curseforgeObj, "project"); + a.curseforge.file = Json::ensureInteger(curseforgeObj, "file"); } void ModpacksCH::loadVersion(ModpacksCH::Version & m, QJsonObject & obj) diff --git a/launcher/modplatform/modpacksch/FTBPackManifest.h b/launcher/modplatform/modpacksch/FTBPackManifest.h index da45d8ac..0451127a 100644 --- a/launcher/modplatform/modpacksch/FTBPackManifest.h +++ b/launcher/modplatform/modpacksch/FTBPackManifest.h @@ -97,6 +97,12 @@ struct VersionTarget int64_t updated; }; +struct VersionFileCurseForge +{ + int project; + int file; +}; + struct VersionFile { int id; @@ -111,6 +117,7 @@ struct VersionFile bool serverOnly; bool optional; int64_t updated; + VersionFileCurseForge curseforge; }; struct Version diff --git a/launcher/ui/pages/modplatform/ftb/FtbPage.cpp b/launcher/ui/pages/modplatform/ftb/FtbPage.cpp index 8a93bc2e..693ee884 100644 --- a/launcher/ui/pages/modplatform/ftb/FtbPage.cpp +++ b/launcher/ui/pages/modplatform/ftb/FtbPage.cpp @@ -126,7 +126,7 @@ void FtbPage::suggestCurrent() return; } - dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ModpacksCH::PackInstallTask(selected, selectedVersion)); + dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ModpacksCH::PackInstallTask(selected, selectedVersion, this)); for(auto art : selected.art) { if(art.type == "square") { QString editedLogoName; From fb289c6b1770f43aaeca79df435d72332ad237c3 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Fri, 3 Jun 2022 09:45:09 +0200 Subject: [PATCH 036/273] chore: add license headers Signed-off-by: Sefa Eyeoglu --- .../modpacksch/FTBPackInstallTask.cpp | 1 + .../modpacksch/FTBPackInstallTask.h | 41 ++++++++++++++----- .../modpacksch/FTBPackManifest.cpp | 41 ++++++++++++++----- .../modplatform/modpacksch/FTBPackManifest.h | 41 ++++++++++++++----- launcher/ui/pages/modplatform/ftb/FtbPage.cpp | 1 + 5 files changed, 92 insertions(+), 33 deletions(-) diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp index c888ef22..b335383c 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield + * Copyright (C) 2022 Sefa Eyeoglu * * 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 diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.h b/launcher/modplatform/modpacksch/FTBPackInstallTask.h index a85f360a..087794cd 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.h +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.h @@ -1,18 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-only /* - * Copyright 2020-2021 Jamie Mansfield - * Copyright 2020-2021 Petr Mrazek + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. * - * http://www.apache.org/licenses/LICENSE-2.0 + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2020-2021 Jamie Mansfield + * Copyright 2020-2021 Petr Mrazek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #pragma once diff --git a/launcher/modplatform/modpacksch/FTBPackManifest.cpp b/launcher/modplatform/modpacksch/FTBPackManifest.cpp index a8c0f6b8..c927a623 100644 --- a/launcher/modplatform/modpacksch/FTBPackManifest.cpp +++ b/launcher/modplatform/modpacksch/FTBPackManifest.cpp @@ -1,18 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-only /* - * Copyright 2020 Jamie Mansfield - * Copyright 2020-2021 Petr Mrazek + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. * - * http://www.apache.org/licenses/LICENSE-2.0 + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2020 Jamie Mansfield + * Copyright 2020-2021 Petr Mrazek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #include "FTBPackManifest.h" diff --git a/launcher/modplatform/modpacksch/FTBPackManifest.h b/launcher/modplatform/modpacksch/FTBPackManifest.h index 0451127a..dac4e3d0 100644 --- a/launcher/modplatform/modpacksch/FTBPackManifest.h +++ b/launcher/modplatform/modpacksch/FTBPackManifest.h @@ -1,18 +1,37 @@ +// SPDX-License-Identifier: GPL-3.0-only /* - * Copyright 2020-2021 Jamie Mansfield - * Copyright 2020 Petr Mrazek + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. * - * http://www.apache.org/licenses/LICENSE-2.0 + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2020-2021 Jamie Mansfield + * Copyright 2020 Petr Mrazek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #pragma once diff --git a/launcher/ui/pages/modplatform/ftb/FtbPage.cpp b/launcher/ui/pages/modplatform/ftb/FtbPage.cpp index 693ee884..504d7f7b 100644 --- a/launcher/ui/pages/modplatform/ftb/FtbPage.cpp +++ b/launcher/ui/pages/modplatform/ftb/FtbPage.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield + * Copyright (C) 2022 Sefa Eyeoglu * * 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 From e741cb7f0aa651b4f7245aed988ff510c7f8e2f9 Mon Sep 17 00:00:00 2001 From: flow Date: Mon, 25 Jul 2022 16:37:10 -0300 Subject: [PATCH 037/273] fix: add abort handlign in Flame's FileResolvingTask Signed-off-by: flow --- launcher/modplatform/flame/FileResolvingTask.cpp | 7 +++++++ launcher/modplatform/flame/FileResolvingTask.h | 3 +++ 2 files changed, 10 insertions(+) diff --git a/launcher/modplatform/flame/FileResolvingTask.cpp b/launcher/modplatform/flame/FileResolvingTask.cpp index c1f56658..058d2471 100644 --- a/launcher/modplatform/flame/FileResolvingTask.cpp +++ b/launcher/modplatform/flame/FileResolvingTask.cpp @@ -7,6 +7,13 @@ Flame::FileResolvingTask::FileResolvingTask(const shared_qobject_ptrabort(); + return true; +} + void Flame::FileResolvingTask::executeTask() { setStatus(tr("Resolving mod IDs...")); diff --git a/launcher/modplatform/flame/FileResolvingTask.h b/launcher/modplatform/flame/FileResolvingTask.h index 87981f0a..f71b87ce 100644 --- a/launcher/modplatform/flame/FileResolvingTask.h +++ b/launcher/modplatform/flame/FileResolvingTask.h @@ -13,6 +13,9 @@ public: explicit FileResolvingTask(const shared_qobject_ptr& network, Flame::Manifest &toProcess); virtual ~FileResolvingTask() {}; + bool canAbort() const override { return true; } + bool abort() override; + const Flame::Manifest &getResults() const { return m_toProcess; From 13372f3f993efcf72c93af5f3169cc01d445f46e Mon Sep 17 00:00:00 2001 From: flow Date: Mon, 25 Jul 2022 16:38:58 -0300 Subject: [PATCH 038/273] chore: clean up FTBPackInstallTask a bit and connect missing signals Signed-off-by: flow --- .../modpacksch/FTBPackInstallTask.cpp | 143 ++++++++++-------- .../modpacksch/FTBPackInstallTask.h | 12 +- 2 files changed, 84 insertions(+), 71 deletions(-) diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp index b335383c..bad0f149 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp @@ -43,7 +43,6 @@ #include "minecraft/PackProfile.h" #include "modplatform/flame/PackManifest.h" #include "net/ChecksumValidator.h" -#include "net/Upload.h" #include "settings/INISettingsObject.h" #include "BuildConfig.h" @@ -53,26 +52,30 @@ namespace ModpacksCH { PackInstallTask::PackInstallTask(Modpack pack, QString version, QWidget* parent) + : m_pack(std::move(pack)), m_version_name(std::move(version)), m_parent(parent) { - m_pack = pack; - m_version_name = version; - m_parent = parent; } bool PackInstallTask::abort() { - if(abortable) - { - if (modIdResolver) - return modIdResolver->abort(); - else if (jobPtr) - return jobPtr->abort(); - } - return false; + bool aborted = true; + + if (jobPtr) + aborted &= jobPtr->abort(); + if (modIdResolver) + aborted &= modIdResolver->abort(); + + // FIXME: This should be 'emitAborted()', but InstanceStaging doesn't connect to the abort signal yet... + if (aborted) + emitFailed(tr("Aborted")); + + return aborted; } void PackInstallTask::executeTask() { + setStatus(tr("Getting the manifest...")); + // Find pack version bool found = false; VersionInfo version; @@ -91,13 +94,17 @@ void PackInstallTask::executeTask() } auto *netJob = new NetJob("ModpacksCH::VersionFetch", APPLICATION->network()); + auto searchUrl = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/%1/%2").arg(m_pack.id).arg(version.id); netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); + jobPtr = netJob; - jobPtr->start(); QObject::connect(netJob, &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded); QObject::connect(netJob, &NetJob::failed, this, &PackInstallTask::onDownloadFailed); + QObject::connect(netJob, &NetJob::progress, this, &PackInstallTask::setProgress); + + jobPtr->start(); } void PackInstallTask::onDownloadSucceeded() @@ -115,15 +122,13 @@ void PackInstallTask::onDownloadSucceeded() auto obj = doc.object(); ModpacksCH::Version version; - try - { + try { ModpacksCH::loadVersion(version, obj); - } - catch (const JSONValidationError &e) - { + } catch (const JSONValidationError &e) { emitFailed(tr("Could not understand pack manifest:\n") + e.cause()); return; } + m_version = version; resolveMods(); @@ -132,29 +137,35 @@ void PackInstallTask::onDownloadSucceeded() void PackInstallTask::onDownloadFailed(QString reason) { jobPtr.reset(); + emitFailed(reason); } void PackInstallTask::resolveMods() { setStatus(tr("Resolving mods...")); + setProgress(0, 100); + + indexFileIdMap.clear(); Flame::Manifest manifest; - indexFileIdMap.clear(); int index = 0; - for(auto file : m_version.files) { + + for(auto const& file : m_version.files) { if(!file.serverOnly && file.url.isEmpty()) { - if(file.curseforge.file <= 0) - emitFailed("Invalid manifest"); // TODO better error + if(file.curseforge.file <= 0) { + emitFailed(QString("Invalid manifest: There's no information available to download the file '%1'!").arg(file.name)); + return; + } - Flame::File f; - f.projectId = file.curseforge.project; - f.fileId = file.curseforge.file; - f.hash = file.sha1; + Flame::File flame_file; + flame_file.projectId = file.curseforge.project; + flame_file.fileId = file.curseforge.file; + flame_file.hash = file.sha1; - manifest.files.insert(f.fileId, f); - indexFileIdMap.insert(index, f.fileId); + manifest.files.insert(flame_file.fileId, flame_file); + indexFileIdMap.insert(index, flame_file.fileId); } index++; } @@ -163,55 +174,56 @@ void PackInstallTask::resolveMods() connect(modIdResolver.get(), &Flame::FileResolvingTask::succeeded, this, [&]() { - abortable = false; + m_abortable = false; - //first check for blocked mods QString text; auto anyBlocked = false; Flame::Manifest results = modIdResolver->getResults(); - for(int index : indexFileIdMap.keys()) - { - int fileId = indexFileIdMap[index]; - Flame::File foo = results.files[fileId]; - VersionFile &bar = m_version.files[index]; - if (!foo.resolved || foo.url.isEmpty()) - { - QString type = bar.type; + for (auto index : indexFileIdMap.keys()) { + int fileId = indexFileIdMap.constFind(index).value(); + + Flame::File results_file = results.files[fileId]; + VersionFile& local_file = m_version.files[index]; + + // 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 - %3
").arg(type, bar.name, foo.websiteUrl); + text += QString("%1: %2 - %3
").arg(type, local_file.name, results_file.websiteUrl); anyBlocked = true; } else { - bar.url = foo.url.toString(); + local_file.url = results_file.url.toString(); } } - if(anyBlocked) { - qWarning() << "Blocked files found, displaying file list"; + + if (anyBlocked) { + qDebug() << "Blocked files found, displaying file list"; auto message_dialog = new ScrollMessageBox(m_parent, tr("Blocked files found"), tr("The following files are not available for download in third party launchers.
" "You will need to manually download them and add them to the instance."), text); - message_dialog->setModal(true); - message_dialog->show(); - connect(message_dialog, &QDialog::rejected, [&]() { - modIdResolver.reset(); - emitFailed("Canceled"); - }); - connect(message_dialog, &QDialog::accepted, [&]() { + if (message_dialog->exec() == QDialog::Accepted) { modIdResolver.reset(); downloadPack(); - }); + } else { + modIdResolver.reset(); + abort(); + return; + } } else { modIdResolver.reset(); downloadPack(); } }); + connect(modIdResolver.get(), &Flame::FileResolvingTask::failed, this, &PackInstallTask::onDownloadFailed); + connect(modIdResolver.get(), &Flame::FileResolvingTask::progress, this, &PackInstallTask::setProgress); modIdResolver->start(); - abortable = true; } void PackInstallTask::downloadPack() @@ -219,11 +231,12 @@ void PackInstallTask::downloadPack() setStatus(tr("Downloading mods...")); jobPtr = new NetJob(tr("Mod download"), APPLICATION->network()); - for(auto file : m_version.files) { - if(file.serverOnly || file.url.isEmpty()) continue; + for (auto const& file : m_version.files) { + if (file.serverOnly || file.url.isEmpty()) + continue; - QFileInfo fileName(file.name); - auto cacheName = fileName.completeBaseName() + "-" + file.sha1 + "." + fileName.suffix(); + QFileInfo file_info(file.name); + auto cacheName = file_info.completeBaseName() + "-" + file.sha1 + "." + file_info.suffix(); auto entry = APPLICATION->metacache()->resolveEntry("ModpacksCHPacks", cacheName); entry->setStale(true); @@ -235,6 +248,7 @@ void PackInstallTask::downloadPack() qWarning() << "Ignoring" << file.url << "as a file of that path is already downloading."; continue; } + qDebug() << "Will download" << file.url << "to" << path; filesToCopy[path] = entry->getFullPath(); @@ -243,37 +257,36 @@ void PackInstallTask::downloadPack() auto rawSha1 = QByteArray::fromHex(file.sha1.toLatin1()); dl->addValidator(new Net::ChecksumValidator(QCryptographicHash::Sha1, rawSha1)); } + jobPtr->addNetAction(dl); } connect(jobPtr.get(), &NetJob::succeeded, this, [&]() { - abortable = false; jobPtr.reset(); install(); }); connect(jobPtr.get(), &NetJob::failed, [&](QString reason) { - abortable = false; jobPtr.reset(); emitFailed(reason); }); - connect(jobPtr.get(), &NetJob::progress, [&](qint64 current, qint64 total) - { - abortable = true; - setProgress(current, total); - }); + connect(jobPtr.get(), &NetJob::progress, this, &PackInstallTask::setProgress); jobPtr->start(); + + m_abortable = true; } void PackInstallTask::install() { + m_abortable = false; + setStatus(tr("Copying modpack files")); - for (auto iter = filesToCopy.begin(); iter != filesToCopy.end(); iter++) { - auto &to = iter.key(); - auto &from = iter.value(); + for (auto iter = filesToCopy.cbegin(); iter != filesToCopy.cend(); iter++) { + auto& to = iter.key(); + auto& from = iter.value(); FS::copy fileCopyOperation(from, to); if(!fileCopyOperation()) { qWarning() << "Failed to copy" << from << "to" << to; diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.h b/launcher/modplatform/modpacksch/FTBPackInstallTask.h index 087794cd..c32a153f 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.h +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.h @@ -53,13 +53,13 @@ class PackInstallTask : public InstanceTask public: explicit PackInstallTask(Modpack pack, QString version, QWidget* parent = nullptr); - virtual ~PackInstallTask(){} + ~PackInstallTask() override = default; - bool canAbort() const override { return true; } + bool canAbort() const override { return m_abortable; } bool abort() override; protected: - virtual void executeTask() override; + void executeTask() override; private slots: void onDownloadSucceeded(); @@ -71,10 +71,10 @@ private: void install(); private: - bool abortable = false; + bool m_abortable = true; - NetJob::Ptr jobPtr; - shared_qobject_ptr modIdResolver; + NetJob::Ptr jobPtr = nullptr; + shared_qobject_ptr modIdResolver = nullptr; QMap indexFileIdMap; QByteArray response; From 0382f33c46b26b6286d45622c97732c4329ddf7d Mon Sep 17 00:00:00 2001 From: flow Date: Mon, 25 Jul 2022 16:48:29 -0300 Subject: [PATCH 039/273] fix(ui): pump events to show "Copying modpack files..." text Signed-off-by: flow --- .../modplatform/modpacksch/FTBPackInstallTask.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp index bad0f149..8483d312 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp @@ -280,10 +280,13 @@ void PackInstallTask::downloadPack() void PackInstallTask::install() { + setStatus(tr("Copying modpack files...")); + setProgress(0, filesToCopy.size()); + QCoreApplication::processEvents(); + m_abortable = false; - setStatus(tr("Copying modpack files")); - + int i = 0; for (auto iter = filesToCopy.cbegin(); iter != filesToCopy.cend(); iter++) { auto& to = iter.key(); auto& from = iter.value(); @@ -293,9 +296,13 @@ void PackInstallTask::install() emitFailed(tr("Failed to copy files")); return; } + + setProgress(i++, filesToCopy.size()); + QCoreApplication::processEvents(); } - setStatus(tr("Installing modpack")); + setStatus(tr("Installing modpack...")); + QCoreApplication::processEvents(); auto instanceConfigPath = FS::PathCombine(m_stagingPath, "instance.cfg"); auto instanceSettings = std::make_shared(instanceConfigPath); From fbf1901d86e67425cd8404c77508395349d80743 Mon Sep 17 00:00:00 2001 From: flow Date: Mon, 25 Jul 2022 17:51:30 -0300 Subject: [PATCH 040/273] refactor: shuffle some things around to improve readability Signed-off-by: flow --- .../modpacksch/FTBPackInstallTask.cpp | 240 +++++++++--------- .../modpacksch/FTBPackInstallTask.h | 25 +- .../modpacksch/FTBPackManifest.cpp | 4 +- .../modplatform/modpacksch/FTBPackManifest.h | 4 +- 4 files changed, 142 insertions(+), 131 deletions(-) diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp index 8483d312..16013070 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher + * Copyright (C) 2022 flowln * Copyright (c) 2022 Jamie Mansfield * Copyright (C) 2022 Sefa Eyeoglu * @@ -45,25 +46,24 @@ #include "net/ChecksumValidator.h" #include "settings/INISettingsObject.h" -#include "BuildConfig.h" #include "Application.h" +#include "BuildConfig.h" #include "ui/dialogs/ScrollMessageBox.h" namespace ModpacksCH { PackInstallTask::PackInstallTask(Modpack pack, QString version, QWidget* parent) : m_pack(std::move(pack)), m_version_name(std::move(version)), m_parent(parent) -{ -} +{} bool PackInstallTask::abort() { bool aborted = true; - if (jobPtr) - aborted &= jobPtr->abort(); - if (modIdResolver) - aborted &= modIdResolver->abort(); + if (m_net_job) + aborted &= m_net_job->abort(); + if (m_mod_id_resolver_task) + aborted &= m_mod_id_resolver_task->abort(); // FIXME: This should be 'emitAborted()', but InstanceStaging doesn't connect to the abort signal yet... if (aborted) @@ -77,54 +77,48 @@ void PackInstallTask::executeTask() setStatus(tr("Getting the manifest...")); // Find pack version - bool found = false; - VersionInfo version; + auto version_it = std::find_if(m_pack.versions.constBegin(), m_pack.versions.constEnd(), + [this](ModpacksCH::VersionInfo const& a) { return a.name == m_version_name; }); - for(auto vInfo : m_pack.versions) { - if (vInfo.name == m_version_name) { - found = true; - version = vInfo; - break; - } - } - - if(!found) { + if (version_it == m_pack.versions.constEnd()) { emitFailed(tr("Failed to find pack version %1").arg(m_version_name)); return; } - auto *netJob = new NetJob("ModpacksCH::VersionFetch", APPLICATION->network()); + auto version = *version_it; + + auto* netJob = new NetJob("ModpacksCH::VersionFetch", APPLICATION->network()); auto searchUrl = QString(BuildConfig.MODPACKSCH_API_BASE_URL + "public/modpack/%1/%2").arg(m_pack.id).arg(version.id); - netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &response)); + netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchUrl), &m_response)); - jobPtr = netJob; - - QObject::connect(netJob, &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded); - QObject::connect(netJob, &NetJob::failed, this, &PackInstallTask::onDownloadFailed); + QObject::connect(netJob, &NetJob::succeeded, this, &PackInstallTask::onManifestDownloadSucceeded); + QObject::connect(netJob, &NetJob::failed, this, &PackInstallTask::onManifestDownloadFailed); QObject::connect(netJob, &NetJob::progress, this, &PackInstallTask::setProgress); - jobPtr->start(); + m_net_job = netJob; + + netJob->start(); } -void PackInstallTask::onDownloadSucceeded() +void PackInstallTask::onManifestDownloadSucceeded() { - jobPtr.reset(); + m_net_job.reset(); - QJsonParseError parse_error; - QJsonDocument doc = QJsonDocument::fromJson(response, &parse_error); - if(parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset << " reason: " << parse_error.errorString(); - qWarning() << response; + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(m_response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from ModpacksCH at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << m_response; return; } - auto obj = doc.object(); - ModpacksCH::Version version; try { + auto obj = Json::requireObject(doc); ModpacksCH::loadVersion(version, obj); - } catch (const JSONValidationError &e) { + } catch (const JSONValidationError& e) { emitFailed(tr("Could not understand pack manifest:\n") + e.cause()); return; } @@ -134,103 +128,98 @@ void PackInstallTask::onDownloadSucceeded() resolveMods(); } -void PackInstallTask::onDownloadFailed(QString reason) -{ - jobPtr.reset(); - - emitFailed(reason); -} - void PackInstallTask::resolveMods() { setStatus(tr("Resolving mods...")); setProgress(0, 100); - indexFileIdMap.clear(); + m_file_id_map.clear(); Flame::Manifest manifest; int index = 0; - for(auto const& file : m_version.files) { - if(!file.serverOnly && file.url.isEmpty()) { - if(file.curseforge.file <= 0) { - emitFailed(QString("Invalid manifest: There's no information available to download the file '%1'!").arg(file.name)); + for (auto const& file : m_version.files) { + if (!file.serverOnly && file.url.isEmpty()) { + if (file.curseforge.file_id <= 0) { + emitFailed(tr("Invalid manifest: There's no information available to download the file '%1'!").arg(file.name)); return; } Flame::File flame_file; - flame_file.projectId = file.curseforge.project; - flame_file.fileId = file.curseforge.file; + flame_file.projectId = file.curseforge.project_id; + flame_file.fileId = file.curseforge.file_id; flame_file.hash = file.sha1; - manifest.files.insert(flame_file.fileId, flame_file); - indexFileIdMap.insert(index, flame_file.fileId); + m_file_id_map.append(flame_file.fileId); + } else { + m_file_id_map.append(-1); } + index++; } - modIdResolver = new Flame::FileResolvingTask(APPLICATION->network(), manifest); + m_mod_id_resolver_task = new Flame::FileResolvingTask(APPLICATION->network(), manifest); - connect(modIdResolver.get(), &Flame::FileResolvingTask::succeeded, this, [&]() - { - m_abortable = false; + connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::succeeded, this, &PackInstallTask::onResolveModsSucceeded); + connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::failed, this, &PackInstallTask::onResolveModsFailed); + connect(m_mod_id_resolver_task.get(), &Flame::FileResolvingTask::progress, this, &PackInstallTask::setProgress); - QString text; - auto anyBlocked = false; + m_mod_id_resolver_task->start(); +} - Flame::Manifest results = modIdResolver->getResults(); - for (auto index : indexFileIdMap.keys()) { - int fileId = indexFileIdMap.constFind(index).value(); +void PackInstallTask::onResolveModsSucceeded() +{ + m_abortable = false; - Flame::File results_file = results.files[fileId]; - VersionFile& local_file = m_version.files[index]; + QString text; + auto anyBlocked = false; - // First check for blocked mods - if (!results_file.resolved || results_file.url.isEmpty()) { - QString type(local_file.type); + Flame::Manifest results = m_mod_id_resolver_task->getResults(); + for (int index = 0; index < m_file_id_map.size(); index++) { + auto const file_id = m_file_id_map.at(index); + if (file_id < 0) + continue; - type[0] = type[0].toUpper(); - text += QString("%1: %2 - %3
").arg(type, local_file.name, results_file.websiteUrl); - anyBlocked = true; - } else { - local_file.url = results_file.url.toString(); - } - } + Flame::File results_file = results.files[file_id]; + VersionFile& local_file = m_version.files[index]; - if (anyBlocked) { - qDebug() << "Blocked files found, displaying file list"; + // First check for blocked mods + if (!results_file.resolved || results_file.url.isEmpty()) { + QString type(local_file.type); - auto message_dialog = new ScrollMessageBox(m_parent, - tr("Blocked files found"), - tr("The following files are not available for download in third party launchers.
" - "You will need to manually download them and add them to the instance."), - text); - if (message_dialog->exec() == QDialog::Accepted) { - modIdResolver.reset(); - downloadPack(); - } else { - modIdResolver.reset(); - abort(); - return; - } + type[0] = type[0].toUpper(); + text += QString("%1: %2 - %3
").arg(type, local_file.name, results_file.websiteUrl); + anyBlocked = true; } else { - modIdResolver.reset(); - downloadPack(); + local_file.url = results_file.url.toString(); } - }); + } - connect(modIdResolver.get(), &Flame::FileResolvingTask::failed, this, &PackInstallTask::onDownloadFailed); - connect(modIdResolver.get(), &Flame::FileResolvingTask::progress, this, &PackInstallTask::setProgress); + m_mod_id_resolver_task.reset(); - modIdResolver->start(); + if (anyBlocked) { + qDebug() << "Blocked files found, displaying file list"; + + auto message_dialog = new ScrollMessageBox(m_parent, tr("Blocked files found"), + tr("The following files are not available for download in third party launchers.
" + "You will need to manually download them and add them to the instance."), + text); + + if (message_dialog->exec() == QDialog::Accepted) + downloadPack(); + else + abort(); + } else { + downloadPack(); + } } void PackInstallTask::downloadPack() { setStatus(tr("Downloading mods...")); - jobPtr = new NetJob(tr("Mod download"), APPLICATION->network()); + auto* jobPtr = new NetJob(tr("Mod download"), APPLICATION->network()); for (auto const& file : m_version.files) { if (file.serverOnly || file.url.isEmpty()) continue; @@ -244,13 +233,13 @@ void PackInstallTask::downloadPack() auto relpath = FS::PathCombine("minecraft", file.path, file.name); auto path = FS::PathCombine(m_stagingPath, relpath); - if (filesToCopy.contains(path)) { + if (m_files_to_copy.contains(path)) { qWarning() << "Ignoring" << file.url << "as a file of that path is already downloading."; continue; } qDebug() << "Will download" << file.url << "to" << path; - filesToCopy[path] = entry->getFullPath(); + m_files_to_copy[path] = entry->getFullPath(); auto dl = Net::Download::makeCached(file.url, entry); if (!file.sha1.isEmpty()) { @@ -261,43 +250,42 @@ void PackInstallTask::downloadPack() jobPtr->addNetAction(dl); } - connect(jobPtr.get(), &NetJob::succeeded, this, [&]() - { - jobPtr.reset(); - install(); - }); - connect(jobPtr.get(), &NetJob::failed, [&](QString reason) - { - jobPtr.reset(); - emitFailed(reason); - }); - connect(jobPtr.get(), &NetJob::progress, this, &PackInstallTask::setProgress); + connect(jobPtr, &NetJob::succeeded, this, &PackInstallTask::onModDownloadSucceeded); + connect(jobPtr, &NetJob::failed, this, &PackInstallTask::onModDownloadFailed); + connect(jobPtr, &NetJob::progress, this, &PackInstallTask::setProgress); + m_net_job = jobPtr; jobPtr->start(); m_abortable = true; } +void PackInstallTask::onModDownloadSucceeded() +{ + m_net_job.reset(); + install(); +} + void PackInstallTask::install() { setStatus(tr("Copying modpack files...")); - setProgress(0, filesToCopy.size()); + setProgress(0, m_files_to_copy.size()); QCoreApplication::processEvents(); m_abortable = false; int i = 0; - for (auto iter = filesToCopy.cbegin(); iter != filesToCopy.cend(); iter++) { + for (auto iter = m_files_to_copy.constBegin(); iter != m_files_to_copy.constEnd(); iter++) { auto& to = iter.key(); auto& from = iter.value(); FS::copy fileCopyOperation(from, to); - if(!fileCopyOperation()) { + if (!fileCopyOperation()) { qWarning() << "Failed to copy" << from << "to" << to; emitFailed(tr("Failed to copy files")); return; } - setProgress(i++, filesToCopy.size()); + setProgress(i++, m_files_to_copy.size()); QCoreApplication::processEvents(); } @@ -312,20 +300,20 @@ void PackInstallTask::install() auto components = instance.getPackProfile(); components->buildingFromScratch(); - for(auto target : m_version.targets) { - if(target.type == "game" && target.name == "minecraft") { + for (auto target : m_version.targets) { + if (target.type == "game" && target.name == "minecraft") { components->setComponentVersion("net.minecraft", target.version, true); break; } } - for(auto target : m_version.targets) { - if(target.type != "modloader") continue; + for (auto target : m_version.targets) { + if (target.type != "modloader") + continue; - if(target.name == "forge") { + if (target.name == "forge") { components->setComponentVersion("net.minecraftforge", target.version); - } - else if(target.name == "fabric") { + } else if (target.name == "fabric") { components->setComponentVersion("net.fabricmc.fabric-loader", target.version); } } @@ -352,4 +340,20 @@ void PackInstallTask::install() emitSucceeded(); } +void PackInstallTask::onManifestDownloadFailed(QString reason) +{ + m_net_job.reset(); + emitFailed(reason); } +void PackInstallTask::onResolveModsFailed(QString reason) +{ + m_net_job.reset(); + emitFailed(reason); +} +void PackInstallTask::onModDownloadFailed(QString reason) +{ + m_net_job.reset(); + emitFailed(reason); +} + +} // namespace ModpacksCH diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.h b/launcher/modplatform/modpacksch/FTBPackInstallTask.h index c32a153f..e63ca0df 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.h +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.h @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher + * Copyright (C) 2022 flowln * Copyright (C) 2022 Sefa Eyeoglu * * This program is free software: you can redistribute it and/or modify @@ -38,16 +39,16 @@ #include "FTBPackManifest.h" +#include "InstanceTask.h" #include "QObjectPtr.h" #include "modplatform/flame/FileResolvingTask.h" -#include "InstanceTask.h" #include "net/NetJob.h" #include namespace ModpacksCH { -class PackInstallTask : public InstanceTask +class PackInstallTask final : public InstanceTask { Q_OBJECT @@ -62,8 +63,13 @@ protected: void executeTask() override; private slots: - void onDownloadSucceeded(); - void onDownloadFailed(QString reason); + void onManifestDownloadSucceeded(); + void onResolveModsSucceeded(); + void onModDownloadSucceeded(); + + void onManifestDownloadFailed(QString reason); + void onResolveModsFailed(QString reason); + void onModDownloadFailed(QString reason); private: void resolveMods(); @@ -73,17 +79,18 @@ private: private: bool m_abortable = true; - NetJob::Ptr jobPtr = nullptr; - shared_qobject_ptr modIdResolver = nullptr; - QMap indexFileIdMap; + NetJob::Ptr m_net_job = nullptr; + shared_qobject_ptr m_mod_id_resolver_task = nullptr; - QByteArray response; + QList m_file_id_map; + + QByteArray m_response; Modpack m_pack; QString m_version_name; Version m_version; - QMap filesToCopy; + QMap m_files_to_copy; //FIXME: nuke QWidget* m_parent; diff --git a/launcher/modplatform/modpacksch/FTBPackManifest.cpp b/launcher/modplatform/modpacksch/FTBPackManifest.cpp index c927a623..421527ae 100644 --- a/launcher/modplatform/modpacksch/FTBPackManifest.cpp +++ b/launcher/modplatform/modpacksch/FTBPackManifest.cpp @@ -154,8 +154,8 @@ static void loadVersionFile(ModpacksCH::VersionFile & a, QJsonObject & obj) a.optional = Json::requireBoolean(obj, "optional"); a.updated = Json::requireInteger(obj, "updated"); auto curseforgeObj = Json::ensureObject(obj, "curseforge"); // optional - a.curseforge.project = Json::ensureInteger(curseforgeObj, "project"); - a.curseforge.file = Json::ensureInteger(curseforgeObj, "file"); + a.curseforge.project_id = Json::ensureInteger(curseforgeObj, "project"); + a.curseforge.file_id = Json::ensureInteger(curseforgeObj, "file"); } void ModpacksCH::loadVersion(ModpacksCH::Version & m, QJsonObject & obj) diff --git a/launcher/modplatform/modpacksch/FTBPackManifest.h b/launcher/modplatform/modpacksch/FTBPackManifest.h index dac4e3d0..a8b6f35e 100644 --- a/launcher/modplatform/modpacksch/FTBPackManifest.h +++ b/launcher/modplatform/modpacksch/FTBPackManifest.h @@ -118,8 +118,8 @@ struct VersionTarget struct VersionFileCurseForge { - int project; - int file; + int project_id; + int file_id; }; struct VersionFile From 1ce0f0e7a5059527cea3c6da84c19eb3599d2eff Mon Sep 17 00:00:00 2001 From: flow Date: Mon, 25 Jul 2022 17:52:55 -0300 Subject: [PATCH 041/273] fix: progress dialog going away even if the task was not aborted Signed-off-by: flow --- launcher/ui/dialogs/ProgressDialog.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index a79bc837..3c7f53d3 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -43,8 +43,8 @@ void ProgressDialog::setSkipButton(bool present, QString label) void ProgressDialog::on_skipButton_clicked(bool checked) { Q_UNUSED(checked); - task->abort(); - QDialog::reject(); + if (task->abort()) + QDialog::reject(); } ProgressDialog::~ProgressDialog() From b4e8abd0adfd5de300fba3b22f58a2a937f9a5f1 Mon Sep 17 00:00:00 2001 From: DavidoTek <54072917+DavidoTek@users.noreply.github.com> Date: Tue, 26 Jul 2022 23:25:17 +0200 Subject: [PATCH 042/273] feat: win32 enable dark titlebar for dark theme Signed-off-by: DavidoTek <54072917+DavidoTek@users.noreply.github.com> --- launcher/Application.cpp | 20 +++++++++++++ launcher/CMakeLists.txt | 10 +++++++ launcher/ui/WinDarkmode.cpp | 32 ++++++++++++++++++++ launcher/ui/WinDarkmode.h | 60 +++++++++++++++++++++++++++++++++++++ 4 files changed, 122 insertions(+) create mode 100644 launcher/ui/WinDarkmode.cpp create mode 100644 launcher/ui/WinDarkmode.h diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 2bd91fd7..6e25b774 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -60,6 +60,10 @@ #include "ui/themes/BrightTheme.h" #include "ui/themes/CustomTheme.h" +#ifdef Q_OS_WIN +#include "ui/WinDarkmode.h" +#endif + #include "ui/setupwizard/SetupWizard.h" #include "ui/setupwizard/LanguageWizardPage.h" #include "ui/setupwizard/JavaWizardPage.h" @@ -1185,6 +1189,15 @@ void Application::setApplicationTheme(const QString& name, bool initial) { auto & theme = (*themeIter).second; theme->apply(initial); +#ifdef Q_OS_WIN + if (m_mainWindow) { + if (QString::compare(theme->id(), "dark") == 0) { + WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), true); + } else { + WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), false); + } + } +#endif } else { @@ -1412,6 +1425,13 @@ MainWindow* Application::showMainWindow(bool minimized) m_mainWindow = new MainWindow(); m_mainWindow->restoreState(QByteArray::fromBase64(APPLICATION->settings()->get("MainWindowState").toByteArray())); m_mainWindow->restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("MainWindowGeometry").toByteArray())); +#ifdef Q_OS_WIN + if (QString::compare(settings()->get("ApplicationTheme").toString(), "dark") == 0) { + WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), true); + } else { + WinDarkmode::setDarkWinTitlebar(m_mainWindow->winId(), false); + } +#endif if(minimized) { m_mainWindow->showMinimized(); diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index a4a1315d..e7afcfc0 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -907,6 +907,16 @@ SET(LAUNCHER_SOURCES ui/instanceview/VisualGroup.h ) +if(WIN32) + set(LAUNCHER_SOURCES + ${LAUNCHER_SOURCES} + + # GUI - dark titlebar for Windows 10/11 + ui/WinDarkmode.h + ui/WinDarkmode.cpp + ) +endif() + qt_wrap_ui(LAUNCHER_UI ui/setupwizard/PasteWizardPage.ui ui/pages/global/AccountListPage.ui diff --git a/launcher/ui/WinDarkmode.cpp b/launcher/ui/WinDarkmode.cpp new file mode 100644 index 00000000..eac68e4f --- /dev/null +++ b/launcher/ui/WinDarkmode.cpp @@ -0,0 +1,32 @@ +#include + +#include "WinDarkmode.h" + +namespace WinDarkmode { + +/* See https://github.com/statiolake/neovim-qt/commit/da8eaba7f0e38b6b51f3bacd02a8cc2d1f7a34d8 */ +void setDarkWinTitlebar(WId winid, bool darkmode) +{ + HWND hwnd = reinterpret_cast(winid); + BOOL dark = (BOOL) darkmode; + + HMODULE hUxtheme = LoadLibraryExW(L"uxtheme.dll", NULL, LOAD_LIBRARY_SEARCH_SYSTEM32); + HMODULE hUser32 = GetModuleHandleW(L"user32.dll"); + fnAllowDarkModeForWindow AllowDarkModeForWindow + = reinterpret_cast(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(133))); + fnSetPreferredAppMode SetPreferredAppMode + = reinterpret_cast(GetProcAddress(hUxtheme, MAKEINTRESOURCEA(135))); + fnSetWindowCompositionAttribute SetWindowCompositionAttribute + = reinterpret_cast(GetProcAddress(hUser32, "SetWindowCompositionAttribute")); + + SetPreferredAppMode(AllowDark); + AllowDarkModeForWindow(hwnd, dark); + WINDOWCOMPOSITIONATTRIBDATA data = { + WCA_USEDARKMODECOLORS, + &dark, + sizeof(dark) + }; + SetWindowCompositionAttribute(hwnd, &data); +} + +} diff --git a/launcher/ui/WinDarkmode.h b/launcher/ui/WinDarkmode.h new file mode 100644 index 00000000..5b567c6b --- /dev/null +++ b/launcher/ui/WinDarkmode.h @@ -0,0 +1,60 @@ +#pragma once + +#include +#include + + +namespace WinDarkmode { + +void setDarkWinTitlebar(WId winid, bool darkmode); + +enum PreferredAppMode { + Default, + AllowDark, + ForceDark, + ForceLight, + Max +}; + +enum WINDOWCOMPOSITIONATTRIB { + WCA_UNDEFINED = 0, + WCA_NCRENDERING_ENABLED = 1, + WCA_NCRENDERING_POLICY = 2, + WCA_TRANSITIONS_FORCEDISABLED = 3, + WCA_ALLOW_NCPAINT = 4, + WCA_CAPTION_BUTTON_BOUNDS = 5, + WCA_NONCLIENT_RTL_LAYOUT = 6, + WCA_FORCE_ICONIC_REPRESENTATION = 7, + WCA_EXTENDED_FRAME_BOUNDS = 8, + WCA_HAS_ICONIC_BITMAP = 9, + WCA_THEME_ATTRIBUTES = 10, + WCA_NCRENDERING_EXILED = 11, + WCA_NCADORNMENTINFO = 12, + WCA_EXCLUDED_FROM_LIVEPREVIEW = 13, + WCA_VIDEO_OVERLAY_ACTIVE = 14, + WCA_FORCE_ACTIVEWINDOW_APPEARANCE = 15, + WCA_DISALLOW_PEEK = 16, + WCA_CLOAK = 17, + WCA_CLOAKED = 18, + WCA_ACCENT_POLICY = 19, + WCA_FREEZE_REPRESENTATION = 20, + WCA_EVER_UNCLOAKED = 21, + WCA_VISUAL_OWNER = 22, + WCA_HOLOGRAPHIC = 23, + WCA_EXCLUDED_FROM_DDA = 24, + WCA_PASSIVEUPDATEMODE = 25, + WCA_USEDARKMODECOLORS = 26, + WCA_LAST = 27 +}; + +struct WINDOWCOMPOSITIONATTRIBDATA { + WINDOWCOMPOSITIONATTRIB Attrib; + PVOID pvData; + SIZE_T cbData; +}; + +using fnAllowDarkModeForWindow = BOOL (WINAPI *)(HWND hWnd, BOOL allow); +using fnSetPreferredAppMode = PreferredAppMode (WINAPI *)(PreferredAppMode appMode); +using fnSetWindowCompositionAttribute = BOOL (WINAPI *)(HWND hwnd, WINDOWCOMPOSITIONATTRIBDATA *); + +} From 22f5011451088857d470733aed4b47952eb2887a Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Fri, 29 Jul 2022 16:25:12 +0200 Subject: [PATCH 043/273] fix(winget): strictly match non-Legacy setup Signed-off-by: Sefa Eyeoglu --- .github/workflows/winget.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/winget.yml b/.github/workflows/winget.yml index b8ecce13..98981e80 100644 --- a/.github/workflows/winget.yml +++ b/.github/workflows/winget.yml @@ -10,5 +10,5 @@ jobs: - uses: vedantmgoyal2009/winget-releaser@latest with: identifier: PolyMC.PolyMC - installers-regex: '\.exe$' + installers-regex: 'PolyMC-Windows-Setup-.+\.exe$' token: ${{ secrets.WINGET_TOKEN }} From 842b7e6c3974c16adddf3cccda4e0008d8c64597 Mon Sep 17 00:00:00 2001 From: LennyMcLennington Date: Sat, 30 Jul 2022 15:35:48 +0100 Subject: [PATCH 044/273] use c11 instead c17 dont work properly with lgtm's build system and c11 is already almost identical to c17 at least in gcc --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bc906ad4..62724323 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,7 +30,7 @@ set(CMAKE_JAVA_TARGET_OUTPUT_DIR ${PROJECT_BINARY_DIR}/jars) set(CMAKE_CXX_STANDARD_REQUIRED true) set(CMAKE_C_STANDARD_REQUIRED true) set(CMAKE_CXX_STANDARD 17) -set(CMAKE_C_STANDARD 17) +set(CMAKE_C_STANDARD 11) include(GenerateExportHeader) set(CMAKE_CXX_FLAGS "-Wall -pedantic -fstack-protector-strong --param=ssp-buffer-size=4 ${CMAKE_CXX_FLAGS}") if(UNIX AND APPLE) From b15544c163ccbca08ea498b3b3a51c82d7fb3e12 Mon Sep 17 00:00:00 2001 From: Ryan Cao <70191398+ryanccn@users.noreply.github.com> Date: Sun, 31 Jul 2022 01:42:33 +0800 Subject: [PATCH 045/273] Trash instances instead of deleting (when possible) (#549) Squashed because of :pofat: commit history --- launcher/FileSystem.cpp | 229 ++++++++------------ launcher/FileSystem.h | 51 +++-- launcher/InstanceList.cpp | 430 +++++++++++++++++-------------------- launcher/InstanceList.h | 13 ++ launcher/ui/MainWindow.cpp | 32 ++- launcher/ui/MainWindow.h | 1 + 6 files changed, 355 insertions(+), 401 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index ebb4460d..21edbb48 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -35,76 +35,64 @@ #include "FileSystem.h" +#include #include #include -#include #include -#include -#include +#include #include #include +#include #if defined Q_OS_WIN32 - #include - #include - #include - #include - #include - #include - #include - #include - #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #else - #include +#include #endif namespace FS { -void ensureExists(const QDir &dir) +void ensureExists(const QDir& dir) { - if (!QDir().mkpath(dir.absolutePath())) - { - throw FileSystemException("Unable to create folder " + dir.dirName() + " (" + - dir.absolutePath() + ")"); + if (!QDir().mkpath(dir.absolutePath())) { + throw FileSystemException("Unable to create folder " + dir.dirName() + " (" + dir.absolutePath() + ")"); } } -void write(const QString &filename, const QByteArray &data) +void write(const QString& filename, const QByteArray& data) { ensureExists(QFileInfo(filename).dir()); QSaveFile file(filename); - if (!file.open(QSaveFile::WriteOnly)) - { - throw FileSystemException("Couldn't open " + filename + " for writing: " + - file.errorString()); + if (!file.open(QSaveFile::WriteOnly)) { + throw FileSystemException("Couldn't open " + filename + " for writing: " + file.errorString()); } - if (data.size() != file.write(data)) - { - throw FileSystemException("Error writing data to " + filename + ": " + - file.errorString()); + if (data.size() != file.write(data)) { + throw FileSystemException("Error writing data to " + filename + ": " + file.errorString()); } - if (!file.commit()) - { - throw FileSystemException("Error while committing data to " + filename + ": " + - file.errorString()); + if (!file.commit()) { + throw FileSystemException("Error while committing data to " + filename + ": " + file.errorString()); } } -QByteArray read(const QString &filename) +QByteArray read(const QString& filename) { QFile file(filename); - if (!file.open(QFile::ReadOnly)) - { - throw FileSystemException("Unable to open " + filename + " for reading: " + - file.errorString()); + if (!file.open(QFile::ReadOnly)) { + throw FileSystemException("Unable to open " + filename + " for reading: " + file.errorString()); } const qint64 size = file.size(); QByteArray data(int(size), 0); const qint64 ret = file.read(data.data(), size); - if (ret == -1 || ret != size) - { - throw FileSystemException("Error reading data from " + filename + ": " + - file.errorString()); + if (ret == -1 || ret != size) { + throw FileSystemException("Error reading data from " + filename + ": " + file.errorString()); } return data; } @@ -138,12 +126,12 @@ bool ensureFolderPathExists(QString foldernamepath) return success; } -bool copy::operator()(const QString &offset) +bool copy::operator()(const QString& offset) { - //NOTE always deep copy on windows. the alternatives are too messy. - #if defined Q_OS_WIN32 +// NOTE always deep copy on windows. the alternatives are too messy. +#if defined Q_OS_WIN32 m_followSymlinks = true; - #endif +#endif auto src = PathCombine(m_src.absolutePath(), offset); auto dst = PathCombine(m_dst.absolutePath(), offset); @@ -152,52 +140,39 @@ bool copy::operator()(const QString &offset) if (!currentSrc.exists()) return false; - if(!m_followSymlinks && currentSrc.isSymLink()) - { + if (!m_followSymlinks && currentSrc.isSymLink()) { qDebug() << "creating symlink" << src << " - " << dst; - if (!ensureFilePathExists(dst)) - { + if (!ensureFilePathExists(dst)) { qWarning() << "Cannot create path!"; return false; } return QFile::link(currentSrc.symLinkTarget(), dst); - } - else if(currentSrc.isFile()) - { + } else if (currentSrc.isFile()) { qDebug() << "copying file" << src << " - " << dst; - if (!ensureFilePathExists(dst)) - { + if (!ensureFilePathExists(dst)) { qWarning() << "Cannot create path!"; return false; } return QFile::copy(src, dst); - } - else if(currentSrc.isDir()) - { + } else if (currentSrc.isDir()) { qDebug() << "recursing" << offset; - if (!ensureFolderPathExists(dst)) - { + if (!ensureFolderPathExists(dst)) { qWarning() << "Cannot create path!"; return false; } QDir currentDir(src); - for(auto & f : currentDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System)) - { + for (auto& f : currentDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System)) { auto inner_offset = PathCombine(offset, f); // ignore and skip stuff that matches the blacklist. - if(m_blacklist && m_blacklist->matches(inner_offset)) - { + if (m_blacklist && m_blacklist->matches(inner_offset)) { continue; } - if(!operator()(inner_offset)) - { + if (!operator()(inner_offset)) { qWarning() << "Failed to copy" << inner_offset; return false; } } - } - else - { + } else { qCritical() << "Copy ERROR: Unknown filesystem object:" << src; return false; } @@ -208,55 +183,41 @@ bool deletePath(QString path) { bool OK = true; QFileInfo finfo(path); - if(finfo.isFile()) { + if (finfo.isFile()) { return QFile::remove(path); } QDir dir(path); - if (!dir.exists()) - { + if (!dir.exists()) { return OK; } - auto allEntries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | - QDir::AllDirs | QDir::Files, - QDir::DirsFirst); + auto allEntries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst); - for(auto & info: allEntries) - { + for (auto& info : allEntries) { #if defined Q_OS_WIN32 QString nativePath = QDir::toNativeSeparators(info.absoluteFilePath()); auto wString = nativePath.toStdWString(); DWORD dwAttrs = GetFileAttributesW(wString.c_str()); // Windows: check for junctions, reparse points and other nasty things of that sort - if(dwAttrs & FILE_ATTRIBUTE_REPARSE_POINT) - { - if (info.isFile()) - { + if (dwAttrs & FILE_ATTRIBUTE_REPARSE_POINT) { + if (info.isFile()) { OK &= QFile::remove(info.absoluteFilePath()); - } - else if (info.isDir()) - { + } else if (info.isDir()) { OK &= dir.rmdir(info.absoluteFilePath()); } } #else // We do not trust Qt with reparse points, but do trust it with unix symlinks. - if(info.isSymLink()) - { + if (info.isSymLink()) { OK &= QFile::remove(info.absoluteFilePath()); } #endif - else if (info.isDir()) - { + else if (info.isDir()) { OK &= deletePath(info.absoluteFilePath()); - } - else if (info.isFile()) - { + } else if (info.isFile()) { OK &= QFile::remove(info.absoluteFilePath()); - } - else - { + } else { OK = false; qCritical() << "Delete ERROR: Unknown filesystem object:" << info.absoluteFilePath(); } @@ -265,22 +226,30 @@ bool deletePath(QString path) return OK; } - -QString PathCombine(const QString & path1, const QString & path2) +bool trash(QString path, QString *pathInTrash = nullptr) { - if(!path1.size()) +#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) + return false; +#else + return QFile::moveToTrash(path, pathInTrash); +#endif +} + +QString PathCombine(const QString& path1, const QString& path2) +{ + if (!path1.size()) return path2; - if(!path2.size()) + if (!path2.size()) return path1; return QDir::cleanPath(path1 + QDir::separator() + path2); } -QString PathCombine(const QString & path1, const QString & path2, const QString & path3) +QString PathCombine(const QString& path1, const QString& path2, const QString& path3) { return PathCombine(PathCombine(path1, path2), path3); } -QString PathCombine(const QString & path1, const QString & path2, const QString & path3, const QString & path4) +QString PathCombine(const QString& path1, const QString& path2, const QString& path3, const QString& path4) { return PathCombine(PathCombine(path1, path2, path3), path4); } @@ -292,17 +261,14 @@ QString AbsolutePath(QString path) QString ResolveExecutable(QString path) { - if (path.isEmpty()) - { + if (path.isEmpty()) { return QString(); } - if(!path.contains('/')) - { + if (!path.contains('/')) { path = QStandardPaths::findExecutable(path); } QFileInfo pathInfo(path); - if(!pathInfo.exists() || !pathInfo.isExecutable()) - { + if (!pathInfo.exists() || !pathInfo.isExecutable()) { return QString(); } return pathInfo.absoluteFilePath(); @@ -322,12 +288,9 @@ QString NormalizePath(QString path) QDir b(path); QString newAbsolute = b.absolutePath(); - if (newAbsolute.startsWith(currentAbsolute)) - { + if (newAbsolute.startsWith(currentAbsolute)) { return a.relativeFilePath(newAbsolute); - } - else - { + } else { return newAbsolute; } } @@ -336,10 +299,8 @@ QString badFilenameChars = "\"\\/?<>:;*|!+\r\n"; QString RemoveInvalidFilenameChars(QString string, QChar replaceWith) { - for (int i = 0; i < string.length(); i++) - { - if (badFilenameChars.contains(string[i])) - { + for (int i = 0; i < string.length(); i++) { + if (badFilenameChars.contains(string[i])) { string[i] = replaceWith; } } @@ -351,15 +312,12 @@ QString DirNameFromString(QString string, QString inDir) int num = 0; QString baseName = RemoveInvalidFilenameChars(string, '-'); QString dirName; - do - { - if(num == 0) - { + do { + if (num == 0) { dirName = baseName; - } - else - { - dirName = baseName + QString::number(num);; + } else { + dirName = baseName + QString::number(num); + ; } // If it's over 9000 @@ -383,36 +341,31 @@ bool checkProblemticPathJava(QDir folder) bool called_coinit = false; -HRESULT CreateLink(LPCSTR linkPath, LPCSTR targetPath, LPCSTR args) +HRESULT CreateLink(LPCCH linkPath, LPCWSTR targetPath, LPCWSTR args) { HRESULT hres; - if (!called_coinit) - { + if (!called_coinit) { hres = CoInitialize(NULL); called_coinit = true; - if (!SUCCEEDED(hres)) - { + if (!SUCCEEDED(hres)) { qWarning("Failed to initialize COM. Error 0x%08lX", hres); return hres; } } - IShellLinkA *link; - hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, - (LPVOID *)&link); + IShellLink* link; + hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&link); - if (SUCCEEDED(hres)) - { - IPersistFile *persistFile; + if (SUCCEEDED(hres)) { + IPersistFile* persistFile; link->SetPath(targetPath); link->SetArguments(args); - hres = link->QueryInterface(IID_IPersistFile, (LPVOID *)&persistFile); - if (SUCCEEDED(hres)) - { + hres = link->QueryInterface(IID_IPersistFile, (LPVOID*)&persistFile); + if (SUCCEEDED(hres)) { WCHAR wstr[MAX_PATH]; MultiByteToWideChar(CP_ACP, 0, linkPath, -1, wstr, MAX_PATH); @@ -433,8 +386,7 @@ QString getDesktopDir() } // Cross-platform Shortcut creation -bool createShortCut(QString location, QString dest, QStringList args, QString name, - QString icon) +bool createShortCut(QString location, QString dest, QStringList args, QString name, QString icon) { #if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) location = PathCombine(location, name + ".desktop"); @@ -459,8 +411,7 @@ bool createShortCut(QString location, QString dest, QStringList args, QString na stream.flush(); f.close(); - f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | - QFileDevice::ExeOther); + f.setPermissions(f.permissions() | QFileDevice::ExeOwner | QFileDevice::ExeGroup | QFileDevice::ExeOther); return true; #elif defined Q_OS_WIN diff --git a/launcher/FileSystem.h b/launcher/FileSystem.h index fd305b01..b46f3281 100644 --- a/launcher/FileSystem.h +++ b/launcher/FileSystem.h @@ -41,29 +41,27 @@ #include #include -namespace FS -{ +namespace FS { -class FileSystemException : public ::Exception -{ -public: - FileSystemException(const QString &message) : Exception(message) {} +class FileSystemException : public ::Exception { + public: + FileSystemException(const QString& message) : Exception(message) {} }; /** * write data to a file safely */ -void write(const QString &filename, const QByteArray &data); +void write(const QString& filename, const QByteArray& data); /** * read data from a file safely\ */ -QByteArray read(const QString &filename); +QByteArray read(const QString& filename); /** * Update the last changed timestamp of an existing file */ -bool updateTimestamp(const QString & filename); +bool updateTimestamp(const QString& filename); /** * Creates all the folders in a path for the specified path @@ -77,35 +75,31 @@ bool ensureFilePathExists(QString filenamepath); */ bool ensureFolderPathExists(QString filenamepath); -class copy -{ -public: - copy(const QString & src, const QString & dst) +class copy { + public: + copy(const QString& src, const QString& dst) { m_src.setPath(src); m_dst.setPath(dst); } - copy & followSymlinks(const bool follow) + copy& followSymlinks(const bool follow) { m_followSymlinks = follow; return *this; } - copy & blacklist(const IPathMatcher * filter) + copy& blacklist(const IPathMatcher* filter) { m_blacklist = filter; return *this; } - bool operator()() - { - return operator()(QString()); - } + bool operator()() { return operator()(QString()); } -private: - bool operator()(const QString &offset); + private: + bool operator()(const QString& offset); -private: + private: bool m_followSymlinks = true; - const IPathMatcher * m_blacklist = nullptr; + const IPathMatcher* m_blacklist = nullptr; QDir m_src; QDir m_dst; }; @@ -115,9 +109,14 @@ private: */ bool deletePath(QString path); -QString PathCombine(const QString &path1, const QString &path2); -QString PathCombine(const QString &path1, const QString &path2, const QString &path3); -QString PathCombine(const QString &path1, const QString &path2, const QString &path3, const QString &path4); +/** + * Trash a folder / file + */ +bool trash(QString path, QString *pathInTrash); + +QString PathCombine(const QString& path1, const QString& path2); +QString PathCombine(const QString& path1, const QString& path2, const QString& path3); +QString PathCombine(const QString& path1, const QString& path2, const QString& path3, const QString& path4); QString AbsolutePath(QString path); diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index fb7103dd..4447a17c 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -33,30 +33,32 @@ * limitations under the License. */ +#include #include #include -#include #include -#include -#include -#include -#include -#include #include -#include #include #include #include +#include +#include +#include +#include +#include +#include +#include +#include -#include "InstanceList.h" #include "BaseInstance.h" -#include "InstanceTask.h" -#include "settings/INISettingsObject.h" -#include "NullInstance.h" -#include "minecraft/MinecraftInstance.h" -#include "FileSystem.h" #include "ExponentialSeries.h" +#include "FileSystem.h" +#include "InstanceList.h" +#include "InstanceTask.h" +#include "NullInstance.h" #include "WatchLock.h" +#include "minecraft/MinecraftInstance.h" +#include "settings/INISettingsObject.h" #ifdef Q_OS_WIN32 #include @@ -64,13 +66,12 @@ const static int GROUP_FILE_FORMAT_VERSION = 1; -InstanceList::InstanceList(SettingsObjectPtr settings, const QString & instDir, QObject *parent) +InstanceList::InstanceList(SettingsObjectPtr settings, const QString& instDir, QObject* parent) : QAbstractListModel(parent), m_globalSettings(settings) { resumeWatch(); // Create aand normalize path - if (!QDir::current().exists(instDir)) - { + if (!QDir::current().exists(instDir)) { QDir::current().mkpath(instDir); } @@ -83,9 +84,7 @@ InstanceList::InstanceList(SettingsObjectPtr settings, const QString & instDir, m_watcher->addPath(m_instDir); } -InstanceList::~InstanceList() -{ -} +InstanceList::~InstanceList() {} Qt::DropActions InstanceList::supportedDragActions() const { @@ -99,7 +98,7 @@ Qt::DropActions InstanceList::supportedDropActions() const bool InstanceList::canDropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) const { - if(data && data->hasFormat("application/x-instanceid")) { + if (data && data->hasFormat("application/x-instanceid")) { return true; } return false; @@ -107,7 +106,7 @@ bool InstanceList::canDropMimeData(const QMimeData* data, Qt::DropAction action, bool InstanceList::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) { - if(data && data->hasFormat("application/x-instanceid")) { + if (data && data->hasFormat("application/x-instanceid")) { return true; } return false; @@ -120,35 +119,33 @@ QStringList InstanceList::mimeTypes() const return types; } -QMimeData * InstanceList::mimeData(const QModelIndexList& indexes) const +QMimeData* InstanceList::mimeData(const QModelIndexList& indexes) const { auto mimeData = QAbstractListModel::mimeData(indexes); - if(indexes.size() == 1) { + if (indexes.size() == 1) { auto instanceId = data(indexes[0], InstanceIDRole).toString(); mimeData->setData("application/x-instanceid", instanceId.toUtf8()); } return mimeData; } - -int InstanceList::rowCount(const QModelIndex &parent) const +int InstanceList::rowCount(const QModelIndex& parent) const { Q_UNUSED(parent); return m_instances.count(); } -QModelIndex InstanceList::index(int row, int column, const QModelIndex &parent) const +QModelIndex InstanceList::index(int row, int column, const QModelIndex& parent) const { Q_UNUSED(parent); if (row < 0 || row >= m_instances.size()) return QModelIndex(); - return createIndex(row, column, (void *)m_instances.at(row).get()); + return createIndex(row, column, (void*)m_instances.at(row).get()); } -QVariant InstanceList::data(const QModelIndex &index, int role) const +QVariant InstanceList::data(const QModelIndex& index, int role) const { - if (!index.isValid()) - { + if (!index.isValid()) { return QVariant(); } BaseInstance *pdata = static_cast(index.internalPointer()); @@ -193,29 +190,25 @@ QVariant InstanceList::data(const QModelIndex &index, int role) const bool InstanceList::setData(const QModelIndex& index, const QVariant& value, int role) { - if (!index.isValid()) - { + if (!index.isValid()) { return false; } - if(role != Qt::EditRole) - { + if (role != Qt::EditRole) { return false; } - BaseInstance *pdata = static_cast(index.internalPointer()); + BaseInstance* pdata = static_cast(index.internalPointer()); auto newName = value.toString(); - if(pdata->name() == newName) - { + if (pdata->name() == newName) { return true; } pdata->setName(newName); return true; } -Qt::ItemFlags InstanceList::flags(const QModelIndex &index) const +Qt::ItemFlags InstanceList::flags(const QModelIndex& index) const { Qt::ItemFlags f; - if (index.isValid()) - { + if (index.isValid()) { f |= (Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable); } return f; @@ -224,13 +217,11 @@ Qt::ItemFlags InstanceList::flags(const QModelIndex &index) const GroupId InstanceList::getInstanceGroup(const InstanceId& id) const { auto inst = getInstanceById(id); - if(!inst) - { + if (!inst) { return GroupId(); } auto iter = m_instanceGroupIndex.find(inst->id()); - if(iter != m_instanceGroupIndex.end()) - { + if (iter != m_instanceGroupIndex.end()) { return *iter; } return GroupId(); @@ -239,33 +230,27 @@ GroupId InstanceList::getInstanceGroup(const InstanceId& id) const void InstanceList::setInstanceGroup(const InstanceId& id, const GroupId& name) { auto inst = getInstanceById(id); - if(!inst) - { + if (!inst) { qDebug() << "Attempt to set a null instance's group"; return; } bool changed = false; auto iter = m_instanceGroupIndex.find(inst->id()); - if(iter != m_instanceGroupIndex.end()) - { - if(*iter != name) - { + if (iter != m_instanceGroupIndex.end()) { + if (*iter != name) { *iter = name; changed = true; } - } - else - { + } else { changed = true; m_instanceGroupIndex[id] = name; } - if(changed) - { + if (changed) { m_groupNameCache.insert(name); auto idx = getInstIndex(inst.get()); - emit dataChanged(index(idx), index(idx), {GroupRole}); + emit dataChanged(index(idx), index(idx), { GroupRole }); saveGroupList(); } } @@ -279,24 +264,20 @@ void InstanceList::deleteGroup(const QString& name) { bool removed = false; qDebug() << "Delete group" << name; - for(auto & instance: m_instances) - { - const auto & instID = instance->id(); + for (auto& instance : m_instances) { + const auto& instID = instance->id(); auto instGroupName = getInstanceGroup(instID); - if(instGroupName == name) - { + if (instGroupName == name) { m_instanceGroupIndex.remove(instID); qDebug() << "Remove" << instID << "from group" << name; removed = true; auto idx = getInstIndex(instance.get()); - if(idx > 0) - { - emit dataChanged(index(idx), index(idx), {GroupRole}); + if (idx > 0) { + emit dataChanged(index(idx), index(idx), { GroupRole }); } } } - if(removed) - { + if (removed) { saveGroupList(); } } @@ -306,23 +287,75 @@ bool InstanceList::isGroupCollapsed(const QString& group) return m_collapsedGroups.contains(group); } +bool InstanceList::trashInstance(const InstanceId& id) +{ + auto inst = getInstanceById(id); + if (!inst) { + qDebug() << "Cannot trash instance" << id << ". No such instance is present (deleted externally?)."; + return false; + } + + auto cachedGroupId = m_instanceGroupIndex[id]; + + qDebug() << "Will trash instance" << id; + QString trashedLoc; + + if (m_instanceGroupIndex.remove(id)) { + saveGroupList(); + } + + if (!FS::trash(inst->instanceRoot(), &trashedLoc)) { + qDebug() << "Trash of instance" << id << "has not been completely successfully..."; + return false; + } + + qDebug() << "Instance" << id << "has been trashed by the launcher."; + m_trashHistory.push({id, inst->instanceRoot(), trashedLoc, cachedGroupId}); + + return true; +} + +bool InstanceList::trashedSomething() { + return !m_trashHistory.empty(); +} + +void InstanceList::undoTrashInstance() { + if (m_trashHistory.empty()) { + qWarning() << "Nothing to recover from trash."; + return; + } + + auto top = m_trashHistory.pop(); + + while (QDir(top.polyPath).exists()) { + top.id += "1"; + top.polyPath += "1"; + } + + qDebug() << "Moving" << top.trashPath << "back to" << top.polyPath; + QFile(top.trashPath).rename(top.polyPath); + + m_instanceGroupIndex[top.id] = top.groupName; + m_groupNameCache.insert(top.groupName); + + saveGroupList(); + emit instancesChanged(); +} + void InstanceList::deleteInstance(const InstanceId& id) { auto inst = getInstanceById(id); - if(!inst) - { + if (!inst) { qDebug() << "Cannot delete instance" << id << ". No such instance is present (deleted externally?)."; return; } - if(m_instanceGroupIndex.remove(id)) - { + if (m_instanceGroupIndex.remove(id)) { saveGroupList(); } qDebug() << "Will delete instance" << id; - if(!FS::deletePath(inst->instanceRoot())) - { + if (!FS::deletePath(inst->instanceRoot())) { qWarning() << "Deletion of instance" << id << "has not been completely successful ..."; return; } @@ -330,15 +363,13 @@ void InstanceList::deleteInstance(const InstanceId& id) qDebug() << "Instance" << id << "has been deleted by the launcher."; } -static QMap getIdMapping(const QList &list) +static QMap getIdMapping(const QList& list) { QMap out; int i = 0; - for(auto & item: list) - { + for (auto& item : list) { auto id = item->id(); - if(out.contains(id)) - { + if (out.contains(id)) { qWarning() << "Duplicate ID" << id << "in instance list"; } out[id] = std::make_pair(item, i); @@ -347,24 +378,21 @@ static QMap getIdMapping(const QList & return out; } -QList< InstanceId > InstanceList::discoverInstances() +QList InstanceList::discoverInstances() { qDebug() << "Discovering instances in" << m_instDir; QList out; QDirIterator iter(m_instDir, QDir::Dirs | QDir::NoDot | QDir::NoDotDot | QDir::Readable | QDir::Hidden, QDirIterator::FollowSymlinks); - while (iter.hasNext()) - { + while (iter.hasNext()) { QString subDir = iter.next(); QFileInfo dirInfo(subDir); if (!QFileInfo(FS::PathCombine(subDir, "instance.cfg")).exists()) continue; // if it is a symlink, ignore it if it goes to the instance folder - if(dirInfo.isSymLink()) - { + if (dirInfo.isSymLink()) { QFileInfo targetInfo(dirInfo.symLinkTarget()); QFileInfo instDirInfo(m_instDir); - if(targetInfo.canonicalPath() == instDirInfo.canonicalFilePath()) - { + if (targetInfo.canonicalPath() == instDirInfo.canonicalFilePath()) { qDebug() << "Ignoring symlink" << subDir << "that leads into the instances folder"; continue; } @@ -388,74 +416,56 @@ InstanceList::InstListError InstanceList::loadList() QList newList; - for(auto & id: discoverInstances()) - { - if(existingIds.contains(id)) - { + for (auto& id : discoverInstances()) { + if (existingIds.contains(id)) { auto instPair = existingIds[id]; existingIds.remove(id); qDebug() << "Should keep and soft-reload" << id; - } - else - { + } else { InstancePtr instPtr = loadInstance(id); - if(instPtr) - { + if (instPtr) { newList.append(instPtr); } } } // TODO: looks like a general algorithm with a few specifics inserted. Do something about it. - if(!existingIds.isEmpty()) - { + if (!existingIds.isEmpty()) { // get the list of removed instances and sort it by their original index, from last to first auto deadList = existingIds.values(); - auto orderSortPredicate = [](const InstanceLocator & a, const InstanceLocator & b) -> bool - { - return a.second > b.second; - }; + auto orderSortPredicate = [](const InstanceLocator& a, const InstanceLocator& b) -> bool { return a.second > b.second; }; std::sort(deadList.begin(), deadList.end(), orderSortPredicate); // remove the contiguous ranges of rows int front_bookmark = -1; int back_bookmark = -1; int currentItem = -1; - auto removeNow = [&]() - { + auto removeNow = [&]() { beginRemoveRows(QModelIndex(), front_bookmark, back_bookmark); m_instances.erase(m_instances.begin() + front_bookmark, m_instances.begin() + back_bookmark + 1); endRemoveRows(); front_bookmark = -1; back_bookmark = currentItem; }; - for(auto & removedItem: deadList) - { + for (auto& removedItem : deadList) { auto instPtr = removedItem.first; instPtr->invalidate(); currentItem = removedItem.second; - if(back_bookmark == -1) - { + if (back_bookmark == -1) { // no bookmark yet back_bookmark = currentItem; - } - else if(currentItem == front_bookmark - 1) - { + } else if (currentItem == front_bookmark - 1) { // part of contiguous sequence, continue - } - else - { + } else { // seam between previous and current item removeNow(); } front_bookmark = currentItem; } - if(back_bookmark != -1) - { + if (back_bookmark != -1) { removeNow(); } } - if(newList.size()) - { + if (newList.size()) { add(newList); } m_dirty = false; @@ -466,26 +476,23 @@ InstanceList::InstListError InstanceList::loadList() void InstanceList::updateTotalPlayTime() { totalPlayTime = 0; - for(auto const& itr : m_instances) - { + for (auto const& itr : m_instances) { totalPlayTime += itr.get()->totalTimePlayed(); } } void InstanceList::saveNow() { - for(auto & item: m_instances) - { + for (auto& item : m_instances) { item->saveNow(); } } -void InstanceList::add(const QList &t) +void InstanceList::add(const QList& t) { beginInsertRows(QModelIndex(), m_instances.count(), m_instances.count() + t.size() - 1); m_instances.append(t); - for(auto & ptr : t) - { + for (auto& ptr : t) { connect(ptr.get(), &BaseInstance::propertiesChanged, this, &InstanceList::propertiesChanged); } endInsertRows(); @@ -493,69 +500,61 @@ void InstanceList::add(const QList &t) void InstanceList::resumeWatch() { - if(m_watchLevel > 0) - { + if (m_watchLevel > 0) { qWarning() << "Bad suspend level resume in instance list"; return; } m_watchLevel++; - if(m_watchLevel > 0 && m_dirty) - { + if (m_watchLevel > 0 && m_dirty) { loadList(); } } void InstanceList::suspendWatch() { - m_watchLevel --; + m_watchLevel--; } void InstanceList::providerUpdated() { m_dirty = true; - if(m_watchLevel == 1) - { + if (m_watchLevel == 1) { loadList(); } } InstancePtr InstanceList::getInstanceById(QString instId) const { - if(instId.isEmpty()) + if (instId.isEmpty()) return InstancePtr(); - for(auto & inst: m_instances) - { - if (inst->id() == instId) - { + for (auto& inst : m_instances) { + if (inst->id() == instId) { return inst; } } return InstancePtr(); } -QModelIndex InstanceList::getInstanceIndexById(const QString &id) const +QModelIndex InstanceList::getInstanceIndexById(const QString& id) const { return index(getInstIndex(getInstanceById(id).get())); } -int InstanceList::getInstIndex(BaseInstance *inst) const +int InstanceList::getInstIndex(BaseInstance* inst) const { int count = m_instances.count(); - for (int i = 0; i < count; i++) - { - if (inst == m_instances[i].get()) - { + for (int i = 0; i < count; i++) { + if (inst == m_instances[i].get()) { return i; } } return -1; } -void InstanceList::propertiesChanged(BaseInstance *inst) +void InstanceList::propertiesChanged(BaseInstance* inst) { int i = getInstIndex(inst); - if (i != -1) - { + if (i != -1) { emit dataChanged(index(i), index(i)); updateTotalPlayTime(); } @@ -563,8 +562,7 @@ void InstanceList::propertiesChanged(BaseInstance *inst) InstancePtr InstanceList::loadInstance(const InstanceId& id) { - if(!m_groupsLoaded) - { + if (!m_groupsLoaded) { loadGroupList(); } @@ -592,50 +590,42 @@ InstancePtr InstanceList::loadInstance(const InstanceId& id) void InstanceList::saveGroupList() { qDebug() << "Will save group list now."; - if(!m_instancesProbed) - { + if (!m_instancesProbed) { qDebug() << "Group saving prevented because we don't know the full list of instances yet."; return; } WatchLock foo(m_watcher, m_instDir); QString groupFileName = m_instDir + "/instgroups.json"; QMap> reverseGroupMap; - for (auto iter = m_instanceGroupIndex.begin(); iter != m_instanceGroupIndex.end(); iter++) - { + for (auto iter = m_instanceGroupIndex.begin(); iter != m_instanceGroupIndex.end(); iter++) { QString id = iter.key(); QString group = iter.value(); if (group.isEmpty()) continue; - if(!instanceSet.contains(id)) - { + if (!instanceSet.contains(id)) { qDebug() << "Skipping saving missing instance" << id << "to groups list."; continue; } - if (!reverseGroupMap.count(group)) - { + if (!reverseGroupMap.count(group)) { QSet set; set.insert(id); reverseGroupMap[group] = set; - } - else - { - QSet &set = reverseGroupMap[group]; + } else { + QSet& set = reverseGroupMap[group]; set.insert(id); } } QJsonObject toplevel; toplevel.insert("formatVersion", QJsonValue(QString("1"))); QJsonObject groupsArr; - for (auto iter = reverseGroupMap.begin(); iter != reverseGroupMap.end(); iter++) - { + for (auto iter = reverseGroupMap.begin(); iter != reverseGroupMap.end(); iter++) { auto list = iter.value(); auto name = iter.key(); QJsonObject groupObj; QJsonArray instanceArr; groupObj.insert("hidden", QJsonValue(m_collapsedGroups.contains(name))); - for (auto item : list) - { + for (auto item : list) { instanceArr.append(QJsonValue(item)); } groupObj.insert("instances", instanceArr); @@ -643,13 +633,10 @@ void InstanceList::saveGroupList() } toplevel.insert("groups", groupsArr); QJsonDocument doc(toplevel); - try - { + try { FS::write(groupFileName, doc.toJson()); qDebug() << "Group list saved."; - } - catch (const FS::FileSystemException &e) - { + } catch (const FS::FileSystemException& e) { qCritical() << "Failed to write instance group file :" << e.cause(); } } @@ -665,12 +652,9 @@ void InstanceList::loadGroupList() return; QByteArray jsonData; - try - { + try { jsonData = FS::read(groupFileName); - } - catch (const FS::FileSystemException &e) - { + } catch (const FS::FileSystemException& e) { qCritical() << "Failed to read instance group file :" << e.cause(); return; } @@ -679,17 +663,15 @@ void InstanceList::loadGroupList() QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonData, &error); // if the json was bad, fail - if (error.error != QJsonParseError::NoError) - { + if (error.error != QJsonParseError::NoError) { qCritical() << QString("Failed to parse instance group file: %1 at offset %2") - .arg(error.errorString(), QString::number(error.offset)) - .toUtf8(); + .arg(error.errorString(), QString::number(error.offset)) + .toUtf8(); return; } // if the root of the json wasn't an object, fail - if (!jsonDoc.isObject()) - { + if (!jsonDoc.isObject()) { qWarning() << "Invalid group file. Root entry should be an object."; return; } @@ -701,8 +683,7 @@ void InstanceList::loadGroupList() return; // Get the groups. if it's not an object, fail - if (!rootObj.value("groups").isObject()) - { + if (!rootObj.value("groups").isObject()) { qWarning() << "Invalid group list JSON: 'groups' should be an object."; return; } @@ -712,21 +693,20 @@ void InstanceList::loadGroupList() // Iterate through all the groups. QJsonObject groupMapping = rootObj.value("groups").toObject(); - for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++) - { + for (QJsonObject::iterator iter = groupMapping.begin(); iter != groupMapping.end(); iter++) { QString groupName = iter.key(); // If not an object, complain and skip to the next one. - if (!iter.value().isObject()) - { + if (!iter.value().isObject()) { qWarning() << QString("Group '%1' in the group list should be an object.").arg(groupName).toUtf8(); continue; } QJsonObject groupObj = iter.value().toObject(); - if (!groupObj.value("instances").isArray()) - { - qWarning() << QString("Group '%1' in the group list is invalid. It should contain an array called 'instances'.").arg(groupName).toUtf8(); + if (!groupObj.value("instances").isArray()) { + qWarning() << QString("Group '%1' in the group list is invalid. It should contain an array called 'instances'.") + .arg(groupName) + .toUtf8(); continue; } @@ -734,15 +714,14 @@ void InstanceList::loadGroupList() groupSet.insert(groupName); auto hidden = groupObj.value("hidden").toBool(false); - if(hidden) { + if (hidden) { m_collapsedGroups.insert(groupName); } // Iterate through the list of instances in the group. QJsonArray instancesArray = groupObj.value("instances").toArray(); - for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end(); iter2++) - { + for (QJsonArray::iterator iter2 = instancesArray.begin(); iter2 != instancesArray.end(); iter2++) { m_instanceGroupIndex[(*iter2).toString()] = groupName; } } @@ -757,13 +736,11 @@ void InstanceList::instanceDirContentsChanged(const QString& path) emit instancesChanged(); } -void InstanceList::on_InstFolderChanged(const Setting &setting, QVariant value) +void InstanceList::on_InstFolderChanged(const Setting& setting, QVariant value) { QString newInstDir = QDir(value.toString()).canonicalPath(); - if(newInstDir != m_instDir) - { - if(m_groupsLoaded) - { + if (newInstDir != m_instDir) { + if (m_groupsLoaded) { saveGroupList(); } m_instDir = newInstDir; @@ -775,7 +752,7 @@ void InstanceList::on_InstFolderChanged(const Setting &setting, QVariant value) void InstanceList::on_GroupStateChanged(const QString& group, bool collapsed) { qDebug() << "Group" << group << (collapsed ? "collapsed" : "expanded"); - if(collapsed) { + if (collapsed) { m_collapsedGroups.insert(group); } else { m_collapsedGroups.remove(group); @@ -783,19 +760,14 @@ void InstanceList::on_GroupStateChanged(const QString& group, bool collapsed) saveGroupList(); } -class InstanceStaging : public Task -{ -Q_OBJECT +class InstanceStaging : public Task { + Q_OBJECT const unsigned minBackoff = 1; const unsigned maxBackoff = 16; -public: - InstanceStaging ( - InstanceList * parent, - Task * child, - const QString & stagingPath, - const QString& instanceName, - const QString& groupName ) - : backoff(minBackoff, maxBackoff) + + public: + InstanceStaging(InstanceList* parent, Task* child, const QString& stagingPath, const QString& instanceName, const QString& groupName) + : backoff(minBackoff, maxBackoff) { m_parent = parent; m_child.reset(child); @@ -810,62 +782,51 @@ public: connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded); } - virtual ~InstanceStaging() {}; - + virtual ~InstanceStaging(){}; // FIXME/TODO: add ability to abort during instance commit retries bool abort() override { - if(m_child && m_child->canAbort()) - { + if (m_child && m_child->canAbort()) { return m_child->abort(); } return false; } bool canAbort() const override { - if(m_child && m_child->canAbort()) - { + if (m_child && m_child->canAbort()) { return true; } return false; } -protected: - virtual void executeTask() override - { - m_child->start(); - } - QStringList warnings() const override - { - return m_child->warnings(); - } + protected: + virtual void executeTask() override { m_child->start(); } + QStringList warnings() const override { return m_child->warnings(); } -private slots: + private slots: void childSucceded() { unsigned sleepTime = backoff(); - if(m_parent->commitStagedInstance(m_stagingPath, m_instanceName, m_groupName)) - { + if (m_parent->commitStagedInstance(m_stagingPath, m_instanceName, m_groupName)) { emitSucceeded(); return; } // we actually failed, retry? - if(sleepTime == maxBackoff) - { + if (sleepTime == maxBackoff) { emitFailed(tr("Failed to commit instance, even after multiple retries. It is being blocked by something.")); return; } qDebug() << "Failed to commit instance" << m_instanceName << "Initiating backoff:" << sleepTime; m_backoffTimer.start(sleepTime * 500); } - void childFailed(const QString & reason) + void childFailed(const QString& reason) { m_parent->destroyStagingPath(m_stagingPath); emitFailed(reason); } -private: + private: /* * WHY: the whole reason why this uses an exponential backoff retry scheme is antivirus on Windows. * Basically, it starts messing things up while the launcher is extracting/creating instances @@ -873,14 +834,14 @@ private: */ ExponentialSeries backoff; QString m_stagingPath; - InstanceList * m_parent; + InstanceList* m_parent; unique_qobject_ptr m_child; QString m_instanceName; QString m_groupName; QTimer m_backoffTimer; }; -Task * InstanceList::wrapInstanceTask(InstanceTask * task) +Task* InstanceList::wrapInstanceTask(InstanceTask* task) { auto stagingPath = getStagedInstancePath(); task->setStagingPath(stagingPath); @@ -895,8 +856,7 @@ QString InstanceList::getStagedInstancePath() QString relPath = FS::PathCombine(tempDir, key); QDir rootPath(m_instDir); auto path = FS::PathCombine(m_instDir, relPath); - if(!rootPath.mkpath(relPath)) - { + if (!rootPath.mkpath(relPath)) { return QString(); } #ifdef Q_OS_WIN32 @@ -913,8 +873,7 @@ bool InstanceList::commitStagedInstance(const QString& path, const QString& inst { WatchLock lock(m_watcher, m_instDir); QString destination = FS::PathCombine(m_instDir, instID); - if(!dir.rename(path, destination)) - { + if (!dir.rename(path, destination)) { qWarning() << "Failed to move" << path << "to" << destination; return false; } @@ -933,7 +892,8 @@ bool InstanceList::destroyStagingPath(const QString& keyPath) return FS::deletePath(keyPath); } -int InstanceList::getTotalPlayTime() { +int InstanceList::getTotalPlayTime() +{ updateTotalPlayTime(); return totalPlayTime; } diff --git a/launcher/InstanceList.h b/launcher/InstanceList.h index bc6c3af0..62282f04 100644 --- a/launcher/InstanceList.h +++ b/launcher/InstanceList.h @@ -19,6 +19,8 @@ #include #include #include +#include +#include #include "BaseInstance.h" @@ -46,6 +48,12 @@ enum class GroupsState Dirty }; +struct TrashHistoryItem { + QString id; + QString polyPath; + QString trashPath; + QString groupName; +}; class InstanceList : public QAbstractListModel { @@ -102,6 +110,9 @@ public: void setInstanceGroup(const InstanceId & id, const GroupId& name); void deleteGroup(const GroupId & name); + bool trashInstance(const InstanceId &id); + bool trashedSomething(); + void undoTrashInstance(); void deleteInstance(const InstanceId & id); // Wrap an instance creation task in some more task machinery and make it ready to be used @@ -180,4 +191,6 @@ private: QSet instanceSet; bool m_groupsLoaded = false; bool m_instancesProbed = false; + + QStack m_trashHistory; }; diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index d58f158e..c3d95599 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -252,6 +252,9 @@ public: TranslatedAction actionViewInstanceFolder; TranslatedAction actionViewCentralModsFolder; + QMenu * editMenu = nullptr; + TranslatedAction actionUndoTrashInstance; + QMenu * helpMenu = nullptr; TranslatedToolButton helpMenuButton; TranslatedAction actionReportBug; @@ -335,6 +338,14 @@ public: actionSettings->setShortcut(QKeySequence::Preferences); all_actions.append(&actionSettings); + actionUndoTrashInstance = TranslatedAction(MainWindow); + connect(actionUndoTrashInstance, SIGNAL(triggered(bool)), MainWindow, SLOT(undoTrashInstance())); + actionUndoTrashInstance->setObjectName(QStringLiteral("actionUndoTrashInstance")); + actionUndoTrashInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "&Undo Last Instance Deletion")); + actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething()); + actionUndoTrashInstance->setShortcut(QKeySequence("Ctrl+Z")); + all_actions.append(&actionUndoTrashInstance); + if (!BuildConfig.BUG_TRACKER_URL.isEmpty()) { actionReportBug = TranslatedAction(MainWindow); actionReportBug->setObjectName(QStringLiteral("actionReportBug")); @@ -508,6 +519,9 @@ public: fileMenu->addSeparator(); fileMenu->addAction(actionSettings); + editMenu = menuBar->addMenu(tr("&Edit")); + editMenu->addAction(actionUndoTrashInstance); + viewMenu = menuBar->addMenu(tr("&View")); viewMenu->setSeparatorsCollapsible(false); viewMenu->addAction(actionCAT); @@ -732,9 +746,10 @@ public: actionDeleteInstance = TranslatedAction(MainWindow); actionDeleteInstance->setObjectName(QStringLiteral("actionDeleteInstance")); - actionDeleteInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Dele&te Instance...")); + actionDeleteInstance.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Dele&te Instance")); actionDeleteInstance.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Delete the selected instance.")); actionDeleteInstance->setShortcuts({QKeySequence(tr("Backspace")), QKeySequence::Delete}); + actionDeleteInstance->setAutoRepeat(false); all_actions.append(&actionDeleteInstance); actionCopyInstance = TranslatedAction(MainWindow); @@ -1150,6 +1165,11 @@ void MainWindow::showInstanceContextMenu(const QPoint &pos) connect(actionDeleteGroup, SIGNAL(triggered(bool)), SLOT(deleteGroup())); actions.append(actionDeleteGroup); } + + QAction *actionUndoTrashInstance = new QAction("Undo last trash instance", this); + connect(actionUndoTrashInstance, SIGNAL(triggered(bool)), SLOT(undoTrashInstance())); + actionUndoTrashInstance->setEnabled(APPLICATION->instances()->trashedSomething()); + actions.append(actionUndoTrashInstance); } QMenu myMenu; myMenu.addActions(actions); @@ -1832,6 +1852,11 @@ void MainWindow::deleteGroup() } } +void MainWindow::undoTrashInstance() +{ + APPLICATION->instances()->undoTrashInstance(); +} + void MainWindow::on_actionViewInstanceFolder_triggered() { QString str = APPLICATION->settings()->get("InstanceDir").toString(); @@ -1957,7 +1982,12 @@ void MainWindow::on_actionDeleteInstance_triggered() { return; } + auto id = m_selectedInstance->id(); + if (APPLICATION->instances()->trashInstance(id)) { + return; + } + auto response = CustomMessageBox::selectable( this, tr("CAREFUL!"), diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index d7930b5a..dde3d02c 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -145,6 +145,7 @@ private slots: void on_actionDeleteInstance_triggered(); void deleteGroup(); + void undoTrashInstance(); void on_actionExportInstance_triggered(); From 9c105914f0ffa4517b8777c77bf27ca3dfac5492 Mon Sep 17 00:00:00 2001 From: kumquat-ir <66188216+kumquat-ir@users.noreply.github.com> Date: Sun, 31 Jul 2022 15:05:47 -0400 Subject: [PATCH 046/273] use BlockedModsDialog for ftb packs as well Signed-off-by: kumquat-ir <66188216+kumquat-ir@users.noreply.github.com> --- launcher/modplatform/modpacksch/FTBPackInstallTask.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp index 16013070..3c15667c 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp @@ -48,7 +48,7 @@ #include "Application.h" #include "BuildConfig.h" -#include "ui/dialogs/ScrollMessageBox.h" +#include "ui/dialogs/BlockedModsDialog.h" namespace ModpacksCH { @@ -173,6 +173,7 @@ void PackInstallTask::onResolveModsSucceeded() m_abortable = false; QString text; + QList urls; auto anyBlocked = false; Flame::Manifest results = m_mod_id_resolver_task->getResults(); @@ -190,6 +191,7 @@ void PackInstallTask::onResolveModsSucceeded() type[0] = type[0].toUpper(); text += QString("%1: %2 - %3
").arg(type, local_file.name, results_file.websiteUrl); + urls.append(QUrl(results_file.websiteUrl)); anyBlocked = true; } else { local_file.url = results_file.url.toString(); @@ -201,10 +203,11 @@ void PackInstallTask::onResolveModsSucceeded() if (anyBlocked) { qDebug() << "Blocked files found, displaying file list"; - auto message_dialog = new ScrollMessageBox(m_parent, tr("Blocked files found"), + auto message_dialog = new BlockedModsDialog(m_parent, tr("Blocked files found"), tr("The following files are not available for download in third party launchers.
" "You will need to manually download them and add them to the instance."), - text); + text, + urls); if (message_dialog->exec() == QDialog::Accepted) downloadPack(); From 5f1efbeb67c1b150a317b99484bf63888a05b78a Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Mon, 1 Aug 2022 09:30:26 +0200 Subject: [PATCH 047/273] fix: work around ubuntu 22.04 openssl appimage issues by copying openssl libs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit terrible hack but it works™️ Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> --- .github/workflows/build.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0599c1d9..a8b02b39 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -314,6 +314,9 @@ jobs: cp -r ${{ github.workspace }}/JREs/jre17/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-17-openjdk cp -r /home/runner/work/PolyMC/Qt/${{ matrix.qt_version }}/gcc_64/plugins/iconengines/* ${{ env.INSTALL_APPIMAGE_DIR }}/usr/plugins/iconengines + + cp /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/ + cp /usr/lib/x86_64-linux-gnu/libssl.so.1.1 ${{ env.INSTALL_APPIMAGE_DIR }}//usr/lib/ LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib" LD_LIBRARY_PATH="${LD_LIBRARY_PATH}:${{ env.INSTALL_APPIMAGE_DIR }}/usr/lib/jvm/java-8-openjdk/lib/amd64/server" From 0f61f5ba0376a5ad115bcc9eba64cbc452ecde78 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 16 Jun 2022 18:25:14 -0300 Subject: [PATCH 048/273] fix(ui): missing tr() in mod download dialog's title Signed-off-by: flow --- launcher/ui/dialogs/ModDownloadDialog.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp index e4fc3ecc..874d6d64 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.cpp +++ b/launcher/ui/dialogs/ModDownloadDialog.cpp @@ -78,7 +78,7 @@ ModDownloadDialog::ModDownloadDialog(const std::shared_ptr &mods QMetaObject::connectSlotsByName(this); setWindowModality(Qt::WindowModal); - setWindowTitle("Download mods"); + setWindowTitle(dialogTitle()); } QString ModDownloadDialog::dialogTitle() From 4a13dbe3bb22ba47cf1ea5dfe0e9ffc9688b048a Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 14 Jul 2022 22:23:41 -0300 Subject: [PATCH 049/273] feat: create delegate for project item views This allows us to define custom painting for list view items. In particular, this is applied to the mod downloader, in order to allow displaying both the mod name and mod description, and settings their effects (like bold or underline) independent of each other. Signed-off-by: flow --- launcher/CMakeLists.txt | 2 + launcher/ui/pages/modplatform/ModModel.cpp | 23 +++---- launcher/ui/pages/modplatform/ModModel.h | 1 - launcher/ui/pages/modplatform/ModPage.cpp | 3 + launcher/ui/widgets/Common.cpp | 22 +++--- launcher/ui/widgets/Common.h | 9 ++- launcher/ui/widgets/ProjectItem.cpp | 78 ++++++++++++++++++++++ launcher/ui/widgets/ProjectItem.h | 25 +++++++ 8 files changed, 139 insertions(+), 24 deletions(-) create mode 100644 launcher/ui/widgets/ProjectItem.cpp create mode 100644 launcher/ui/widgets/ProjectItem.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 9c5c2ea8..492a4b9d 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -884,6 +884,8 @@ SET(LAUNCHER_SOURCES ui/widgets/PageContainer.cpp ui/widgets/PageContainer.h ui/widgets/PageContainer_p.h + ui/widgets/ProjectItem.h + ui/widgets/ProjectItem.cpp ui/widgets/VersionListView.cpp ui/widgets/VersionListView.h ui/widgets/VersionSelectWidget.cpp diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index 94b1f099..5861fcc6 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -6,6 +6,8 @@ #include "minecraft/PackProfile.h" #include "ui/dialogs/ModDownloadDialog.h" +#include "ui/widgets/ProjectItem.h" + #include namespace ModPlatform { @@ -39,9 +41,6 @@ auto ListModel::data(const QModelIndex& index, int role) const -> QVariant ModPlatform::IndexedPack pack = modpacks.at(pos); switch (role) { - case Qt::DisplayRole: { - return pack.name; - } case Qt::ToolTipRole: { if (pack.description.length() > 100) { // some magic to prevent to long tooltips and replace html linebreaks @@ -64,20 +63,20 @@ auto ListModel::data(const QModelIndex& index, int role) const -> QVariant ((ListModel*)this)->requestLogo(pack.logoName, pack.logoUrl); return icon; } + case Qt::SizeHintRole: + return QSize(0, 58); case Qt::UserRole: { QVariant v; v.setValue(pack); return v; } - case Qt::FontRole: { - QFont font; - if (m_parent->getDialog()->isModSelected(pack.name)) { - font.setBold(true); - font.setUnderline(true); - } - - return font; - } + // Custom data + case UserDataTypes::TITLE: + return pack.name; + case UserDataTypes::DESCRIPTION: + return pack.description; + case UserDataTypes::SELECTED: + return m_parent->getDialog()->isModSelected(pack.name); default: break; } diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h index dd22407c..de864df5 100644 --- a/launcher/ui/pages/modplatform/ModModel.h +++ b/launcher/ui/pages/modplatform/ModModel.h @@ -2,7 +2,6 @@ #include -#include "modplatform/ModAPI.h" #include "modplatform/ModIndex.h" #include "net/NetJob.h" diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 200fe59e..b7fe0ffa 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -43,6 +43,7 @@ #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" #include "ui/dialogs/ModDownloadDialog.h" +#include "ui/widgets/ProjectItem.h" ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api) : QWidget(dialog) @@ -71,6 +72,8 @@ ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api) connect(&filter_widget, &ModFilterWidget::filterUnchanged, this, [&]{ ui->searchButton->setStyleSheet("text-decoration: none"); }); + + ui->packView->setItemDelegate(new ProjectItemDelegate(this)); } ModPage::~ModPage() diff --git a/launcher/ui/widgets/Common.cpp b/launcher/ui/widgets/Common.cpp index f72f3596..097bb6d4 100644 --- a/launcher/ui/widgets/Common.cpp +++ b/launcher/ui/widgets/Common.cpp @@ -1,27 +1,33 @@ #include "Common.h" // Origin: Qt -QStringList viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height, - qreal &widthUsed) +// More specifically, this is a trimmed down version on the algorithm in: +// https://code.woboq.org/qt5/qtbase/src/widgets/styles/qcommonstyle.cpp.html#846 +QList> viewItemTextLayout(QTextLayout& textLayout, int lineWidth, qreal& height) { - QStringList lines; + QList> lines; height = 0; - widthUsed = 0; + textLayout.beginLayout(); + QString str = textLayout.text(); - while (true) - { + while (true) { QTextLine line = textLayout.createLine(); + if (!line.isValid()) break; if (line.textLength() == 0) break; + line.setLineWidth(lineWidth); line.setPosition(QPointF(0, height)); + height += line.height(); - lines.append(str.mid(line.textStart(), line.textLength())); - widthUsed = qMax(widthUsed, line.naturalTextWidth()); + + lines.append(std::make_pair(line.naturalTextWidth(), str.mid(line.textStart(), line.textLength()))); } + textLayout.endLayout(); + return lines; } diff --git a/launcher/ui/widgets/Common.h b/launcher/ui/widgets/Common.h index b3fbe1a0..b3dd5ca8 100644 --- a/launcher/ui/widgets/Common.h +++ b/launcher/ui/widgets/Common.h @@ -1,6 +1,9 @@ #pragma once -#include + #include -QStringList viewItemTextLayout(QTextLayout &textLayout, int lineWidth, qreal &height, - qreal &widthUsed); \ No newline at end of file +/** Cuts out the text in textLayout into smaller pieces, according to the lineWidth. + * Returns a list of pairs, each containing the width of that line and that line's string, respectively. + * The total height of those lines is set in the last argument, 'height'. + */ +QList> viewItemTextLayout(QTextLayout& textLayout, int lineWidth, qreal& height); diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp new file mode 100644 index 00000000..56ae35fb --- /dev/null +++ b/launcher/ui/widgets/ProjectItem.cpp @@ -0,0 +1,78 @@ +#include "ProjectItem.h" + +#include "Common.h" + +#include +#include + +ProjectItemDelegate::ProjectItemDelegate(QWidget* parent) : QStyledItemDelegate(parent) {} + +void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + painter->save(); + + QStyleOptionViewItem opt(option); + initStyleOption(&opt, index); + + auto& rect = opt.rect; + auto icon_width = rect.height(), icon_height = rect.height(); + auto remaining_width = rect.width() - icon_width; + + if (opt.state & QStyle::State_Selected) { + painter->fillRect(rect, opt.palette.highlight()); + painter->setPen(opt.palette.highlightedText().color()); + } else if (opt.state & QStyle::State_MouseOver) { + painter->fillRect(rect, opt.palette.window()); + } + + { // Icon painting + // Square-sized, occupying the left portion + opt.icon.paint(painter, rect.x(), rect.y(), icon_width, icon_height); + } + + { // Title painting + auto title = index.data(UserDataTypes::TITLE).toString(); + + painter->save(); + + auto font = opt.font; + if (index.data(UserDataTypes::SELECTED).toBool()) { + // Set nice font + font.setBold(true); + font.setUnderline(true); + } + + font.setPointSize(font.pointSize() + 2); + painter->setFont(font); + + // On the top, aligned to the left after the icon + painter->drawText(rect.x() + icon_width, rect.y() + QFontMetrics(font).height(), title); + + painter->restore(); + } + + { // Description painting + auto description = index.data(UserDataTypes::DESCRIPTION).toString(); + + QTextLayout text_layout(description, opt.font); + + qreal height = 0; + auto cut_text = viewItemTextLayout(text_layout, remaining_width, height); + + // Get first line unconditionally + description = cut_text.first().second; + // Get second line, elided if needed + if (cut_text.size() > 1) { + if (cut_text.size() > 2) + description += opt.fontMetrics.elidedText(cut_text.at(1).second, opt.textElideMode, cut_text.at(1).first); + else + description += cut_text.at(1).second; + } + + // On the bottom, aligned to the left after the icon, and featuring at most two lines of text (with some margin space to spare) + painter->drawText(rect.x() + icon_width, rect.y() + rect.height() - 2.2 * opt.fontMetrics.height(), remaining_width, + 2 * opt.fontMetrics.height(), Qt::TextWordWrap, description); + } + + painter->restore(); +} diff --git a/launcher/ui/widgets/ProjectItem.h b/launcher/ui/widgets/ProjectItem.h new file mode 100644 index 00000000..f668edf6 --- /dev/null +++ b/launcher/ui/widgets/ProjectItem.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +/* Custom data types for our custom list models :) */ +enum UserDataTypes { + TITLE = 257, // QString + DESCRIPTION = 258, // QString + SELECTED = 259 // bool +}; + +/** This is an item delegate composed of: + * - An Icon on the left + * - A title + * - A description + * */ +class ProjectItemDelegate final : public QStyledItemDelegate { + Q_OBJECT + + public: + ProjectItemDelegate(QWidget* parent); + + void paint(QPainter*, const QStyleOptionViewItem&, const QModelIndex&) const override; + +}; From 6e9a27f40faa00719f7cfd680edc5e16c86a9da7 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 16 Jun 2022 20:46:47 -0300 Subject: [PATCH 050/273] feat: display the 'body' of a MR mod on the mod downloader Signed-off-by: flow --- launcher/modplatform/ModIndex.h | 2 ++ launcher/modplatform/modrinth/ModrinthPackIndex.cpp | 2 ++ launcher/ui/pages/modplatform/ModPage.cpp | 5 ++++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/launcher/modplatform/ModIndex.h b/launcher/modplatform/ModIndex.h index dc297d03..bd3c28e3 100644 --- a/launcher/modplatform/ModIndex.h +++ b/launcher/modplatform/ModIndex.h @@ -73,6 +73,8 @@ struct ExtraPackData { QString sourceUrl; QString wikiUrl; QString discordUrl; + + QString body; }; struct IndexedPack { diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index e50dd96d..3e53becb 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -87,6 +87,8 @@ void Modrinth::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob pack.extraData.donate.append(donate); } + pack.extraData.body = Json::ensureString(obj, "body"); + pack.extraDataLoaded = true; } diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index b7fe0ffa..a9a37b4b 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -40,6 +40,8 @@ #include #include +#include + #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" #include "ui/dialogs/ModDownloadDialog.h" @@ -288,5 +290,6 @@ void ModPage::updateUi() text += "
"; - ui->packDescription->setHtml(text + current.description); + HoeDown h; + ui->packDescription->setHtml(text + (current.extraData.body.isEmpty() ? current.description : h.process(current.extraData.body.toUtf8()))); } From 127b558f9573daece14fe7140bc2242010fbaa9e Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 16 Jun 2022 22:22:14 -0300 Subject: [PATCH 051/273] change: change button names to be more user-friendly Signed-off-by: flow --- launcher/ui/dialogs/ModDownloadDialog.cpp | 1 + launcher/ui/dialogs/ReviewMessageBox.cpp | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp index 874d6d64..60c19856 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.cpp +++ b/launcher/ui/dialogs/ModDownloadDialog.cpp @@ -64,6 +64,7 @@ ModDownloadDialog::ModDownloadDialog(const std::shared_ptr &mods OkButton->setEnabled(false); OkButton->setDefault(true); OkButton->setAutoDefault(true); + OkButton->setText(tr("Review and confirm")); connect(OkButton, &QPushButton::clicked, this, &ModDownloadDialog::confirm); auto CancelButton = m_buttons->button(QDialogButtonBox::Cancel); diff --git a/launcher/ui/dialogs/ReviewMessageBox.cpp b/launcher/ui/dialogs/ReviewMessageBox.cpp index e664e566..7c25c91c 100644 --- a/launcher/ui/dialogs/ReviewMessageBox.cpp +++ b/launcher/ui/dialogs/ReviewMessageBox.cpp @@ -1,11 +1,16 @@ #include "ReviewMessageBox.h" #include "ui_ReviewMessageBox.h" +#include + ReviewMessageBox::ReviewMessageBox(QWidget* parent, QString const& title, QString const& icon) : QDialog(parent), ui(new Ui::ReviewMessageBox) { ui->setupUi(this); + auto back_button = ui->buttonBox->button(QDialogButtonBox::Cancel); + back_button->setText(tr("Back")); + connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &ReviewMessageBox::accept); connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &ReviewMessageBox::reject); } From a8bcd85c93ad1e5af277652862f773413c255c47 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 15 Jul 2022 09:34:11 -0300 Subject: [PATCH 052/273] feat+refactor: add shortcuts to mod downloader and clean up Signed-off-by: flow --- launcher/ui/dialogs/ModDownloadDialog.cpp | 46 ++++++++++------------ launcher/ui/dialogs/ModDownloadDialog.h | 20 +++++----- launcher/ui/pages/modplatform/ModModel.cpp | 1 + launcher/ui/pages/modplatform/ModPage.cpp | 16 ++++++++ 4 files changed, 47 insertions(+), 36 deletions(-) diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp index 60c19856..31253bc2 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.cpp +++ b/launcher/ui/dialogs/ModDownloadDialog.cpp @@ -19,36 +19,33 @@ #include "ModDownloadDialog.h" #include -#include #include +#include #include "Application.h" -#include "ProgressDialog.h" #include "ReviewMessageBox.h" +#include #include #include #include -#include -#include "ui/widgets/PageContainer.h" -#include "ui/pages/modplatform/modrinth/ModrinthModPage.h" #include "ModDownloadTask.h" +#include "ui/pages/modplatform/flame/FlameModPage.h" +#include "ui/pages/modplatform/modrinth/ModrinthModPage.h" +#include "ui/widgets/PageContainer.h" - -ModDownloadDialog::ModDownloadDialog(const std::shared_ptr &mods, QWidget *parent, - BaseInstance *instance) - : QDialog(parent), mods(mods), m_instance(instance) +ModDownloadDialog::ModDownloadDialog(const std::shared_ptr& mods, QWidget* parent, BaseInstance* instance) + : QDialog(parent), mods(mods), m_verticalLayout(new QVBoxLayout(this)), m_instance(instance) { setObjectName(QStringLiteral("ModDownloadDialog")); - - resize(std::max(0.5*parent->width(), 400.0), std::max(0.75*parent->height(), 400.0)); - - m_verticalLayout = new QVBoxLayout(this); m_verticalLayout->setObjectName(QStringLiteral("verticalLayout")); + resize(std::max(0.5 * parent->width(), 400.0), std::max(0.75 * parent->height(), 400.0)); + setWindowIcon(APPLICATION->getThemedIcon("new")); - // NOTE: m_buttons must be initialized before PageContainer, because it indirectly accesses m_buttons through setSuggestedPack! Do not move this below. + // NOTE: m_buttons must be initialized before PageContainer, because it indirectly accesses m_buttons through setSuggestedPack! Do not + // move this below. m_buttons = new QDialogButtonBox(QDialogButtonBox::Help | QDialogButtonBox::Ok | QDialogButtonBox::Cancel); m_container = new PageContainer(this); @@ -65,6 +62,8 @@ ModDownloadDialog::ModDownloadDialog(const std::shared_ptr &mods OkButton->setDefault(true); OkButton->setAutoDefault(true); OkButton->setText(tr("Review and confirm")); + OkButton->setShortcut(tr("Ctrl+Return")); + OkButton->setToolTip(tr("Opens a new popup to review your selected mods and confirm your selection. Shortcut: Ctrl+Return")); connect(OkButton, &QPushButton::clicked, this, &ModDownloadDialog::confirm); auto CancelButton = m_buttons->button(QDialogButtonBox::Cancel); @@ -118,9 +117,9 @@ void ModDownloadDialog::accept() QDialog::accept(); } -QList ModDownloadDialog::getPages() +QList ModDownloadDialog::getPages() { - QList pages; + QList pages; pages.append(new ModrinthModPage(this, m_instance)); if (APPLICATION->currentCapabilities() & Application::SupportsFlame) @@ -129,7 +128,7 @@ QList ModDownloadDialog::getPages() return pages; } -void ModDownloadDialog::addSelectedMod(const QString& name, ModDownloadTask* task) +void ModDownloadDialog::addSelectedMod(QString name, ModDownloadTask* task) { removeSelectedMod(name); modTask.insert(name, task); @@ -137,16 +136,16 @@ void ModDownloadDialog::addSelectedMod(const QString& name, ModDownloadTask* tas m_buttons->button(QDialogButtonBox::Ok)->setEnabled(!modTask.isEmpty()); } -void ModDownloadDialog::removeSelectedMod(const QString &name) +void ModDownloadDialog::removeSelectedMod(QString name) { - if(modTask.contains(name)) + if (modTask.contains(name)) delete modTask.find(name).value(); modTask.remove(name); m_buttons->button(QDialogButtonBox::Ok)->setEnabled(!modTask.isEmpty()); } -bool ModDownloadDialog::isModSelected(const QString &name, const QString& filename) const +bool ModDownloadDialog::isModSelected(QString name, QString filename) const { // FIXME: Is there a way to check for versions without checking the filename // as a heuristic, other than adding such info to ModDownloadTask itself? @@ -154,16 +153,13 @@ bool ModDownloadDialog::isModSelected(const QString &name, const QString& filena return iter != modTask.end() && (iter.value()->getFilename() == filename); } -bool ModDownloadDialog::isModSelected(const QString &name) const +bool ModDownloadDialog::isModSelected(QString name) const { auto iter = modTask.find(name); return iter != modTask.end(); } -ModDownloadDialog::~ModDownloadDialog() +const QList ModDownloadDialog::getTasks() { -} - -const QList ModDownloadDialog::getTasks() { return modTask.values(); } diff --git a/launcher/ui/dialogs/ModDownloadDialog.h b/launcher/ui/dialogs/ModDownloadDialog.h index 1fa1f058..2a26c849 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.h +++ b/launcher/ui/dialogs/ModDownloadDialog.h @@ -21,11 +21,9 @@ #include #include -#include "BaseVersion.h" -#include "ui/pages/BasePageProvider.h" -#include "minecraft/mod/ModFolderModel.h" #include "ModDownloadTask.h" -#include "ui/pages/modplatform/flame/FlameModPage.h" +#include "minecraft/mod/ModFolderModel.h" +#include "ui/pages/BasePageProvider.h" namespace Ui { @@ -41,16 +39,16 @@ class ModDownloadDialog : public QDialog, public BasePageProvider Q_OBJECT public: - explicit ModDownloadDialog(const std::shared_ptr &mods, QWidget *parent, BaseInstance *instance); - ~ModDownloadDialog(); + explicit ModDownloadDialog(const std::shared_ptr& mods, QWidget* parent, BaseInstance* instance); + ~ModDownloadDialog() override = default; QString dialogTitle() override; - QList getPages() override; + QList getPages() override; - void addSelectedMod(const QString & name = QString(), ModDownloadTask * task = nullptr); - void removeSelectedMod(const QString & name = QString()); - bool isModSelected(const QString & name, const QString & filename) const; - bool isModSelected(const QString & name) const; + void addSelectedMod(QString name = QString(), ModDownloadTask* task = nullptr); + void removeSelectedMod(QString name = QString()); + bool isModSelected(QString name, QString filename) const; + bool isModSelected(QString name) const; const QList getTasks(); const std::shared_ptr &mods; diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index 5861fcc6..84f6f4c4 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -2,6 +2,7 @@ #include "BuildConfig.h" #include "Json.h" +#include "ModPage.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" #include "ui/dialogs/ModDownloadDialog.h" diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index a9a37b4b..c355f069 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -76,6 +76,7 @@ ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api) }); ui->packView->setItemDelegate(new ProjectItemDelegate(this)); + ui->packView->installEventFilter(this); } ModPage::~ModPage() @@ -98,6 +99,18 @@ auto ModPage::eventFilter(QObject* watched, QEvent* event) -> bool auto* keyEvent = dynamic_cast(event); if (keyEvent->key() == Qt::Key_Return) { triggerSearch(); + keyEvent->accept(); + return true; + } + } else if (watched == ui->packView && event->type() == QEvent::KeyPress) { + auto* keyEvent = dynamic_cast(event); + if (keyEvent->key() == Qt::Key_Return) { + onModSelected(); + + // To have the 'select mod' button outlined instead of the 'review and confirm' one + ui->modSelectionButton->setFocus(Qt::FocusReason::ShortcutFocusReason); + ui->packView->setFocus(Qt::FocusReason::NoFocusReason); + keyEvent->accept(); return true; } @@ -172,6 +185,9 @@ void ModPage::onVersionSelectionChanged(QString data) void ModPage::onModSelected() { + if (selectedVersion < 0) + return; + auto& version = current.versions[selectedVersion]; if (dialog->isModSelected(current.name, version.fileName)) { dialog->removeSelectedMod(current.name); From 5936c7b65ceef28fb569e966f2bcbe3aed3fa999 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 15 Jul 2022 11:20:08 -0300 Subject: [PATCH 053/273] change: preserve search term across different mod providers Signed-off-by: flow --- launcher/ui/dialogs/ModDownloadDialog.cpp | 20 ++++++++++++++++++++ launcher/ui/dialogs/ModDownloadDialog.h | 5 ++++- launcher/ui/pages/modplatform/ModPage.cpp | 11 ++++++++++- launcher/ui/pages/modplatform/ModPage.h | 5 +++++ launcher/ui/widgets/PageContainer.cpp | 9 ++++++++- launcher/ui/widgets/PageContainer.h | 4 ++++ 6 files changed, 51 insertions(+), 3 deletions(-) diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp index 31253bc2..af6704d9 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.cpp +++ b/launcher/ui/dialogs/ModDownloadDialog.cpp @@ -55,6 +55,8 @@ ModDownloadDialog::ModDownloadDialog(const std::shared_ptr& mods m_container->addButtons(m_buttons); + connect(m_container, &PageContainer::selectedPageChanged, this, &ModDownloadDialog::selectedPageChanged); + // Bonk Qt over its stupid head and make sure it understands which button is the default one... // See: https://stackoverflow.com/questions/24556831/qbuttonbox-set-default-button auto OkButton = m_buttons->button(QDialogButtonBox::Ok); @@ -163,3 +165,21 @@ const QList ModDownloadDialog::getTasks() { return modTask.values(); } + +void ModDownloadDialog::selectedPageChanged(BasePage* previous, BasePage* selected) +{ + auto* prev_page = dynamic_cast(previous); + if (!prev_page) { + qCritical() << "Page '" << previous->displayName() << "' in ModDownloadDialog is not a ModPage!"; + return; + } + + auto* selected_page = dynamic_cast(selected); + if (!selected_page) { + 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()); +} diff --git a/launcher/ui/dialogs/ModDownloadDialog.h b/launcher/ui/dialogs/ModDownloadDialog.h index 2a26c849..18a5f0f3 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.h +++ b/launcher/ui/dialogs/ModDownloadDialog.h @@ -34,7 +34,7 @@ class PageContainer; class QDialogButtonBox; class ModrinthModPage; -class ModDownloadDialog : public QDialog, public BasePageProvider +class ModDownloadDialog final : public QDialog, public BasePageProvider { Q_OBJECT @@ -58,6 +58,9 @@ public slots: void accept() override; void reject() override; +private slots: + void selectedPageChanged(BasePage* previous, BasePage* selected); + private: Ui::ModDownloadDialog *ui = nullptr; PageContainer * m_container = nullptr; diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index c355f069..e052b655 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -138,7 +138,16 @@ void ModPage::triggerSearch() updateSelectionButton(); } - listModel->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex(), changed); + listModel->searchWithTerm(getSearchTerm(), ui->sortByBox->currentIndex(), changed); +} + +QString ModPage::getSearchTerm() const +{ + return ui->searchEdit->text(); +} +void ModPage::setSearchTerm(QString term) +{ + ui->searchEdit->setText(term); } void ModPage::onSelectionChanged(QModelIndex first, QModelIndex second) diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index cf00e16e..4990c1c0 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -45,6 +45,11 @@ class ModPage : public QWidget, public BasePage { auto getFilter() const -> const std::shared_ptr { return m_filter; } auto getDialog() const -> const ModDownloadDialog* { return dialog; } + /** Get the current term in the search bar. */ + auto getSearchTerm() const -> QString; + /** Programatically set the term in the search bar. */ + void setSearchTerm(QString); + auto getCurrent() -> ModPlatform::IndexedPack& { return current; } void updateModVersions(int prev_count = -1); diff --git a/launcher/ui/widgets/PageContainer.cpp b/launcher/ui/widgets/PageContainer.cpp index 419ccb66..8d606820 100644 --- a/launcher/ui/widgets/PageContainer.cpp +++ b/launcher/ui/widgets/PageContainer.cpp @@ -244,7 +244,14 @@ void PageContainer::help() void PageContainer::currentChanged(const QModelIndex ¤t) { - showPage(current.isValid() ? m_proxyModel->mapToSource(current).row() : -1); + int selected_index = current.isValid() ? m_proxyModel->mapToSource(current).row() : -1; + + auto* selected = m_model->pages().at(selected_index); + auto* previous = m_currentPage; + + emit selectedPageChanged(previous, selected); + + showPage(selected_index); } bool PageContainer::prepareToClose() diff --git a/launcher/ui/widgets/PageContainer.h b/launcher/ui/widgets/PageContainer.h index 86f549eb..80d87a9b 100644 --- a/launcher/ui/widgets/PageContainer.h +++ b/launcher/ui/widgets/PageContainer.h @@ -95,6 +95,10 @@ private: public slots: void help(); +signals: + /** Emitted when the currently selected page is changed */ + void selectedPageChanged(BasePage* previous, BasePage* selected); + private slots: void currentChanged(const QModelIndex ¤t); void showPage(int row); From c3f647dc962a0da6e96e54f472ee764370ed4f66 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 15 Jul 2022 11:57:27 -0300 Subject: [PATCH 054/273] feat: add (semi) instant searching in mod downloader It has a delay of 350ms from the last typed character to search, in order to cache small changes while typing. Signed-off-by: flow --- launcher/ui/pages/modplatform/ModPage.cpp | 12 ++++++++++++ launcher/ui/pages/modplatform/ModPage.h | 3 +++ 2 files changed, 15 insertions(+) diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index e052b655..4fad037e 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -56,8 +56,15 @@ ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api) , api(api) { ui->setupUi(this); + connect(ui->searchButton, &QPushButton::clicked, this, &ModPage::triggerSearch); connect(ui->modFilterButton, &QPushButton::clicked, this, &ModPage::filterMods); + + m_search_timer.setTimerType(Qt::TimerType::CoarseTimer); + m_search_timer.setSingleShot(true); + + connect(&m_search_timer, &QTimer::timeout, this, &ModPage::triggerSearch); + ui->searchEdit->installEventFilter(this); ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); @@ -101,6 +108,11 @@ auto ModPage::eventFilter(QObject* watched, QEvent* event) -> bool triggerSearch(); keyEvent->accept(); return true; + } else { + if (m_search_timer.isActive()) + m_search_timer.stop(); + + m_search_timer.start(350); } } else if (watched == ui->packView && event->type() == QEvent::KeyPress) { auto* keyEvent = dynamic_cast(event); diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index 4990c1c0..c58a7cbb 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -81,4 +81,7 @@ class ModPage : public QWidget, public BasePage { std::unique_ptr api; int selectedVersion = -1; + + // Used to do instant searching with a delay to cache quick changes + QTimer m_search_timer; }; From 158b7fd166f6be76b4e6c0e3f7cf14d6a4b43a17 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 15 Jul 2022 13:18:12 -0300 Subject: [PATCH 055/273] feat+refactor: clean up ProgressWidget and add progress indicatior to mod downloader Signed-off-by: flow --- launcher/ui/pages/modplatform/ModModel.h | 1 + launcher/ui/pages/modplatform/ModPage.cpp | 9 +- launcher/ui/pages/modplatform/ModPage.h | 3 + launcher/ui/widgets/ProgressWidget.cpp | 102 +++++++++++++++------- launcher/ui/widgets/ProgressWidget.h | 48 +++++++--- 5 files changed, 118 insertions(+), 45 deletions(-) diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h index de864df5..96747fbd 100644 --- a/launcher/ui/pages/modplatform/ModModel.h +++ b/launcher/ui/pages/modplatform/ModModel.h @@ -30,6 +30,7 @@ class ListModel : public QAbstractListModel { auto data(const QModelIndex& index, int role) const -> QVariant override; inline void setActiveJob(NetJob::Ptr ptr) { jobPtr = ptr; } + inline NetJob* activeJob() { return jobPtr.get(); } /* Ask the API for more information */ void fetchMore(const QModelIndex& parent) override; diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 4fad037e..176f8fde 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -53,6 +53,7 @@ ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api) , ui(new Ui::ModPage) , dialog(dialog) , filter_widget(static_cast(instance)->getPackProfile()->getComponentVersion("net.minecraft"), this) + , m_fetch_progress(this, false) , api(api) { ui->setupUi(this); @@ -70,7 +71,12 @@ ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api) ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); - ui->gridLayout_3->addWidget(&filter_widget, 0, 0, 1, ui->gridLayout_3->columnCount()); + m_fetch_progress.hideIfInactive(true); + m_fetch_progress.setFixedHeight(24); + m_fetch_progress.progressFormat(""); + + ui->gridLayout_3->addWidget(&m_fetch_progress, 0, 0, 1, ui->gridLayout_3->columnCount()); + ui->gridLayout_3->addWidget(&filter_widget, 1, 0, 1, ui->gridLayout_3->columnCount()); filter_widget.setInstance(static_cast(m_instance)); m_filter = filter_widget.getFilter(); @@ -151,6 +157,7 @@ void ModPage::triggerSearch() } listModel->searchWithTerm(getSearchTerm(), ui->sortByBox->currentIndex(), changed); + m_fetch_progress.watch(listModel->activeJob()); } QString ModPage::getSearchTerm() const diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index c58a7cbb..09c38d8b 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -8,6 +8,7 @@ #include "ui/pages/BasePage.h" #include "ui/pages/modplatform/ModModel.h" #include "ui/widgets/ModFilterWidget.h" +#include "ui/widgets/ProgressWidget.h" class ModDownloadDialog; @@ -75,6 +76,8 @@ class ModPage : public QWidget, public BasePage { ModFilterWidget filter_widget; std::shared_ptr m_filter; + ProgressWidget m_fetch_progress; + ModPlatform::ListModel* listModel = nullptr; ModPlatform::IndexedPack current; diff --git a/launcher/ui/widgets/ProgressWidget.cpp b/launcher/ui/widgets/ProgressWidget.cpp index 911e555d..b60d9a7a 100644 --- a/launcher/ui/widgets/ProgressWidget.cpp +++ b/launcher/ui/widgets/ProgressWidget.cpp @@ -1,66 +1,104 @@ // Licensed under the Apache-2.0 license. See README.md for details. #include "ProgressWidget.h" -#include -#include -#include #include +#include +#include +#include #include "tasks/Task.h" -ProgressWidget::ProgressWidget(QWidget *parent) - : QWidget(parent) +ProgressWidget::ProgressWidget(QWidget* parent, bool show_label) : QWidget(parent) { - m_label = new QLabel(this); - m_label->setWordWrap(true); + auto* layout = new QVBoxLayout(this); + + if (show_label) { + m_label = new QLabel(this); + m_label->setWordWrap(true); + layout->addWidget(m_label); + } + m_bar = new QProgressBar(this); m_bar->setMinimum(0); m_bar->setMaximum(100); - QVBoxLayout *layout = new QVBoxLayout(this); - layout->addWidget(m_label); layout->addWidget(m_bar); - layout->addStretch(); + setLayout(layout); } -void ProgressWidget::start(std::shared_ptr task) +void ProgressWidget::reset() { - if (m_task) - { - disconnect(m_task.get(), 0, this, 0); - } - m_task = task; - connect(m_task.get(), &Task::finished, this, &ProgressWidget::handleTaskFinish); - connect(m_task.get(), &Task::status, this, &ProgressWidget::handleTaskStatus); - connect(m_task.get(), &Task::progress, this, &ProgressWidget::handleTaskProgress); - connect(m_task.get(), &Task::destroyed, this, &ProgressWidget::taskDestroyed); - if (!m_task->isRunning()) - { - QMetaObject::invokeMethod(m_task.get(), "start", Qt::QueuedConnection); - } + m_bar->reset(); } + +void ProgressWidget::progressFormat(QString format) +{ + if (format.isEmpty()) + m_bar->setTextVisible(false); + else + m_bar->setFormat(format); +} + +void ProgressWidget::watch(Task* task) +{ + if (!task) + return; + + if (m_task) + disconnect(m_task, nullptr, this, nullptr); + + m_task = task; + + connect(m_task, &Task::finished, this, &ProgressWidget::handleTaskFinish); + connect(m_task, &Task::status, this, &ProgressWidget::handleTaskStatus); + connect(m_task, &Task::progress, this, &ProgressWidget::handleTaskProgress); + connect(m_task, &Task::destroyed, this, &ProgressWidget::taskDestroyed); + + show(); +} + +void ProgressWidget::start(Task* task) +{ + watch(task); + if (!m_task->isRunning()) + QMetaObject::invokeMethod(m_task, "start", Qt::QueuedConnection); +} + bool ProgressWidget::exec(std::shared_ptr task) { QEventLoop loop; + connect(task.get(), &Task::finished, &loop, &QEventLoop::quit); - start(task); + + start(task.get()); + if (task->isRunning()) - { loop.exec(); - } + return task->wasSuccessful(); } +void ProgressWidget::show() +{ + setHidden(false); +} +void ProgressWidget::hide() +{ + setHidden(true); +} + void ProgressWidget::handleTaskFinish() { - if (!m_task->wasSuccessful()) - { + if (!m_task->wasSuccessful() && m_label) m_label->setText(m_task->failReason()); - } + + if (m_hide_if_inactive) + hide(); } -void ProgressWidget::handleTaskStatus(const QString &status) +void ProgressWidget::handleTaskStatus(const QString& status) { - m_label->setText(status); + if (m_label) + m_label->setText(status); } void ProgressWidget::handleTaskProgress(qint64 current, qint64 total) { diff --git a/launcher/ui/widgets/ProgressWidget.h b/launcher/ui/widgets/ProgressWidget.h index fa67748a..4d9097b8 100644 --- a/launcher/ui/widgets/ProgressWidget.h +++ b/launcher/ui/widgets/ProgressWidget.h @@ -9,24 +9,48 @@ class Task; class QProgressBar; class QLabel; -class ProgressWidget : public QWidget -{ +class ProgressWidget : public QWidget { Q_OBJECT -public: - explicit ProgressWidget(QWidget *parent = nullptr); + public: + explicit ProgressWidget(QWidget* parent = nullptr, bool show_label = true); -public slots: - void start(std::shared_ptr task); + /** Whether to hide the widget automatically if it's watching no running task. */ + void hideIfInactive(bool hide) { m_hide_if_inactive = hide; } + + /** Reset the displayed progress to 0 */ + void reset(); + + /** The text that shows up in the middle of the progress bar. + * By default it's '%p%', with '%p' being the total progress in percentage. + */ + void progressFormat(QString); + + public slots: + /** Watch the progress of a task. */ + void watch(Task* task); + + /** Watch the progress of a task, and start it if needed */ + void start(Task* task); + + /** Blocking way of waiting for a task to finish. */ bool exec(std::shared_ptr task); -private slots: + /** Un-hide the widget if needed. */ + void show(); + + /** Make the widget invisible. */ + void hide(); + + private slots: void handleTaskFinish(); - void handleTaskStatus(const QString &status); + void handleTaskStatus(const QString& status); void handleTaskProgress(qint64 current, qint64 total); void taskDestroyed(); -private: - QLabel *m_label; - QProgressBar *m_bar; - std::shared_ptr m_task; + private: + QLabel* m_label = nullptr; + QProgressBar* m_bar = nullptr; + Task* m_task = nullptr; + + bool m_hide_if_inactive = false; }; From 74c6c5cfbc9f588052d8423c03c30f2c547bd5c9 Mon Sep 17 00:00:00 2001 From: flow Date: Mon, 18 Jul 2022 19:15:02 -0300 Subject: [PATCH 056/273] refactor: use function cb instead of class cb in getModInfo I've discovered functional programming :^) This makes this route more fit for general use. Signed-off-by: flow --- launcher/modplatform/ModAPI.h | 2 +- launcher/modplatform/helpers/NetworkModAPI.cpp | 8 ++++---- launcher/modplatform/helpers/NetworkModAPI.h | 2 +- launcher/ui/pages/modplatform/ModModel.cpp | 3 ++- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/launcher/modplatform/ModAPI.h b/launcher/modplatform/ModAPI.h index 4114d83c..999a0552 100644 --- a/launcher/modplatform/ModAPI.h +++ b/launcher/modplatform/ModAPI.h @@ -73,7 +73,7 @@ class ModAPI { }; virtual void searchMods(CallerType* caller, SearchArgs&& args) const = 0; - virtual void getModInfo(CallerType* caller, ModPlatform::IndexedPack& pack) = 0; + virtual void getModInfo(ModPlatform::IndexedPack& pack, std::function callback) = 0; virtual auto getProject(QString addonId, QByteArray* response) const -> NetJob* = 0; virtual auto getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* = 0; diff --git a/launcher/modplatform/helpers/NetworkModAPI.cpp b/launcher/modplatform/helpers/NetworkModAPI.cpp index 90edfe31..36e11217 100644 --- a/launcher/modplatform/helpers/NetworkModAPI.cpp +++ b/launcher/modplatform/helpers/NetworkModAPI.cpp @@ -31,22 +31,22 @@ void NetworkModAPI::searchMods(CallerType* caller, SearchArgs&& args) const netJob->start(); } -void NetworkModAPI::getModInfo(CallerType* caller, ModPlatform::IndexedPack& pack) +void NetworkModAPI::getModInfo(ModPlatform::IndexedPack& pack, std::function callback) { auto response = new QByteArray(); auto job = getProject(pack.addonId.toString(), response); - QObject::connect(job, &NetJob::succeeded, caller, [caller, &pack, response] { + QObject::connect(job, &NetJob::succeeded, [callback, &pack, response] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from " << caller->debugName() << " at " << parse_error.offset + qWarning() << "Error while parsing JSON response for mod info at " << parse_error.offset << " reason: " << parse_error.errorString(); qWarning() << *response; return; } - caller->infoRequestFinished(doc, pack); + callback(doc, pack); }); job->start(); diff --git a/launcher/modplatform/helpers/NetworkModAPI.h b/launcher/modplatform/helpers/NetworkModAPI.h index 989bcec4..364fbb8a 100644 --- a/launcher/modplatform/helpers/NetworkModAPI.h +++ b/launcher/modplatform/helpers/NetworkModAPI.h @@ -5,7 +5,7 @@ class NetworkModAPI : public ModAPI { public: void searchMods(CallerType* caller, SearchArgs&& args) const override; - void getModInfo(CallerType* caller, ModPlatform::IndexedPack& pack) override; + void getModInfo(ModPlatform::IndexedPack& pack, std::function callback) override; void getVersions(CallerType* caller, VersionSearchArgs&& args) const override; auto getProject(QString addonId, QByteArray* response) const -> NetJob* override; diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index 84f6f4c4..77ecd056 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -102,7 +102,8 @@ void ListModel::performPaginatedSearch() void ListModel::requestModInfo(ModPlatform::IndexedPack& current) { - m_parent->apiProvider()->getModInfo(this, current); + m_parent->apiProvider()->getModInfo( + current, [this](QJsonDocument& doc, ModPlatform::IndexedPack& pack) { infoRequestFinished(doc, pack); }); } void ListModel::refresh() From 5bc67d3f6b03bae6ff861d6641d22bbddefe2202 Mon Sep 17 00:00:00 2001 From: flow Date: Mon, 18 Jul 2022 19:17:44 -0300 Subject: [PATCH 057/273] feat: cache extra mod info (like links and body) Signed-off-by: flow --- launcher/ui/pages/modplatform/ModModel.cpp | 27 +++++++++++++++++++--- launcher/ui/pages/modplatform/ModModel.h | 5 ++-- launcher/ui/pages/modplatform/ModPage.cpp | 9 ++++---- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index 77ecd056..13cfc9cb 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -85,6 +85,17 @@ auto ListModel::data(const QModelIndex& index, int role) const -> QVariant return {}; } +bool ListModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + int pos = index.row(); + if (pos >= modpacks.size() || pos < 0 || !index.isValid()) + return false; + + modpacks[pos] = value.value(); + + return true; +} + void ListModel::requestModVersions(ModPlatform::IndexedPack const& current) { auto profile = (dynamic_cast((dynamic_cast(parent()))->m_instance))->getPackProfile(); @@ -100,10 +111,10 @@ void ListModel::performPaginatedSearch() this, { nextSearchOffset, currentSearchTerm, getSorts()[currentSort], profile->getModLoaders(), getMineVersions() }); } -void ListModel::requestModInfo(ModPlatform::IndexedPack& current) +void ListModel::requestModInfo(ModPlatform::IndexedPack& current, QModelIndex index) { m_parent->apiProvider()->getModInfo( - current, [this](QJsonDocument& doc, ModPlatform::IndexedPack& pack) { infoRequestFinished(doc, pack); }); + current, [this, index](QJsonDocument& doc, ModPlatform::IndexedPack& pack) { infoRequestFinished(doc, pack, index); }); } void ListModel::refresh() @@ -256,7 +267,7 @@ void ListModel::searchRequestFailed(QString reason) } } -void ListModel::infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack) +void ListModel::infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index) { qDebug() << "Loading mod info"; @@ -268,6 +279,16 @@ void ListModel::infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack qWarning() << "Error while reading " << debugName() << " mod info: " << e.cause(); } + // Check if the index is still valid for this mod or not + if (pack.addonId == data(index, Qt::UserRole).value().addonId) { + // Cache info :^) + QVariant new_pack; + new_pack.setValue(pack); + if (!setData(index, new_pack, Qt::UserRole)) { + qWarning() << "Failed to cache mod info!"; + } + } + m_parent->updateUi(); } diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h index 96747fbd..33b327d0 100644 --- a/launcher/ui/pages/modplatform/ModModel.h +++ b/launcher/ui/pages/modplatform/ModModel.h @@ -28,6 +28,7 @@ class ListModel : public QAbstractListModel { /* Retrieve information from the model at a given index with the given role */ auto data(const QModelIndex& index, int role) const -> QVariant override; + bool setData(const QModelIndex &index, const QVariant &value, int role) override; inline void setActiveJob(NetJob::Ptr ptr) { jobPtr = ptr; } inline NetJob* activeJob() { return jobPtr.get(); } @@ -36,7 +37,7 @@ class ListModel : public QAbstractListModel { void fetchMore(const QModelIndex& parent) override; void refresh(); void searchWithTerm(const QString& term, const int sort, const bool filter_changed); - void requestModInfo(ModPlatform::IndexedPack& current); + void requestModInfo(ModPlatform::IndexedPack& current, QModelIndex index); void requestModVersions(const ModPlatform::IndexedPack& current); virtual void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) = 0; @@ -51,7 +52,7 @@ class ListModel : public QAbstractListModel { void searchRequestFinished(QJsonDocument& doc); void searchRequestFailed(QString reason); - void infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack); + void infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index); void versionRequestSucceeded(QJsonDocument doc, QString addonId); diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 176f8fde..59831dd9 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -169,13 +169,13 @@ void ModPage::setSearchTerm(QString term) ui->searchEdit->setText(term); } -void ModPage::onSelectionChanged(QModelIndex first, QModelIndex second) +void ModPage::onSelectionChanged(QModelIndex curr, QModelIndex prev) { ui->versionSelectionBox->clear(); - if (!first.isValid()) { return; } + if (!curr.isValid()) { return; } - current = listModel->data(first, Qt::UserRole).value(); + current = listModel->data(curr, Qt::UserRole).value(); if (!current.versionsLoaded) { qDebug() << QString("Loading %1 mod versions").arg(debugName()); @@ -195,7 +195,8 @@ void ModPage::onSelectionChanged(QModelIndex first, QModelIndex second) if(!current.extraDataLoaded){ qDebug() << QString("Loading %1 mod info").arg(debugName()); - listModel->requestModInfo(current); + + listModel->requestModInfo(current, curr); } updateUi(); From 6f052baa94f6f758cb18b81e2cf5e28cdc5bd367 Mon Sep 17 00:00:00 2001 From: flow Date: Mon, 18 Jul 2022 19:22:31 -0300 Subject: [PATCH 058/273] refactor: use function cb instead of class cb in getVersions I've discovered even more functional programming! :^) Signed-off-by: flow --- launcher/modplatform/ModAPI.h | 2 +- launcher/modplatform/helpers/NetworkModAPI.cpp | 12 ++++++------ launcher/modplatform/helpers/NetworkModAPI.h | 2 +- launcher/ui/pages/modplatform/ModModel.cpp | 3 ++- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/launcher/modplatform/ModAPI.h b/launcher/modplatform/ModAPI.h index 999a0552..c7408835 100644 --- a/launcher/modplatform/ModAPI.h +++ b/launcher/modplatform/ModAPI.h @@ -85,7 +85,7 @@ class ModAPI { ModLoaderTypes loaders; }; - virtual void getVersions(CallerType* caller, VersionSearchArgs&& args) const = 0; + virtual void getVersions(VersionSearchArgs&& args, std::function callback) const = 0; static auto getModLoaderString(ModLoaderType type) -> const QString { switch (type) { diff --git a/launcher/modplatform/helpers/NetworkModAPI.cpp b/launcher/modplatform/helpers/NetworkModAPI.cpp index 36e11217..866e7540 100644 --- a/launcher/modplatform/helpers/NetworkModAPI.cpp +++ b/launcher/modplatform/helpers/NetworkModAPI.cpp @@ -52,27 +52,27 @@ void NetworkModAPI::getModInfo(ModPlatform::IndexedPack& pack, std::functionstart(); } -void NetworkModAPI::getVersions(CallerType* caller, VersionSearchArgs&& args) const +void NetworkModAPI::getVersions(VersionSearchArgs&& args, std::function callback) const { - auto netJob = new NetJob(QString("%1::ModVersions(%2)").arg(caller->debugName()).arg(args.addonId), APPLICATION->network()); + auto netJob = new NetJob(QString("ModVersions(%2)").arg(args.addonId), APPLICATION->network()); auto response = new QByteArray(); netJob->addNetAction(Net::Download::makeByteArray(getVersionsURL(args), response)); - QObject::connect(netJob, &NetJob::succeeded, caller, [response, caller, args] { + QObject::connect(netJob, &NetJob::succeeded, [response, callback, args] { QJsonParseError parse_error{}; QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); if (parse_error.error != QJsonParseError::NoError) { - qWarning() << "Error while parsing JSON response from " << caller->debugName() << " at " << parse_error.offset + qWarning() << "Error while parsing JSON response for getting versions at " << parse_error.offset << " reason: " << parse_error.errorString(); qWarning() << *response; return; } - caller->versionRequestSucceeded(doc, args.addonId); + callback(doc, args.addonId); }); - QObject::connect(netJob, &NetJob::finished, caller, [response, netJob] { + QObject::connect(netJob, &NetJob::finished, [response, netJob] { netJob->deleteLater(); delete response; }); diff --git a/launcher/modplatform/helpers/NetworkModAPI.h b/launcher/modplatform/helpers/NetworkModAPI.h index 364fbb8a..b8af22c7 100644 --- a/launcher/modplatform/helpers/NetworkModAPI.h +++ b/launcher/modplatform/helpers/NetworkModAPI.h @@ -6,7 +6,7 @@ class NetworkModAPI : public ModAPI { public: void searchMods(CallerType* caller, SearchArgs&& args) const override; void getModInfo(ModPlatform::IndexedPack& pack, std::function callback) override; - void getVersions(CallerType* caller, VersionSearchArgs&& args) const override; + void getVersions(VersionSearchArgs&& args, std::function callback) const override; auto getProject(QString addonId, QByteArray* response) const -> NetJob* override; diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index 13cfc9cb..f3e1e6ae 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -100,7 +100,8 @@ void ListModel::requestModVersions(ModPlatform::IndexedPack const& current) { auto profile = (dynamic_cast((dynamic_cast(parent()))->m_instance))->getPackProfile(); - m_parent->apiProvider()->getVersions(this, { current.addonId.toString(), getMineVersions(), profile->getModLoaders() }); + m_parent->apiProvider()->getVersions({ current.addonId.toString(), getMineVersions(), profile->getModLoaders() }, + [this, current](QJsonDocument& doc, QString addonId) { versionRequestSucceeded(doc, addonId); }); } void ListModel::performPaginatedSearch() From 0808a10b7b9b9cde808a62b7d617b5c7010c24cf Mon Sep 17 00:00:00 2001 From: flow Date: Mon, 18 Jul 2022 19:28:46 -0300 Subject: [PATCH 059/273] feat: cache mod versions Signed-off-by: flow --- launcher/ui/pages/modplatform/ModModel.cpp | 14 +++++++++++--- launcher/ui/pages/modplatform/ModModel.h | 4 ++-- launcher/ui/pages/modplatform/ModPage.cpp | 2 +- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index f3e1e6ae..9d7a9970 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -96,12 +96,12 @@ bool ListModel::setData(const QModelIndex &index, const QVariant &value, int rol return true; } -void ListModel::requestModVersions(ModPlatform::IndexedPack const& current) +void ListModel::requestModVersions(ModPlatform::IndexedPack const& current, QModelIndex index) { auto profile = (dynamic_cast((dynamic_cast(parent()))->m_instance))->getPackProfile(); m_parent->apiProvider()->getVersions({ current.addonId.toString(), getMineVersions(), profile->getModLoaders() }, - [this, current](QJsonDocument& doc, QString addonId) { versionRequestSucceeded(doc, addonId); }); + [this, current, index](QJsonDocument& doc, QString addonId) { versionRequestSucceeded(doc, addonId, index); }); } void ListModel::performPaginatedSearch() @@ -293,7 +293,7 @@ void ListModel::infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack m_parent->updateUi(); } -void ListModel::versionRequestSucceeded(QJsonDocument doc, QString addonId) +void ListModel::versionRequestSucceeded(QJsonDocument doc, QString addonId, const QModelIndex& index) { auto& current = m_parent->getCurrent(); if (addonId != current.addonId) { @@ -309,6 +309,14 @@ void ListModel::versionRequestSucceeded(QJsonDocument doc, QString addonId) qWarning() << "Error while reading " << debugName() << " mod version: " << e.cause(); } + // Cache info :^) + QVariant new_pack; + new_pack.setValue(current); + if (!setData(index, new_pack, Qt::UserRole)) { + qWarning() << "Failed to cache mod versions!"; + } + + m_parent->updateModVersions(); } diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h index 33b327d0..d338b8c0 100644 --- a/launcher/ui/pages/modplatform/ModModel.h +++ b/launcher/ui/pages/modplatform/ModModel.h @@ -38,7 +38,7 @@ class ListModel : public QAbstractListModel { void refresh(); void searchWithTerm(const QString& term, const int sort, const bool filter_changed); void requestModInfo(ModPlatform::IndexedPack& current, QModelIndex index); - void requestModVersions(const ModPlatform::IndexedPack& current); + 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) {}; @@ -54,7 +54,7 @@ class ListModel : public QAbstractListModel { void infoRequestFinished(QJsonDocument& doc, ModPlatform::IndexedPack& pack, const QModelIndex& index); - void versionRequestSucceeded(QJsonDocument doc, QString addonId); + void versionRequestSucceeded(QJsonDocument doc, QString addonId, const QModelIndex& index); protected slots: diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 59831dd9..014ba5af 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -183,7 +183,7 @@ void ModPage::onSelectionChanged(QModelIndex curr, QModelIndex prev) ui->modSelectionButton->setText(tr("Loading versions...")); ui->modSelectionButton->setEnabled(false); - listModel->requestModVersions(current); + listModel->requestModVersions(current, curr); } else { for (int i = 0; i < current.versions.size(); i++) { ui->versionSelectionBox->addItem(current.versions[i].version, QVariant(i)); From 368a0ddd4489705ac4823d8d9dbb2409fa1d0fa4 Mon Sep 17 00:00:00 2001 From: flow Date: Tue, 19 Jul 2022 11:50:38 -0300 Subject: [PATCH 060/273] feat: add mod descriptions to CF mods Signed-off-by: flow --- launcher/modplatform/flame/FlameAPI.cpp | 37 ++++++++++++++++++++ launcher/modplatform/flame/FlameAPI.h | 1 + launcher/modplatform/flame/FlameModIndex.cpp | 5 +-- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp index 0ff04f72..9c74918b 100644 --- a/launcher/modplatform/flame/FlameAPI.cpp +++ b/launcher/modplatform/flame/FlameAPI.cpp @@ -67,6 +67,43 @@ auto FlameAPI::getModFileChangelog(int modId, int fileId) -> QString return changelog; } +auto FlameAPI::getModDescription(int modId) -> QString +{ + QEventLoop lock; + QString description; + + auto* netJob = new NetJob(QString("Flame::ModDescription"), APPLICATION->network()); + auto* response = new QByteArray(); + netJob->addNetAction(Net::Download::makeByteArray( + QString("https://api.curseforge.com/v1/mods/%1/description") + .arg(QString::number(modId)), response)); + + QObject::connect(netJob, &NetJob::succeeded, [netJob, response, &description] { + QJsonParseError parse_error{}; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from Flame::ModDescription at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *response; + + netJob->failed(parse_error.errorString()); + return; + } + + description = Json::ensureString(doc.object(), "data"); + }); + + QObject::connect(netJob, &NetJob::finished, [response, &lock] { + delete response; + lock.quit(); + }); + + netJob->start(); + lock.exec(); + + return description; +} + auto FlameAPI::getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion { QEventLoop loop; diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 336df387..4eac0664 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -7,6 +7,7 @@ class FlameAPI : public NetworkModAPI { public: auto matchFingerprints(const QList& fingerprints, QByteArray* response) -> NetJob::Ptr; auto getModFileChangelog(int modId, int fileId) -> QString; + auto getModDescription(int modId) -> QString; auto getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion; diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index 746018e2..26187358 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -4,10 +4,9 @@ #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" #include "modplatform/flame/FlameAPI.h" -#include "net/NetJob.h" -static ModPlatform::ProviderCapabilities ProviderCaps; static FlameAPI api; +static ModPlatform::ProviderCapabilities ProviderCaps; void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) { @@ -50,6 +49,8 @@ void FlameMod::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob if(pack.extraData.wikiUrl.endsWith('/')) pack.extraData.wikiUrl.chop(1); + pack.extraData.body = api.getModDescription(pack.addonId.toInt()); + pack.extraDataLoaded = true; } From 6aaf1f4f213bb258bc565d4085da13158abf4520 Mon Sep 17 00:00:00 2001 From: flow Date: Tue, 19 Jul 2022 12:29:31 -0300 Subject: [PATCH 061/273] feat: lazy-load CF mod descriptions Signed-off-by: flow --- launcher/modplatform/flame/FlameModIndex.cpp | 14 +++++++++++--- launcher/modplatform/flame/FlameModIndex.h | 3 ++- .../ui/pages/modplatform/flame/FlameModModel.cpp | 6 ++++++ .../ui/pages/modplatform/flame/FlameModModel.h | 1 + 4 files changed, 20 insertions(+), 4 deletions(-) diff --git a/launcher/modplatform/flame/FlameModIndex.cpp b/launcher/modplatform/flame/FlameModIndex.cpp index 26187358..32aa4bdb 100644 --- a/launcher/modplatform/flame/FlameModIndex.cpp +++ b/launcher/modplatform/flame/FlameModIndex.cpp @@ -30,10 +30,11 @@ void FlameMod::loadIndexedPack(ModPlatform::IndexedPack& pack, QJsonObject& obj) pack.authors.append(packAuthor); } - loadExtraPackData(pack, obj); + pack.extraDataLoaded = false; + loadURLs(pack, obj); } -void FlameMod::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& obj) +void FlameMod::loadURLs(ModPlatform::IndexedPack& pack, QJsonObject& obj) { auto links_obj = Json::ensureObject(obj, "links"); @@ -49,9 +50,16 @@ void FlameMod::loadExtraPackData(ModPlatform::IndexedPack& pack, QJsonObject& ob if(pack.extraData.wikiUrl.endsWith('/')) pack.extraData.wikiUrl.chop(1); + if (!pack.extraData.body.isEmpty()) + pack.extraDataLoaded = true; +} + +void FlameMod::loadBody(ModPlatform::IndexedPack& pack, QJsonObject& obj) +{ pack.extraData.body = api.getModDescription(pack.addonId.toInt()); - pack.extraDataLoaded = true; + if (!pack.extraData.issuesUrl.isEmpty() || !pack.extraData.sourceUrl.isEmpty() || !pack.extraData.wikiUrl.isEmpty()) + pack.extraDataLoaded = true; } static QString enumToString(int hash_algorithm) diff --git a/launcher/modplatform/flame/FlameModIndex.h b/launcher/modplatform/flame/FlameModIndex.h index a839dd83..db63cdbb 100644 --- a/launcher/modplatform/flame/FlameModIndex.h +++ b/launcher/modplatform/flame/FlameModIndex.h @@ -12,7 +12,8 @@ namespace FlameMod { void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj); -void loadExtraPackData(ModPlatform::IndexedPack& m, QJsonObject& obj); +void loadURLs(ModPlatform::IndexedPack& m, QJsonObject& obj); +void loadBody(ModPlatform::IndexedPack& m, QJsonObject& obj); void loadIndexedPackVersions(ModPlatform::IndexedPack& pack, QJsonArray& arr, const shared_qobject_ptr& network, diff --git a/launcher/ui/pages/modplatform/flame/FlameModModel.cpp b/launcher/ui/pages/modplatform/flame/FlameModModel.cpp index 8de2e545..bc2c686c 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModModel.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModModel.cpp @@ -12,6 +12,12 @@ void ListModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) FlameMod::loadIndexedPack(m, obj); } +// We already deal with the URLs when initializing the pack, due to the API response's structure +void ListModel::loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + FlameMod::loadBody(m, obj); +} + void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) { FlameMod::loadIndexedPackVersions(m, arr, APPLICATION->network(), m_parent->m_instance); diff --git a/launcher/ui/pages/modplatform/flame/FlameModModel.h b/launcher/ui/pages/modplatform/flame/FlameModModel.h index 707c1bb1..6a6aef2e 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModModel.h +++ b/launcher/ui/pages/modplatform/flame/FlameModModel.h @@ -13,6 +13,7 @@ class ListModel : public ModPlatform::ListModel { private: void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadExtraPackInfo(ModPlatform::IndexedPack& m, QJsonObject& obj) override; void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; From 7a95314e424a0da203c9202c048f0cba0adfc8a6 Mon Sep 17 00:00:00 2001 From: flow Date: Tue, 19 Jul 2022 13:50:02 -0300 Subject: [PATCH 062/273] feat(ui): remember mod download dialog's geometry Makes it consistent with other modal dialogs. Signed-off-by: flow --- launcher/Application.cpp | 2 ++ launcher/ui/dialogs/ModDownloadDialog.cpp | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 2bd91fd7..5066fd0b 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -680,6 +680,8 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_settings->registerSetting("UpdateDialogGeometry", ""); + m_settings->registerSetting("ModDownloadGeometry", ""); + // HACK: This code feels so stupid is there a less stupid way of doing this? { m_settings->registerSetting("PastebinURL", ""); diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp index af6704d9..05f76fbb 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.cpp +++ b/launcher/ui/dialogs/ModDownloadDialog.cpp @@ -81,6 +81,8 @@ ModDownloadDialog::ModDownloadDialog(const std::shared_ptr& mods QMetaObject::connectSlotsByName(this); setWindowModality(Qt::WindowModal); setWindowTitle(dialogTitle()); + + restoreGeometry(QByteArray::fromBase64(APPLICATION->settings()->get("ModDownloadGeometry").toByteArray())); } QString ModDownloadDialog::dialogTitle() @@ -90,6 +92,7 @@ QString ModDownloadDialog::dialogTitle() void ModDownloadDialog::reject() { + APPLICATION->settings()->set("ModDownloadGeometry", saveGeometry().toBase64()); QDialog::reject(); } @@ -116,6 +119,7 @@ void ModDownloadDialog::confirm() void ModDownloadDialog::accept() { + APPLICATION->settings()->set("ModDownloadGeometry", saveGeometry().toBase64()); QDialog::accept(); } From cee41b87f728e79d7e1ec0e67c24dc23a5dde9d0 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 31 Jul 2022 16:01:47 -0300 Subject: [PATCH 063/273] fix(ui): force redraw of mod list when (de)selecting a mod Signed-off-by: flow --- launcher/ui/pages/modplatform/ModPage.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 014ba5af..a34a74db 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -226,6 +226,9 @@ void ModPage::onModSelected() } updateSelectionButton(); + + /* Force redraw on the mods list when the selection changes */ + ui->packView->adjustSize(); } From a8dfe98b1a0a887175579e2a57dd8a00837de9fa Mon Sep 17 00:00:00 2001 From: Gingeh <39150378+Gingeh@users.noreply.github.com> Date: Mon, 1 Aug 2022 20:18:48 +1000 Subject: [PATCH 064/273] Disable "Check for Updates" if all mods are removed Signed-off-by: Gingeh <39150378+Gingeh@users.noreply.github.com> --- launcher/ui/pages/instance/ModFolderPage.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 14e1f1e5..ae96ec2f 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -89,6 +89,9 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr connect(mods.get(), &ModFolderModel::rowsInserted, this, [this] { ui->actionUpdateItem->setEnabled(ui->treeView->selectionModel()->hasSelection() || !m_model->empty()); }); + + connect(mods.get(), &ModFolderModel::rowsRemoved, this, + [this] { ui->actionUpdateItem->setEnabled(ui->treeView->selectionModel()->hasSelection() || !m_model->empty()); }); connect(mods.get(), &ModFolderModel::updateFinished, this, [this, mods] { ui->actionUpdateItem->setEnabled(ui->treeView->selectionModel()->hasSelection() || !m_model->empty()); From 77b640b76b5ca4948763e3c260ac1c832c72b0f2 Mon Sep 17 00:00:00 2001 From: Gingeh <39150378+Gingeh@users.noreply.github.com> Date: Mon, 1 Aug 2022 20:19:20 +1000 Subject: [PATCH 065/273] Disable "Check for Updates" and "Download Mods" while the game is running Signed-off-by: Gingeh <39150378+Gingeh@users.noreply.github.com> --- launcher/ui/pages/instance/ExternalResourcesPage.h | 2 +- launcher/ui/pages/instance/ModFolderPage.cpp | 7 +++++++ launcher/ui/pages/instance/ModFolderPage.h | 1 + 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.h b/launcher/ui/pages/instance/ExternalResourcesPage.h index 41237139..ff294678 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.h +++ b/launcher/ui/pages/instance/ExternalResourcesPage.h @@ -46,7 +46,7 @@ class ExternalResourcesPage : public QMainWindow, public BasePage { protected slots: void itemActivated(const QModelIndex& index); void filterTextChanged(const QString& newContents); - void runningStateChanged(bool running); + virtual void runningStateChanged(bool running); virtual void addItem(); virtual void removeItem(); diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index ae96ec2f..0db6a0ca 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -106,6 +106,13 @@ CoreModFolderPage::CoreModFolderPage(BaseInstance* inst, std::shared_ptractionDownloadItem->setEnabled(!running); + ui->actionUpdateItem->setEnabled(!running); +} + bool ModFolderPage::shouldDisplay() const { return true; diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index 0a7fc9fa..522d2a60 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -53,6 +53,7 @@ class ModFolderPage : public ExternalResourcesPage { virtual QString helpPage() const override { return "Loader-mods"; } virtual bool shouldDisplay() const override; + virtual void runningStateChanged(bool running); private slots: void installMods(); From 9c9528838a514b26243d69b81018afc6fb5cfe91 Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Mon, 1 Aug 2022 14:40:55 +0200 Subject: [PATCH 066/273] chore: update issue template to ask about Qt version Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> --- .github/ISSUE_TEMPLATE/bug_report.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index bac73932..3f00609e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -27,7 +27,14 @@ body: attributes: label: Version of PolyMC description: The version of PolyMC used in the bug report. - placeholder: PolyMC 1.3.2 + placeholder: PolyMC 1.4.1 + validations: + required: true +- type: textarea + attributes: + label: Version of Qt + description: The version of Qt used in the bug report. You can find it in Help- About PolyMC- About Qt. + placeholder: Qt 6.3.0 validations: required: true - type: textarea From 9d78b2d259288c3cc863ccf698c0ecc00b273ced Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Mon, 1 Aug 2022 14:50:10 +0200 Subject: [PATCH 067/273] Update .github/ISSUE_TEMPLATE/bug_report.yml Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Co-authored-by: flow --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 3f00609e..ab3c8a29 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -33,7 +33,7 @@ body: - type: textarea attributes: label: Version of Qt - description: The version of Qt used in the bug report. You can find it in Help- About PolyMC- About Qt. + description: The version of Qt used in the bug report. You can find it in Help -> About PolyMC -> About Qt. placeholder: Qt 6.3.0 validations: required: true From abd090bd48a3cbf50091e629c0174f8802f4eedf Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Mon, 1 Aug 2022 20:21:19 +0200 Subject: [PATCH 068/273] fix: remove iconfix from libraries/README.MD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit someone forgor (💀) to remove it Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> --- libraries/README.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/libraries/README.md b/libraries/README.md index 37f53385..f225ade8 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -22,11 +22,6 @@ Hoedown is a revived fork of Sundown, the Markdown parser based on the original See [github repo](https://github.com/hoedown/hoedown). -## iconfix -This was originally part of the razor-qt project and the Qt toolkit, respecitvely. Its sole purpose is to reimplement Qt's icon loading logic to prevent it from using any platform plugins that could break icon loading. - -Licensed under LGPL 2.1 - ## javacheck Simple Java tool that prints the JVM details - version and platform bitness. From 4a8abc948ef15664dbde196ba77daccdf3d623ad Mon Sep 17 00:00:00 2001 From: flow Date: Mon, 1 Aug 2022 18:34:15 -0300 Subject: [PATCH 069/273] fix: prevent segfault due to callbacks into deleted objects Since network requests are, for the most part, asynchronous, there's a chance a request only comes through after the request sender has already been deleted. This adds a global (read static) hash table relating models for the mod downloader to their status (true = alive, false = destroyed). It is a bit of a hack, but I couldn't come up with a better way of doing this. To reproduce the issue before this commit: scroll really quickly through CF mods, to trigger network requests for their versions and description. Then, in the middle of it close the mod downloader. Sometimes this will create a crash. Signed-off-by: flow --- launcher/ui/pages/modplatform/ModModel.cpp | 24 ++++++++++++++++++---- launcher/ui/pages/modplatform/ModModel.h | 2 +- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index 9d7a9970..9cf9ec29 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -13,7 +13,16 @@ namespace ModPlatform { -ListModel::ListModel(ModPage* parent) : QAbstractListModel(parent), m_parent(parent) {} +// HACK: We need this to prevent callbacks from calling the ListModel after it has already been deleted. +// This leaks a tiny bit of memory per time the user has opened the mod dialog. How to make this better? +static QHash s_running; + +ListModel::ListModel(ModPage* parent) : QAbstractListModel(parent), m_parent(parent) { s_running.insert(this, true); } + +ListModel::~ListModel() +{ + s_running.find(this).value() = false; +} auto ListModel::debugName() const -> QString { @@ -101,7 +110,11 @@ void ListModel::requestModVersions(ModPlatform::IndexedPack const& current, QMod auto profile = (dynamic_cast((dynamic_cast(parent()))->m_instance))->getPackProfile(); m_parent->apiProvider()->getVersions({ current.addonId.toString(), getMineVersions(), profile->getModLoaders() }, - [this, current, index](QJsonDocument& doc, QString addonId) { versionRequestSucceeded(doc, addonId, index); }); + [this, current, index](QJsonDocument& doc, QString addonId) { + if (!s_running.constFind(this).value()) + return; + versionRequestSucceeded(doc, addonId, index); + }); } void ListModel::performPaginatedSearch() @@ -114,8 +127,11 @@ void ListModel::performPaginatedSearch() void ListModel::requestModInfo(ModPlatform::IndexedPack& current, QModelIndex index) { - m_parent->apiProvider()->getModInfo( - current, [this, index](QJsonDocument& doc, ModPlatform::IndexedPack& pack) { infoRequestFinished(doc, pack, index); }); + m_parent->apiProvider()->getModInfo(current, [this, index](QJsonDocument& doc, ModPlatform::IndexedPack& pack) { + if (!s_running.constFind(this).value()) + return; + infoRequestFinished(doc, pack, index); + }); } void ListModel::refresh() diff --git a/launcher/ui/pages/modplatform/ModModel.h b/launcher/ui/pages/modplatform/ModModel.h index d338b8c0..a58c7c55 100644 --- a/launcher/ui/pages/modplatform/ModModel.h +++ b/launcher/ui/pages/modplatform/ModModel.h @@ -18,7 +18,7 @@ class ListModel : public QAbstractListModel { public: ListModel(ModPage* parent); - ~ListModel() override = default; + ~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; }; From 0d10ebb7caf7bb8292198af1933120c2c6e02cb8 Mon Sep 17 00:00:00 2001 From: Gingeh <39150378+Gingeh@users.noreply.github.com> Date: Tue, 2 Aug 2022 12:48:32 +1000 Subject: [PATCH 070/273] Update launcher/ui/pages/instance/ModFolderPage.h Co-authored-by: flow Signed-off-by: Gingeh <39150378+Gingeh@users.noreply.github.com> --- launcher/ui/pages/instance/ModFolderPage.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index 522d2a60..93889707 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -53,7 +53,7 @@ class ModFolderPage : public ExternalResourcesPage { virtual QString helpPage() const override { return "Loader-mods"; } virtual bool shouldDisplay() const override; - virtual void runningStateChanged(bool running); + void runningStateChanged(bool running) override; private slots: void installMods(); From f33b31e048affecf76083fbc56d02e883d0fffad Mon Sep 17 00:00:00 2001 From: Gingeh <39150378+Gingeh@users.noreply.github.com> Date: Tue, 2 Aug 2022 14:14:45 +1000 Subject: [PATCH 071/273] Check for running instance when re-opening the mod folder page and when selecting mods Signed-off-by: Gingeh <39150378+Gingeh@users.noreply.github.com> --- .../pages/instance/ExternalResourcesPage.cpp | 2 +- launcher/ui/pages/instance/ModFolderPage.cpp | 25 +++++++++++++------ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp index 69c20309..39fbe3e2 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.cpp +++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp @@ -101,7 +101,7 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared { ui->setupUi(this); - runningStateChanged(m_instance && m_instance->isRunning()); + ExternalResourcesPage::runningStateChanged(m_instance && m_instance->isRunning()); ui->actionsToolbar->insertSpacer(ui->actionViewConfigs); diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 0db6a0ca..b3f4759f 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -84,21 +84,30 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr ui->actionsToolbar->insertActionAfter(ui->actionAddItem, ui->actionUpdateItem); connect(ui->actionUpdateItem, &QAction::triggered, this, &ModFolderPage::updateMods); - connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, - [this] { ui->actionUpdateItem->setEnabled(ui->treeView->selectionModel()->hasSelection() || !m_model->empty()); }); + connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this] { + ui->actionUpdateItem->setEnabled(!(m_instance && m_instance->isRunning()) && + (ui->treeView->selectionModel()->hasSelection() || !m_model->empty())); + }); - connect(mods.get(), &ModFolderModel::rowsInserted, this, - [this] { ui->actionUpdateItem->setEnabled(ui->treeView->selectionModel()->hasSelection() || !m_model->empty()); }); + connect(mods.get(), &ModFolderModel::rowsInserted, this, [this] { + ui->actionUpdateItem->setEnabled(!(m_instance && m_instance->isRunning()) && + (ui->treeView->selectionModel()->hasSelection() || !m_model->empty())); + }); + + connect(mods.get(), &ModFolderModel::rowsRemoved, this, [this] { + ui->actionUpdateItem->setEnabled(!(m_instance && m_instance->isRunning()) && + (ui->treeView->selectionModel()->hasSelection() || !m_model->empty())); + }); - connect(mods.get(), &ModFolderModel::rowsRemoved, this, - [this] { ui->actionUpdateItem->setEnabled(ui->treeView->selectionModel()->hasSelection() || !m_model->empty()); }); - connect(mods.get(), &ModFolderModel::updateFinished, this, [this, mods] { - ui->actionUpdateItem->setEnabled(ui->treeView->selectionModel()->hasSelection() || !m_model->empty()); + ui->actionUpdateItem->setEnabled(!(m_instance && m_instance->isRunning()) && + (ui->treeView->selectionModel()->hasSelection() || !m_model->empty())); // Prevent a weird crash when trying to open the mods page twice in a session o.O disconnect(mods.get(), &ModFolderModel::updateFinished, this, 0); }); + + ModFolderPage::runningStateChanged(m_instance && m_instance->isRunning()); } } From bca1fef2b25587a30c05502cf0231d8d337487f4 Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Tue, 2 Aug 2022 10:19:47 +0200 Subject: [PATCH 072/273] chore: downgrade to Qt 6.3.0 on macos seems to fix some emoji-related issues Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0599c1d9..53c8cd7d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,7 +43,7 @@ jobs: macosx_deployment_target: 10.14 qt_ver: 6 qt_host: mac - qt_version: '6.3.1' + qt_version: '6.3.0' qt_modules: 'qt5compat qtimageformats' qt_path: /Users/runner/work/PolyMC/Qt From 31ba1de53b2308cdaabda2fa94a7c1a259a8a078 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 31 Jul 2022 21:56:37 -0300 Subject: [PATCH 073/273] fix: remove orphaned metadata to avoid problems with auto-updating insts Just as my master has taught me. :gun: Signed-off-by: flow --- launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp index a2e055ba..9b70e7a1 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp @@ -83,6 +83,17 @@ void ModFolderLoadTask::run() } } + // Remove orphan metadata to prevent issues + // See https://github.com/PolyMC/PolyMC/issues/996 + QMutableMapIterator iter(m_result->mods); + while (iter.hasNext()) { + auto mod = iter.next().value(); + if (mod->status() == ModStatus::NotInstalled) { + mod->destroy(m_index_dir, false); + iter.remove(); + } + } + emit succeeded(); } From a8aa86291965ddd3cdfa2bb5f5fc7a34170559ed Mon Sep 17 00:00:00 2001 From: Gingeh <39150378+Gingeh@users.noreply.github.com> Date: Wed, 3 Aug 2022 16:39:30 +1000 Subject: [PATCH 074/273] Move large condition into a new lambda Signed-off-by: Gingeh <39150378+Gingeh@users.noreply.github.com> --- launcher/ui/pages/instance/ModFolderPage.cpp | 25 ++++++++++---------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index b3f4759f..1b2cde0c 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -84,24 +84,25 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr ui->actionsToolbar->insertActionAfter(ui->actionAddItem, ui->actionUpdateItem); connect(ui->actionUpdateItem, &QAction::triggered, this, &ModFolderPage::updateMods); - connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this] { - ui->actionUpdateItem->setEnabled(!(m_instance && m_instance->isRunning()) && - (ui->treeView->selectionModel()->hasSelection() || !m_model->empty())); + auto check_allow_update = [this] { + return (!m_instance || !m_instance->isRunning()) && + (ui->treeView->selectionModel()->hasSelection() || !m_model->empty()); + }; + + connect(ui->treeView->selectionModel(), &QItemSelectionModel::selectionChanged, this, [this, check_allow_update] { + ui->actionUpdateItem->setEnabled(check_allow_update()); }); - connect(mods.get(), &ModFolderModel::rowsInserted, this, [this] { - ui->actionUpdateItem->setEnabled(!(m_instance && m_instance->isRunning()) && - (ui->treeView->selectionModel()->hasSelection() || !m_model->empty())); + connect(mods.get(), &ModFolderModel::rowsInserted, this, [this, check_allow_update] { + ui->actionUpdateItem->setEnabled(check_allow_update()); }); - connect(mods.get(), &ModFolderModel::rowsRemoved, this, [this] { - ui->actionUpdateItem->setEnabled(!(m_instance && m_instance->isRunning()) && - (ui->treeView->selectionModel()->hasSelection() || !m_model->empty())); + connect(mods.get(), &ModFolderModel::rowsRemoved, this, [this, check_allow_update] { + ui->actionUpdateItem->setEnabled(check_allow_update()); }); - connect(mods.get(), &ModFolderModel::updateFinished, this, [this, mods] { - ui->actionUpdateItem->setEnabled(!(m_instance && m_instance->isRunning()) && - (ui->treeView->selectionModel()->hasSelection() || !m_model->empty())); + connect(mods.get(), &ModFolderModel::updateFinished, this, [this, check_allow_update, mods] { + ui->actionUpdateItem->setEnabled(check_allow_update()); // Prevent a weird crash when trying to open the mods page twice in a session o.O disconnect(mods.get(), &ModFolderModel::updateFinished, this, 0); From 4ed296bad493f2cefbde275fea79a91a1849e886 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Wed, 3 Aug 2022 20:37:10 +0200 Subject: [PATCH 075/273] fix: allow user to interrupt launch after 3 tries Signed-off-by: Sefa Eyeoglu --- launcher/LaunchController.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index d36ee3fe..38df1b04 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -145,16 +145,25 @@ void LaunchController::login() { return; } - // we try empty password first :) - QString password; // we loop until the user succeeds in logging in or gives up bool tryagain = true; - // the failure. the default failure. - const QString needLoginAgain = tr("Your account is currently not logged in. Please enter your password to log in again.

This could be caused by a password change."); - QString failReason = needLoginAgain; + unsigned int tries = 0; while (tryagain) { + if (tries > 0 && tries % 3 == 0) { + auto result = QMessageBox::question( + m_parentWidget, + tr("Continue launch?"), + tr("It looks like we couldn't launch after %1 tries. Do you want to continue trying?") + .arg(tries) + ); + + if (result == QMessageBox::No) { + return; + } + } + tries++; m_session = std::make_shared(); m_session->wants_online = m_online; m_accountToUse->fillSession(m_session); From be4fb65470d2a005560e069f7e0969e974767b00 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Wed, 3 Aug 2022 21:14:23 +0200 Subject: [PATCH 076/273] fix: Add root path detection on OpenBSD Signed-off-by: Sefa Eyeoglu --- launcher/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 2bd91fd7..97d41993 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -321,7 +321,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) { // Root path is used for updates and portable data -#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) +#if defined(Q_OS_LINUX) || defined(Q_OS_FREEBSD) || defined(Q_OS_OPENBSD) QDir foo(FS::PathCombine(binPath, "..")); // typically portable-root or /usr m_rootPath = foo.absolutePath(); #elif defined(Q_OS_WIN32) From 355762aa303361819340cc2f8b92ada326a7e03d Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Thu, 4 Aug 2022 10:02:54 +0200 Subject: [PATCH 077/273] fix: emit abort in LaunchController Signed-off-by: Sefa Eyeoglu --- launcher/Application.cpp | 3 +++ launcher/LaunchController.cpp | 1 + 2 files changed, 4 insertions(+) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 2bd91fd7..c4bb84e4 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -1258,6 +1258,9 @@ bool Application::launch( } connect(controller.get(), &LaunchController::succeeded, this, &Application::controllerSucceeded); connect(controller.get(), &LaunchController::failed, this, &Application::controllerFailed); + connect(controller.get(), &LaunchController::aborted, this, [this] { + controllerFailed(tr("Aborted")); + }); addRunningInstance(); controller->start(); return true; diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 38df1b04..11f9b2bb 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -160,6 +160,7 @@ void LaunchController::login() { ); if (result == QMessageBox::No) { + emitAborted(); return; } } From 362ecdb583293e09b55b95d270a72eae84510935 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 4 Aug 2022 13:58:30 -0300 Subject: [PATCH 078/273] refactor+fix: use QSharedPointer for shared_qobject_ptr Signed-off-by: flow --- launcher/QObjectPtr.h | 97 ++++++-------------- launcher/minecraft/MinecraftUpdate.cpp | 10 +- launcher/minecraft/MinecraftUpdate.h | 2 +- launcher/minecraft/auth/MinecraftAccount.cpp | 2 +- launcher/ui/dialogs/LoginDialog.cpp | 2 +- launcher/ui/dialogs/MSALoginDialog.cpp | 2 +- launcher/ui/dialogs/OfflineLoginDialog.cpp | 2 +- 7 files changed, 37 insertions(+), 80 deletions(-) diff --git a/launcher/QObjectPtr.h b/launcher/QObjectPtr.h index 173dc5e7..7c453adb 100644 --- a/launcher/QObjectPtr.h +++ b/launcher/QObjectPtr.h @@ -1,91 +1,48 @@ #pragma once +#include +#include #include #include -#include -namespace details +namespace details { +[[maybe_unused]] static void do_delete_later(QObject* obj) { -struct DeleteQObjectLater -{ - void operator()(QObject *obj) const - { + if (obj) obj->deleteLater(); - } -}; } +struct DeleteQObjectLater { + void operator()(QObject* obj) const { do_delete_later(obj); } +}; + +} // namespace details + /** * A unique pointer class with unique pointer semantics intended for derivates of QObject * Calls deleteLater() instead of destroying the contained object immediately */ -template using unique_qobject_ptr = std::unique_ptr; +template +using unique_qobject_ptr = std::unique_ptr; /** * A shared pointer class with shared pointer semantics intended for derivates of QObject * Calls deleteLater() instead of destroying the contained object immediately */ template -class shared_qobject_ptr -{ -public: - shared_qobject_ptr(){} - shared_qobject_ptr(T * wrap) - { - reset(wrap); - } - shared_qobject_ptr(const shared_qobject_ptr& other) - { - m_ptr = other.m_ptr; - } - template - shared_qobject_ptr(const shared_qobject_ptr &other) - { - m_ptr = other.unwrap(); - } +class shared_qobject_ptr : public QSharedPointer { + public: + constexpr shared_qobject_ptr() : QSharedPointer() {} + constexpr shared_qobject_ptr(T* ptr) : QSharedPointer(ptr, details::do_delete_later) {} + constexpr shared_qobject_ptr(std::nullptr_t null_ptr) : QSharedPointer(null_ptr, details::do_delete_later) {} -public: - void reset(T * wrap) - { - using namespace std::placeholders; - m_ptr.reset(wrap, std::bind(&QObject::deleteLater, _1)); - } - void reset(const shared_qobject_ptr &other) - { - m_ptr = other.m_ptr; - } - void reset() - { - m_ptr.reset(); - } - T * get() const - { - return m_ptr.get(); - } - T * operator->() const - { - return m_ptr.get(); - } - T & operator*() const - { - return *m_ptr.get(); - } - operator bool() const - { - return m_ptr.get() != nullptr; - } - const std::shared_ptr unwrap() const - { - return m_ptr; - } - template - bool operator==(const shared_qobject_ptr& other) const { - return m_ptr == other.m_ptr; - } - template - bool operator!=(const shared_qobject_ptr& other) const { - return m_ptr != other.m_ptr; - } + template + constexpr shared_qobject_ptr(const shared_qobject_ptr& other) : QSharedPointer(other) + {} -private: - std::shared_ptr m_ptr; + void reset() { QSharedPointer::reset(); } + void reset(const shared_qobject_ptr& other) + { + shared_qobject_ptr t(other); + this->swap(t); + } }; diff --git a/launcher/minecraft/MinecraftUpdate.cpp b/launcher/minecraft/MinecraftUpdate.cpp index 0ce0c347..3a3aa864 100644 --- a/launcher/minecraft/MinecraftUpdate.cpp +++ b/launcher/minecraft/MinecraftUpdate.cpp @@ -43,7 +43,7 @@ void MinecraftUpdate::executeTask() m_tasks.clear(); // create folders { - m_tasks.append(std::make_shared(m_inst)); + m_tasks.append(new FoldersTask(m_inst)); } // add metadata update task if necessary @@ -53,23 +53,23 @@ void MinecraftUpdate::executeTask() auto task = components->getCurrentTask(); if(task) { - m_tasks.append(task.unwrap()); + m_tasks.append(task); } } // libraries download { - m_tasks.append(std::make_shared(m_inst)); + m_tasks.append(new LibrariesTask(m_inst)); } // FML libraries download and copy into the instance { - m_tasks.append(std::make_shared(m_inst)); + m_tasks.append(new FMLLibrariesTask(m_inst)); } // assets update { - m_tasks.append(std::make_shared(m_inst)); + m_tasks.append(new AssetUpdateTask(m_inst)); } if(!m_preFailure.isEmpty()) diff --git a/launcher/minecraft/MinecraftUpdate.h b/launcher/minecraft/MinecraftUpdate.h index acf2eb86..c9cf8624 100644 --- a/launcher/minecraft/MinecraftUpdate.h +++ b/launcher/minecraft/MinecraftUpdate.h @@ -50,7 +50,7 @@ private: private: MinecraftInstance *m_inst = nullptr; - QList> m_tasks; + QList m_tasks; QString m_preFailure; int m_currentTask = -1; bool m_abort = false; diff --git a/launcher/minecraft/auth/MinecraftAccount.cpp b/launcher/minecraft/auth/MinecraftAccount.cpp index a5c6f542..73d570f1 100644 --- a/launcher/minecraft/auth/MinecraftAccount.cpp +++ b/launcher/minecraft/auth/MinecraftAccount.cpp @@ -238,7 +238,7 @@ void MinecraftAccount::authFailed(QString reason) } bool MinecraftAccount::isActive() const { - return m_currentTask; + return !m_currentTask.isNull(); } bool MinecraftAccount::shouldRefresh() const { diff --git a/launcher/ui/dialogs/LoginDialog.cpp b/launcher/ui/dialogs/LoginDialog.cpp index 194315a7..30394b72 100644 --- a/launcher/ui/dialogs/LoginDialog.cpp +++ b/launcher/ui/dialogs/LoginDialog.cpp @@ -115,5 +115,5 @@ MinecraftAccountPtr LoginDialog::newAccount(QWidget *parent, QString msg) { return dlg.m_account; } - return 0; + return nullptr; } diff --git a/launcher/ui/dialogs/MSALoginDialog.cpp b/launcher/ui/dialogs/MSALoginDialog.cpp index b11b6980..be49babb 100644 --- a/launcher/ui/dialogs/MSALoginDialog.cpp +++ b/launcher/ui/dialogs/MSALoginDialog.cpp @@ -169,5 +169,5 @@ MinecraftAccountPtr MSALoginDialog::newAccount(QWidget *parent, QString msg) { return dlg.m_account; } - return 0; + return nullptr; } diff --git a/launcher/ui/dialogs/OfflineLoginDialog.cpp b/launcher/ui/dialogs/OfflineLoginDialog.cpp index 4f3d8be4..a69537ab 100644 --- a/launcher/ui/dialogs/OfflineLoginDialog.cpp +++ b/launcher/ui/dialogs/OfflineLoginDialog.cpp @@ -103,5 +103,5 @@ MinecraftAccountPtr OfflineLoginDialog::newAccount(QWidget *parent, QString msg) { return dlg.m_account; } - return 0; + return nullptr; } From d835e1d14e426ea73a66a0f8e225898598581f4a Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 4 Aug 2022 17:09:32 -0300 Subject: [PATCH 079/273] refactor: simplify smart pointers more Signed-off-by: flow --- launcher/QObjectPtr.h | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/launcher/QObjectPtr.h b/launcher/QObjectPtr.h index 7c453adb..b1ef1c8d 100644 --- a/launcher/QObjectPtr.h +++ b/launcher/QObjectPtr.h @@ -2,27 +2,16 @@ #include #include + #include #include -namespace details { -[[maybe_unused]] static void do_delete_later(QObject* obj) -{ - if (obj) - obj->deleteLater(); -} -struct DeleteQObjectLater { - void operator()(QObject* obj) const { do_delete_later(obj); } -}; - -} // namespace details - /** * A unique pointer class with unique pointer semantics intended for derivates of QObject * Calls deleteLater() instead of destroying the contained object immediately */ template -using unique_qobject_ptr = std::unique_ptr; +using unique_qobject_ptr = QScopedPointer; /** * A shared pointer class with shared pointer semantics intended for derivates of QObject @@ -32,8 +21,8 @@ template class shared_qobject_ptr : public QSharedPointer { public: constexpr shared_qobject_ptr() : QSharedPointer() {} - constexpr shared_qobject_ptr(T* ptr) : QSharedPointer(ptr, details::do_delete_later) {} - constexpr shared_qobject_ptr(std::nullptr_t null_ptr) : QSharedPointer(null_ptr, details::do_delete_later) {} + constexpr shared_qobject_ptr(T* ptr) : QSharedPointer(ptr, &QObject::deleteLater) {} + constexpr shared_qobject_ptr(std::nullptr_t null_ptr) : QSharedPointer(null_ptr, &QObject::deleteLater) {} template constexpr shared_qobject_ptr(const shared_qobject_ptr& other) : QSharedPointer(other) From f4b207220c60b9bbc6d4ea414a419768b8913c12 Mon Sep 17 00:00:00 2001 From: flow Date: Tue, 2 Aug 2022 15:38:51 -0300 Subject: [PATCH 080/273] fix: add some more nullptr checks / protection die sigsegv :gun: Signed-off-by: flow --- launcher/modplatform/EnsureMetadataTask.cpp | 44 ++++++++++++++++----- launcher/modplatform/EnsureMetadataTask.h | 4 +- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/launcher/modplatform/EnsureMetadataTask.cpp b/launcher/modplatform/EnsureMetadataTask.cpp index 1f49d7c9..11bdb99d 100644 --- a/launcher/modplatform/EnsureMetadataTask.cpp +++ b/launcher/modplatform/EnsureMetadataTask.cpp @@ -24,8 +24,10 @@ EnsureMetadataTask::EnsureMetadataTask(Mod* mod, QDir dir, ModPlatform::Provider : Task(nullptr), m_index_dir(dir), m_provider(prov), m_hashing_task(nullptr), m_current_task(nullptr) { auto hash_task = createNewHash(mod); + if (!hash_task) + return; connect(hash_task.get(), &Task::succeeded, [this, hash_task, mod] { m_mods.insert(hash_task->getResult(), mod); }); - connect(hash_task.get(), &Task::failed, [this, hash_task, mod] { emitFail(mod, RemoveFromList::No); }); + connect(hash_task.get(), &Task::failed, [this, hash_task, mod] { emitFail(mod, "", RemoveFromList::No); }); hash_task->start(); } @@ -38,14 +40,14 @@ EnsureMetadataTask::EnsureMetadataTask(QList& mods, QDir dir, ModPlatform: if (!hash_task) continue; connect(hash_task.get(), &Task::succeeded, [this, hash_task, mod] { m_mods.insert(hash_task->getResult(), mod); }); - connect(hash_task.get(), &Task::failed, [this, hash_task, mod] { emitFail(mod, RemoveFromList::No); }); + connect(hash_task.get(), &Task::failed, [this, hash_task, mod] { emitFail(mod, "", RemoveFromList::No); }); m_hashing_task->addTask(hash_task); } } Hashing::Hasher::Ptr EnsureMetadataTask::createNewHash(Mod* mod) { - if (!mod->valid() || mod->type() == Mod::MOD_FOLDER) + if (!mod || !mod->valid() || mod->type() == Mod::MOD_FOLDER) return nullptr; return Hashing::createHasher(mod->fileinfo().absoluteFilePath(), m_provider); @@ -120,7 +122,7 @@ void EnsureMetadataTask::executeTask() QMutableHashIterator mods_iter(m_mods); while (mods_iter.hasNext()) { auto mod = mods_iter.next(); - emitFail(mod.value()); + emitFail(mod.value(), mod.key()); } emitSucceeded(); @@ -168,22 +170,44 @@ void EnsureMetadataTask::executeTask() version_task->start(); } -void EnsureMetadataTask::emitReady(Mod* m, RemoveFromList remove) +void EnsureMetadataTask::emitReady(Mod* m, QString key, RemoveFromList remove) { + if (!m) { + qCritical() << "Tried to mark a null mod as ready."; + if (!key.isEmpty()) + m_mods.remove(key); + + return; + } + qDebug() << QString("Generated metadata for %1").arg(m->name()); emit metadataReady(m); - if (remove == RemoveFromList::Yes) - m_mods.remove(getExistingHash(m)); + if (remove == RemoveFromList::Yes) { + if (key.isEmpty()) + key = getExistingHash(m); + m_mods.remove(key); + } } -void EnsureMetadataTask::emitFail(Mod* m, RemoveFromList remove) +void EnsureMetadataTask::emitFail(Mod* m, QString key, RemoveFromList remove) { + if (!m) { + qCritical() << "Tried to mark a null mod as failed."; + if (!key.isEmpty()) + m_mods.remove(key); + + return; + } + qDebug() << QString("Failed to generate metadata for %1").arg(m->name()); emit metadataFailed(m); - if (remove == RemoveFromList::Yes) - m_mods.remove(getExistingHash(m)); + if (remove == RemoveFromList::Yes) { + if (key.isEmpty()) + key = getExistingHash(m); + m_mods.remove(key); + } } // Modrinth diff --git a/launcher/modplatform/EnsureMetadataTask.h b/launcher/modplatform/EnsureMetadataTask.h index 13319266..a8b0851e 100644 --- a/launcher/modplatform/EnsureMetadataTask.h +++ b/launcher/modplatform/EnsureMetadataTask.h @@ -39,8 +39,8 @@ class EnsureMetadataTask : public Task { Yes, No }; - void emitReady(Mod*, RemoveFromList = RemoveFromList::Yes); - void emitFail(Mod*, RemoveFromList = RemoveFromList::Yes); + void emitReady(Mod*, QString key = {}, RemoveFromList = RemoveFromList::Yes); + void emitFail(Mod*, QString key = {}, RemoveFromList = RemoveFromList::Yes); // Hashes and stuff auto createNewHash(Mod*) -> Hashing::Hasher::Ptr; From 7b27f200b1f131f0ea3b23433974cbe68eb979bb Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 5 Aug 2022 16:23:46 -0300 Subject: [PATCH 081/273] fix: don't mutate QHash while iterating over it Even though it was using a QMutableHashIterator, sometimes it didn't work quite well, so this is a bit better. Signed-off-by: flow --- launcher/modplatform/EnsureMetadataTask.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/launcher/modplatform/EnsureMetadataTask.cpp b/launcher/modplatform/EnsureMetadataTask.cpp index 11bdb99d..42137398 100644 --- a/launcher/modplatform/EnsureMetadataTask.cpp +++ b/launcher/modplatform/EnsureMetadataTask.cpp @@ -119,11 +119,9 @@ void EnsureMetadataTask::executeTask() } auto invalidade_leftover = [this] { - QMutableHashIterator mods_iter(m_mods); - while (mods_iter.hasNext()) { - auto mod = mods_iter.next(); - emitFail(mod.value(), mod.key()); - } + for (auto mod = m_mods.constBegin(); mod != m_mods.constEnd(); mod++) + emitFail(mod.value(), mod.key(), RemoveFromList::No); + m_mods.clear(); emitSucceeded(); }; From 8cea57ff0f06e1cc7ec58716e8177e3a0b6e5ee9 Mon Sep 17 00:00:00 2001 From: txtsd Date: Sat, 6 Aug 2022 12:53:50 +0530 Subject: [PATCH 082/273] chore(markdown): MD022 Headings should be surrounded by blank lines Signed-off-by: txtsd --- CODE_OF_CONDUCT.md | 1 + README.md | 1 + libraries/README.md | 8 ++++++++ 3 files changed, 10 insertions(+) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 52a9f30a..18031f9e 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -1,4 +1,5 @@ # Contributor Covenant Code of Conduct + This is a modified version of the Contributor Covenant. See commit history to see our changes. diff --git a/README.md b/README.md index 69639e5b..167e7fc1 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ All launcher code is available under the GPL-3.0-only license. The logo and related assets are under the CC BY-SA 4.0 license. ## Sponsors + Thank you to all our generous backers over at Open Collective! Support PolyMC by [becoming a backer](https://opencollective.com/polymc). [![OpenCollective Backers](https://opencollective.com/polymc/backers.svg?width=890&limit=1000)](https://opencollective.com/polymc#backers) diff --git a/libraries/README.md b/libraries/README.md index f225ade8..2feaea9e 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -3,6 +3,7 @@ This folder has third-party or otherwise external libraries needed for other parts to work. ## classparser + A simplistic parser for Java class files. This library has served as a base for some (much more full-featured and advanced) work under NDA for AVG. It, however, should NOT be confused with that work. @@ -18,16 +19,19 @@ See [github repo](https://github.com/FeralInteractive/gamemode). BSD licensed ## hoedown + Hoedown is a revived fork of Sundown, the Markdown parser based on the original code of the Upskirt library by Natacha Porté. See [github repo](https://github.com/hoedown/hoedown). ## javacheck + Simple Java tool that prints the JVM details - version and platform bitness. Do what you want with it. It is so trivial that noone cares. ## Katabasis + Oauth2 library customized for Microsoft authentication. This is a fork of the [O2 library](https://github.com/pipacs/o2). @@ -35,6 +39,7 @@ This is a fork of the [O2 library](https://github.com/pipacs/o2). MIT licensed. ## launcher + Java launcher part for Minecraft. It: @@ -131,6 +136,7 @@ launcher onesix Available under `GPL-3.0-only` (with classpath exception), sublicensed from its original `Apache-2.0` codebase ## libnbtplusplus + libnbt++ is a free C++ library for Minecraft's file format Named Binary Tag (NBT). It can read and write compressed and uncompressed NBT files and provides a code interface for working with NBT data. See [github repo](https://github.com/ljfa-ag/libnbtplusplus). @@ -138,6 +144,7 @@ See [github repo](https://github.com/ljfa-ag/libnbtplusplus). Available either under LGPL version 3 or later. ## LocalPeer + Library for making only one instance of the application run at all times. BSD licensed, derived from [QtSingleApplication](https://github.com/qtproject/qt-solutions/tree/master/qtsingleapplication). @@ -157,6 +164,7 @@ A zip manipulation library, forked for MultiMC's use. LGPL 2.1 ## rainbow + Color functions extracted from [KGuiAddons](https://inqlude.org/libraries/kguiaddons.html). Used for adaptive text coloring. Available either under LGPL version 2.1 or later. From 358f080c76dbdfc65f32e08448a5e38b78d2bab0 Mon Sep 17 00:00:00 2001 From: txtsd Date: Tue, 12 Jul 2022 20:46:06 +0530 Subject: [PATCH 083/273] chore(markdown): MD012 Multiple consecutive blank lines Signed-off-by: txtsd --- CODE_OF_CONDUCT.md | 1 - CONTRIBUTING.md | 2 -- README.md | 1 - libraries/README.md | 1 - libraries/katabasis/acknowledgements.md | 2 -- libraries/tomlc99/README.md | 2 -- 6 files changed, 9 deletions(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 18031f9e..7bbd01da 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -134,4 +134,3 @@ For answers to common questions about this code of conduct, see the FAQ at [Mozilla CoC]: https://github.com/mozilla/diversity [FAQ]: https://www.contributor-covenant.org/faq [translations]: https://www.contributor-covenant.org/translations - diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 216549c6..7b60daf4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -56,7 +56,5 @@ These terms will be enforced once you create a pull request, and you will be inf As a bonus, you can also [cryptographically sign your commits][gh-signing-commits] and enable [vigilant mode][gh-vigilant-mode] on GitHub. - - [gh-signing-commits]: https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits [gh-vigilant-mode]: https://docs.github.com/en/authentication/managing-commit-signature-verification/displaying-verification-statuses-for-all-of-your-commits diff --git a/README.md b/README.md index 167e7fc1..210bdb00 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,6 @@ If you want to read about why this fork was created, check out [our FAQ page](ht - All downloads and instructions for PolyMC can be found [here](https://polymc.org/download/) - Last build status: https://github.com/PolyMC/PolyMC/actions - ## Development Builds There are per-commit development builds available [here](https://github.com/PolyMC/PolyMC/actions). These have debug information in the binaries, so their file sizes are relatively larger. diff --git a/libraries/README.md b/libraries/README.md index 2feaea9e..90e836e1 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -188,4 +188,3 @@ Licenced under the MIT licence. Tiny implementation of LZMA2 de/compression. This format is only used by Forge to save bandwidth. Public domain. - diff --git a/libraries/katabasis/acknowledgements.md b/libraries/katabasis/acknowledgements.md index c1c8a3d4..21e73e5d 100644 --- a/libraries/katabasis/acknowledgements.md +++ b/libraries/katabasis/acknowledgements.md @@ -26,7 +26,6 @@ > OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE > OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - # SimpleCrypt by Andre Somers Cryptographic methods for Qt. @@ -107,4 +106,3 @@ Bug fixes, support for ```qml``` module # Fabian Vogt Bug fixes, support for building without Qt keywords enabled - diff --git a/libraries/tomlc99/README.md b/libraries/tomlc99/README.md index 6715b5be..d2776448 100644 --- a/libraries/tomlc99/README.md +++ b/libraries/tomlc99/README.md @@ -10,7 +10,6 @@ If you are looking for a C++ library, you might try this wrapper: [https://githu [iarna/toml-spec-tests](https://github.com/iarna/toml-spec-tests). * Provides very simple and intuitive interface. - ## Usage Please see the `toml.h` file for details. What follows is a simple example that @@ -183,7 +182,6 @@ To test against the standard test set provided by BurntSushi/toml-test: % bash run.sh # this will run the test suite ``` - To test against the standard test set provided by iarna/toml: ```sh From fba20e2cfb2bcf10b9987f74a3d454b59b079fa6 Mon Sep 17 00:00:00 2001 From: txtsd Date: Tue, 12 Jul 2022 21:04:57 +0530 Subject: [PATCH 084/273] chore(markdown): MD040 Fenced code blocks should have a language specified Signed-off-by: txtsd --- libraries/tomlc99/README.md | 2 +- nix/NIX.md | 26 +++++++++++++++----------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/libraries/tomlc99/README.md b/libraries/tomlc99/README.md index d2776448..103024f0 100644 --- a/libraries/tomlc99/README.md +++ b/libraries/tomlc99/README.md @@ -150,7 +150,7 @@ call was successful. If so, you may proceed to read the value corresponding to the type of the content. For example: -``` +```c toml_datum_t host = toml_string_in(tab, "host"); if (host.ok) { printf("host: %s\n", host.u.s); diff --git a/nix/NIX.md b/nix/NIX.md index 1ceba9a3..39c67f57 100644 --- a/nix/NIX.md +++ b/nix/NIX.md @@ -2,19 +2,21 @@ To import with flakes use ```nix -inputs = { - polymc.url = "github:PolyMC/PolyMC"; -}; +{ + inputs = { + polymc.url = "github:PolyMC/PolyMC"; + }; ... -nixpkgs.overlays = [ inputs.polymc.overlay ]; ## Within configuration.nix -environment.systemPackages = with pkgs; [ polymc ]; ## + nixpkgs.overlays = [ inputs.polymc.overlay ]; ## Within configuration.nix + environment.systemPackages = with pkgs; [ polymc ]; ## +} ``` To import without flakes use channels: -``` +```sh nix-channel --add https://github.com/PolyMC/PolyMC/archive/master.tar.gz polymc nix-channel --update polymc nix-env -iA polymc @@ -22,10 +24,12 @@ nix-env -iA polymc or alternatively you can use -``` -nixpkgs.overlays = [ - (import (builtins.fetchTarball "https://github.com/PolyMC/PolyMC/archive/develop.tar.gz")).overlay -]; +```nix +{ + nixpkgs.overlays = [ + (import (builtins.fetchTarball "https://github.com/PolyMC/PolyMC/archive/develop.tar.gz")).overlay + ]; -environment.systemPackages = with pkgs; [ polymc ]; + environment.systemPackages = with pkgs; [ polymc ]; +} ``` From 06cc7e4ea000d38999664d7810238364a0db1a71 Mon Sep 17 00:00:00 2001 From: txtsd Date: Tue, 12 Jul 2022 21:07:05 +0530 Subject: [PATCH 085/273] chore(markdown): MD046 Code block style Signed-off-by: txtsd --- CONTRIBUTING.md | 42 ++++++++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7b60daf4..b5156fa0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,31 +26,33 @@ Signed-off-by: Author name By signing off your work, you agree to the terms below: - Developer's Certificate of Origin 1.1 +``` +Developer's Certificate of Origin 1.1 - By making a contribution to this project, I certify that: +By making a contribution to this project, I certify that: - (a) The contribution was created in whole or in part by me and I - have the right to submit it under the open source license - indicated in the file; or +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or - (b) The contribution is based upon previous work that, to the best - of my knowledge, is covered under an appropriate open source - license and I have the right under that license to submit that - work with modifications, whether created in whole or in part - by me, under the same open source license (unless I am - permitted to submit under a different license), as indicated - in the file; or +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or - (c) The contribution was provided directly to me by some other - person who certified (a), (b) or (c) and I have not modified - it. +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. - (d) I understand and agree that this project and the contribution - are public and that a record of the contribution (including all - personal information I submit with it, including my sign-off) is - maintained indefinitely and may be redistributed consistent with - this project or the open source license(s) involved. +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. +``` These terms will be enforced once you create a pull request, and you will be informed automatically if any of your commits aren't signed-off by you. From c7d435bb7a0405f5b841dad9a1e52e2479850c93 Mon Sep 17 00:00:00 2001 From: txtsd Date: Sat, 6 Aug 2022 12:57:11 +0530 Subject: [PATCH 086/273] chore(markdown): MD025 Multiple top-level headings in the same document Signed-off-by: txtsd --- COPYING.md | 30 ++++++++++++------------- libraries/katabasis/acknowledgements.md | 26 ++++++++++----------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/COPYING.md b/COPYING.md index 1dd18e17..1674a620 100644 --- a/COPYING.md +++ b/COPYING.md @@ -1,4 +1,4 @@ -# PolyMC +## PolyMC PolyMC - Minecraft Launcher Copyright (C) 2021-2022 PolyMC Contributors @@ -32,7 +32,7 @@ See the License for the specific language governing permissions and limitations under the License. -# MinGW runtime (Windows) +## MinGW runtime (Windows) Copyright (c) 2012 MinGW.org project @@ -54,14 +54,14 @@ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# Qt 5/6 +## Qt 5/6 Copyright (C) 2022 The Qt Company Ltd and other contributors. Contact: https://www.qt.io/licensing Licensed under LGPL v3 -# libnbt++ +## libnbt++ libnbt++ - A library for the Minecraft Named Binary Tag format. Copyright (C) 2013, 2015 ljfa-ag @@ -79,7 +79,7 @@ You should have received a copy of the GNU Lesser General Public License along with libnbt++. If not, see . -# rainbow (KGuiAddons) +## rainbow (KGuiAddons) Copyright (C) 2007 Matthew Woehlke Copyright (C) 2007 Olaf Schmidt @@ -102,7 +102,7 @@ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# Hoedown +## Hoedown Copyright (c) 2008, Natacha Porté Copyright (c) 2011, Vicent Martí @@ -120,7 +120,7 @@ ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -# Batch icon set +## Batch icon set You are free to use Batch (the "icon set") or any part thereof (the "icons") in any personal, open-source or commercial work without obligation of payment @@ -136,7 +136,7 @@ PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THE USE OF THE ICONS, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. -# Material Design Icons +## Material Design Icons Copyright (c) 2014, Austin Andrews (http://materialdesignicons.com/), with Reserved Font Name Material Design Icons. @@ -147,7 +147,7 @@ This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL -# Quazip +## Quazip Copyright (C) 2005-2021 Sergey A. Tachenov @@ -171,7 +171,7 @@ See COPYING file for the full LGPL text. -# xz-minidec +## xz-minidec XZ decompressor @@ -181,7 +181,7 @@ This file has been put into the public domain. You can do whatever you want with this file. -# ColumnResizer +## ColumnResizer Copyright (c) 2011-2016 Aurélien Gâteau and contributors. @@ -217,7 +217,7 @@ OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# launcher (`libraries/launcher`) +## launcher (`libraries/launcher`) PolyMC - Minecraft Launcher Copyright (C) 2021-2022 PolyMC Contributors @@ -268,7 +268,7 @@ See the License for the specific language governing permissions and limitations under the License. -# lionshead +## lionshead Code has been taken from https://github.com/natefoo/lionshead and loosely translated to C++ laced with Qt. @@ -295,7 +295,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# tomlc99 +## tomlc99 MIT License @@ -320,7 +320,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -# O2 (Katabasis fork) +## O2 (Katabasis fork) Copyright (c) 2012, Akos Polster All rights reserved. diff --git a/libraries/katabasis/acknowledgements.md b/libraries/katabasis/acknowledgements.md index 21e73e5d..a2fbfa04 100644 --- a/libraries/katabasis/acknowledgements.md +++ b/libraries/katabasis/acknowledgements.md @@ -1,4 +1,4 @@ -# O2 library by Akos Polster and contributors +## O2 library by Akos Polster and contributors [The origin of this fork.](https://github.com/pipacs/o2) @@ -26,7 +26,7 @@ > OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE > OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# SimpleCrypt by Andre Somers +## SimpleCrypt by Andre Somers Cryptographic methods for Qt. @@ -56,7 +56,7 @@ Cryptographic methods for Qt. > (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS > SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# Mandeep Sandhu +## Mandeep Sandhu Configurable settings storage, Twitter XAuth specialization, new demos, cleanups. @@ -67,42 +67,42 @@ Configurable settings storage, Twitter XAuth specialization, new demos, cleanups > Regards, > -mandeep" -# Sergey Gavrushkin +## Sergey Gavrushkin FreshBooks specialization -# Theofilos Intzoglou +## Theofilos Intzoglou Hubic specialization -# Dimitar +## Dimitar SurveyMonkey specialization -# David Brooks +## David Brooks CMake related fixes and improvements. -# Lukas Vogel +## Lukas Vogel Spotify support -# Alan Garny +## Alan Garny Windows DLL build support -# MartinMikita +## MartinMikita Bug fixes -# Larry Shaffer +## Larry Shaffer Versioning, shared lib, install target and header support -# Gilmanov Ildar +## Gilmanov Ildar Bug fixes, support for ```qml``` module -# Fabian Vogt +## Fabian Vogt Bug fixes, support for building without Qt keywords enabled From 896337803951c46d6932c443e366281692c95f27 Mon Sep 17 00:00:00 2001 From: txtsd Date: Tue, 12 Jul 2022 22:43:05 +0530 Subject: [PATCH 087/273] chore(markdown): MD007 Unordered list indentation Signed-off-by: txtsd --- README.md | 4 ++-- libraries/katabasis/README.md | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 210bdb00..9ae3013d 100644 --- a/README.md +++ b/README.md @@ -74,8 +74,8 @@ We don't care what you do with your fork/custom build as long as you follow the If you have any questions or want any clarification on the above conditions please make an issue and ask us. Be aware that if you build this software without removing the provided API keys in [CMakeLists.txt](CMakeLists.txt) you are accepting the following terms and conditions: - - [Microsoft Identity Platform Terms of Use](https://docs.microsoft.com/en-us/legal/microsoft-identity-platform/terms-of-use) - - [CurseForge 3rd Party API Terms and Conditions](https://support.curseforge.com/en/support/solutions/articles/9000207405-curse-forge-3rd-party-api-terms-and-conditions) +- [Microsoft Identity Platform Terms of Use](https://docs.microsoft.com/en-us/legal/microsoft-identity-platform/terms-of-use) +- [CurseForge 3rd Party API Terms and Conditions](https://support.curseforge.com/en/support/solutions/articles/9000207405-curse-forge-3rd-party-api-terms-and-conditions) If you do not agree with these terms and conditions, then remove the associated API keys from the [CMakeLists.txt](CMakeLists.txt) file by setting them to an empty string (`""`). diff --git a/libraries/katabasis/README.md b/libraries/katabasis/README.md index 08f3c9d1..621446e1 100644 --- a/libraries/katabasis/README.md +++ b/libraries/katabasis/README.md @@ -8,9 +8,9 @@ It may be possible to backport some of the changes to O2 in the future, but for Notes to contributors: - * Please follow the coding style of the existing source, where reasonable - * Code contributions are released under Simplified BSD License, as specified in LICENSE. Do not contribute if this license does not suit your code - * If you are interested in working on this, come to the PolyMC Discord server and talk first +* Please follow the coding style of the existing source, where reasonable +* Code contributions are released under Simplified BSD License, as specified in LICENSE. Do not contribute if this license does not suit your code +* If you are interested in working on this, come to the PolyMC Discord server and talk first ## Installation From 3275bc4e93bee2179b0a8b5a30e71d8172aaa54e Mon Sep 17 00:00:00 2001 From: txtsd Date: Wed, 13 Jul 2022 00:06:12 +0530 Subject: [PATCH 088/273] chore(markdown): MD009 Trailing spaces Signed-off-by: txtsd --- libraries/katabasis/acknowledgements.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/libraries/katabasis/acknowledgements.md b/libraries/katabasis/acknowledgements.md index a2fbfa04..ccc7c263 100644 --- a/libraries/katabasis/acknowledgements.md +++ b/libraries/katabasis/acknowledgements.md @@ -32,10 +32,10 @@ Cryptographic methods for Qt. > Copyright (c) 2011, Andre Somers > All rights reserved. -> +> > Redistribution and use in source and binary forms, with or without > modification, are permitted provided that the following conditions are met: -> +> > * Redistributions of source code must retain the above copyright > notice, this list of conditions and the following disclaimer. > * Redistributions in binary form must reproduce the above copyright @@ -44,7 +44,7 @@ Cryptographic methods for Qt. > * Neither the name of the Rathenau Instituut, Andre Somers nor the > names of its contributors may be used to endorse or promote products > derived from this software without specific prior written permission. -> +> > THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND > ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED > WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE @@ -61,9 +61,9 @@ Cryptographic methods for Qt. Configurable settings storage, Twitter XAuth specialization, new demos, cleanups. > "Hi Akos, -> +> > I'm writing this mail to confirm that my contributions to the O2 library, available here https://github.com/pipacs/o2, can be freely distributed according to the project's license (as shown in the LICENSE file). -> +> > Regards, > -mandeep" From 56cad31a36c96220d88c96183220797a512ccd62 Mon Sep 17 00:00:00 2001 From: txtsd Date: Sat, 6 Aug 2022 13:00:46 +0530 Subject: [PATCH 089/273] chore(markdown): MD034 Bare URL used Signed-off-by: txtsd --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9ae3013d..f4829723 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ If you want to read about why this fork was created, check out [our FAQ page](ht # Installation - All downloads and instructions for PolyMC can be found [here](https://polymc.org/download/) -- Last build status: https://github.com/PolyMC/PolyMC/actions +- Last build status: ## Development Builds @@ -59,7 +59,7 @@ If you want to build PolyMC yourself, check [Build Instructions](https://polymc. ## Translations -The translation effort for PolyMC is hosted on [Weblate](https://hosted.weblate.org/projects/polymc/polymc/) and information about translating PolyMC is available at https://github.com/PolyMC/Translations +The translation effort for PolyMC is hosted on [Weblate](https://hosted.weblate.org/projects/polymc/polymc/) and information about translating PolyMC is available at ## Download information @@ -68,7 +68,7 @@ To modify download information or change packaging information send a pull reque ## Forking/Redistributing/Custom builds policy We don't care what you do with your fork/custom build as long as you follow the terms of the [license](LICENSE) (this is a legal responsibility), and if you made code changes rather than just packaging a custom build, please do the following as a basic courtesy: -- Make it clear that your fork is not PolyMC and is not endorsed by or affiliated with the PolyMC project (https://polymc.org). +- Make it clear that your fork is not PolyMC and is not endorsed by or affiliated with the PolyMC project (). - Go through [CMakeLists.txt](CMakeLists.txt) and change PolyMC's API keys to your own or set them to empty strings (`""`) to disable them (this way the program will still compile but the functionality requiring those keys will be disabled). If you have any questions or want any clarification on the above conditions please make an issue and ask us. From 8085b2728d573693f05874de550171e50dbd111a Mon Sep 17 00:00:00 2001 From: txtsd Date: Wed, 13 Jul 2022 00:14:11 +0530 Subject: [PATCH 090/273] chore(markdown): MD032 Lists should be surrounded by blank lines Signed-off-by: txtsd --- CONTRIBUTING.md | 1 + README.md | 2 ++ libraries/README.md | 1 + program_info/README.md | 1 + 4 files changed, 5 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b5156fa0..4bca126f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,6 +6,7 @@ Try to follow the existing formatting. If there is no existing formatting, you may use `clang-format` with our included `.clang-format` configuration. In general, in order of importance: + - Make sure your IDE is not messing up line endings or whitespace and avoid using linters. - Prefer readability over dogma. - Keep to the existing formatting. diff --git a/README.md b/README.md index f4829723..9c07666f 100644 --- a/README.md +++ b/README.md @@ -68,12 +68,14 @@ To modify download information or change packaging information send a pull reque ## Forking/Redistributing/Custom builds policy We don't care what you do with your fork/custom build as long as you follow the terms of the [license](LICENSE) (this is a legal responsibility), and if you made code changes rather than just packaging a custom build, please do the following as a basic courtesy: + - Make it clear that your fork is not PolyMC and is not endorsed by or affiliated with the PolyMC project (). - Go through [CMakeLists.txt](CMakeLists.txt) and change PolyMC's API keys to your own or set them to empty strings (`""`) to disable them (this way the program will still compile but the functionality requiring those keys will be disabled). If you have any questions or want any clarification on the above conditions please make an issue and ask us. Be aware that if you build this software without removing the provided API keys in [CMakeLists.txt](CMakeLists.txt) you are accepting the following terms and conditions: + - [Microsoft Identity Platform Terms of Use](https://docs.microsoft.com/en-us/legal/microsoft-identity-platform/terms-of-use) - [CurseForge 3rd Party API Terms and Conditions](https://support.curseforge.com/en/support/solutions/articles/9000207405-curse-forge-3rd-party-api-terms-and-conditions) diff --git a/libraries/README.md b/libraries/README.md index 90e836e1..990d0597 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -43,6 +43,7 @@ MIT licensed. Java launcher part for Minecraft. It: + * Starts a process * Waits for a launch script on stdin * Consumes the launch script you feed it diff --git a/program_info/README.md b/program_info/README.md index 1e805d4a..421ef1f9 100644 --- a/program_info/README.md +++ b/program_info/README.md @@ -1,6 +1,7 @@ # PolyMC Program Info This is PolyMC's program info which contains information about: + - Application name and logo (and branding in general) - Various URLs and API endpoints - Desktop file From 19ecb1701e42eb277bfe5b185aec051a300f66dd Mon Sep 17 00:00:00 2001 From: txtsd Date: Wed, 13 Jul 2022 00:23:51 +0530 Subject: [PATCH 091/273] chore(markdown): MD031 Fenced code blocks should be surrounded by blank lines Signed-off-by: txtsd --- libraries/README.md | 1 + libraries/tomlc99/README.md | 5 +++++ nix/NIX.md | 1 + 3 files changed, 7 insertions(+) diff --git a/libraries/README.md b/libraries/README.md index 990d0597..e58f2273 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -57,6 +57,7 @@ A `legacy` and `onesix` launchers are available. * `onesix` can handle launching any Minecraft version, at the cost of some extra features `legacy` enables (custom window icon and title). Example (some parts have been censored): + ``` mod legacyjavafixer-1.0 mainClass net.minecraft.launchwrapper.Launch diff --git a/libraries/tomlc99/README.md b/libraries/tomlc99/README.md index 103024f0..8a144e70 100644 --- a/libraries/tomlc99/README.md +++ b/libraries/tomlc99/README.md @@ -102,6 +102,7 @@ general, all access functions on tables are named `toml_*_in(...)`. In the normal case, you know the key and its content type, and retrievals can be done using one of these functions: + ```c toml_string_in(tab, key); toml_bool_in(tab, key); @@ -113,6 +114,7 @@ toml_array_in(tab, key); ``` You can also interrogate the keys in a table using an integer index: + ```c toml_table_t* tab = toml_parse_file(...); for (int i = 0; ; i++) { @@ -127,11 +129,13 @@ for (int i = 0; ; i++) { TOML arrays can be deref-ed using integer indices. In general, all access methods on arrays are named `toml_*_at()`. To obtain the size of an array: + ```c int size = toml_array_nelem(arr); ``` To obtain the content of an array, use a valid index and call one of these functions: + ```c toml_string_at(arr, idx); toml_bool_at(arr, idx); @@ -150,6 +154,7 @@ call was successful. If so, you may proceed to read the value corresponding to the type of the content. For example: + ```c toml_datum_t host = toml_string_in(tab, "host"); if (host.ok) { diff --git a/nix/NIX.md b/nix/NIX.md index 39c67f57..047dd82f 100644 --- a/nix/NIX.md +++ b/nix/NIX.md @@ -1,6 +1,7 @@ # How to import To import with flakes use + ```nix { inputs = { From 96a91e988d7e87c3185efc930cc38de4c24e5d96 Mon Sep 17 00:00:00 2001 From: txtsd Date: Wed, 13 Jul 2022 00:25:03 +0530 Subject: [PATCH 092/273] chore(markdown): MD010 Hard tabs Signed-off-by: txtsd --- libraries/tomlc99/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/tomlc99/README.md b/libraries/tomlc99/README.md index 8a144e70..7a0eecff 100644 --- a/libraries/tomlc99/README.md +++ b/libraries/tomlc99/README.md @@ -17,8 +17,8 @@ parses this config file: ```toml [server] - host = "www.example.com" - port = [ 8080, 8181, 8282 ] + host = "www.example.com" + port = [ 8080, 8181, 8282 ] ``` The steps for getting values from our file is usually : @@ -158,8 +158,8 @@ For example: ```c toml_datum_t host = toml_string_in(tab, "host"); if (host.ok) { - printf("host: %s\n", host.u.s); - free(host.u.s); /* FREE applies to string and timestamp types only */ + printf("host: %s\n", host.u.s); + free(host.u.s); /* FREE applies to string and timestamp types only */ } ``` From 123d1864f430ef23b3c1fc5627c2f16284136fca Mon Sep 17 00:00:00 2001 From: txtsd Date: Wed, 13 Jul 2022 00:25:49 +0530 Subject: [PATCH 093/273] chore(markdown): MD037 Spaces inside emphasis markers Signed-off-by: txtsd --- libraries/tomlc99/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/tomlc99/README.md b/libraries/tomlc99/README.md index 7a0eecff..1ac4d8c9 100644 --- a/libraries/tomlc99/README.md +++ b/libraries/tomlc99/README.md @@ -163,7 +163,7 @@ if (host.ok) { } ``` -** IMPORTANT: if the accessed value is a string or a timestamp, you must call `free(datum.u.s)` or `free(datum.u.ts)` respectively after usage. ** +**IMPORTANT: if the accessed value is a string or a timestamp, you must call `free(datum.u.s)` or `free(datum.u.ts)` respectively after usage.** ## Building and installing From 9654728bfbebeb4b77c6bde26d1c45a6b9d7aac4 Mon Sep 17 00:00:00 2001 From: txtsd Date: Wed, 13 Jul 2022 00:27:32 +0530 Subject: [PATCH 094/273] chore(markdown): Align inline HTML correctly Signed-off-by: txtsd --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9c07666f..6ff868e0 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

+

PolyMC logo PolyMC logo

From 78dc0cfdf3aab90af359c8639a77e0165a916f2b Mon Sep 17 00:00:00 2001 From: txtsd Date: Wed, 13 Jul 2022 00:46:24 +0530 Subject: [PATCH 095/273] chore(markdown): MD001 Heading levels should only increment by one level at a time Signed-off-by: txtsd --- libraries/tomlc99/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/tomlc99/README.md b/libraries/tomlc99/README.md index 1ac4d8c9..e5fe9480 100644 --- a/libraries/tomlc99/README.md +++ b/libraries/tomlc99/README.md @@ -95,7 +95,7 @@ int main() } ``` -#### Accessing Table Content +### Accessing Table Content TOML tables are dictionaries where lookups are done using string keys. In general, all access functions on tables are named `toml_*_in(...)`. @@ -124,7 +124,7 @@ for (int i = 0; ; i++) { } ``` -#### Accessing Array Content +### Accessing Array Content TOML arrays can be deref-ed using integer indices. In general, all access methods on arrays are named `toml_*_at()`. @@ -146,7 +146,7 @@ toml_table_at(arr, idx); toml_array_at(arr, idx); ``` -#### toml_datum_t +### toml_datum_t Some `toml_*_at` and `toml_*_in` functions return a toml_datum_t structure. The `ok` flag in the structure indicates if the function From 76aa0c434a27be1865144bada7ba74f3d6d5e9de Mon Sep 17 00:00:00 2001 From: txtsd Date: Wed, 13 Jul 2022 00:51:20 +0530 Subject: [PATCH 096/273] feat(markdown): Add markdownlint config Signed-off-by: txtsd --- .markdownlint.yaml | 12 ++++++++++++ .markdownlintignore | 2 ++ 2 files changed, 14 insertions(+) create mode 100644 .markdownlint.yaml create mode 100644 .markdownlintignore diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 00000000..5781edb0 --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,12 @@ +# MD013/line-length - Line length +MD013: false + +# MD024/no-duplicate-heading/no-duplicate-header - Multiple headings with the same content +MD024: + siblings-only: true + +# MD033/no-inline-html Inline HTML +MD033: false + +# MD041/first-line-heading/first-line-h1 First line in a file should be a top-level heading +MD041: false diff --git a/.markdownlintignore b/.markdownlintignore new file mode 100644 index 00000000..a8669d01 --- /dev/null +++ b/.markdownlintignore @@ -0,0 +1,2 @@ +libraries/nbtplusplus +libraries/quazip From fa8df286d96f5f046e64e1bc9c9605a29d5552e1 Mon Sep 17 00:00:00 2001 From: txtsd Date: Wed, 13 Jul 2022 01:09:56 +0530 Subject: [PATCH 097/273] chore(ci): Add markdownlint config to ignored paths Signed-off-by: txtsd --- .github/workflows/trigger_builds.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/trigger_builds.yml b/.github/workflows/trigger_builds.yml index ee9eb4ea..55b4fdd4 100644 --- a/.github/workflows/trigger_builds.yml +++ b/.github/workflows/trigger_builds.yml @@ -11,6 +11,7 @@ on: - '**.nix' - 'packages/**' - '.github/ISSUE_TEMPLATE/**' + - '.markdownlint**' pull_request: paths-ignore: - '**.md' @@ -19,6 +20,7 @@ on: - '**.nix' - 'packages/**' - '.github/ISSUE_TEMPLATE/**' + - '.markdownlint**' workflow_dispatch: jobs: From f873cd5b1aa17fe8119f27cc5d9b9eba10bd42bf Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Mon, 8 Aug 2022 20:49:49 +0200 Subject: [PATCH 098/273] refactor: store current capabilities Signed-off-by: Sefa Eyeoglu --- launcher/Application.cpp | 11 ++++++----- launcher/Application.h | 7 ++++++- launcher/net/Download.cpp | 2 +- launcher/net/Upload.cpp | 2 +- launcher/ui/dialogs/ModDownloadDialog.cpp | 2 +- launcher/ui/dialogs/NewInstanceDialog.cpp | 2 +- launcher/ui/pages/global/AccountListPage.cpp | 2 +- 7 files changed, 17 insertions(+), 11 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 97d41993..710fa38d 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -919,6 +919,8 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) { return; } + + updateCapabilities(); performMainStartupAction(); } @@ -1564,14 +1566,13 @@ shared_qobject_ptr Application::metadataIndex() return m_metadataIndex; } -Application::Capabilities Application::currentCapabilities() +void Application::updateCapabilities() { - Capabilities c; + m_capabilities = None; if (!getMSAClientID().isEmpty()) - c |= SupportsMSA; + m_capabilities |= SupportsMSA; if (!getFlameAPIKey().isEmpty()) - c |= SupportsFlame; - return c; + m_capabilities |= SupportsFlame; } QString Application::getJarPath(QString jarFile) diff --git a/launcher/Application.h b/launcher/Application.h index 019c3c3d..05525bcf 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -162,7 +162,7 @@ public: shared_qobject_ptr metadataIndex(); - Capabilities currentCapabilities(); + void updateCapabilities(); /*! * Finds and returns the full path to a jar file. @@ -180,6 +180,10 @@ public: return m_rootPath; } + const Capabilities capabilities() { + return m_capabilities; + } + /*! * Opens a json file using either a system default editor, or, if not empty, the editor * specified in the settings @@ -258,6 +262,7 @@ private: QString m_rootPath; Status m_status = Application::StartingUp; + Capabilities m_capabilities; #ifdef Q_OS_MACOS Qt::ApplicationState m_prevAppState = Qt::ApplicationInactive; diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index e6a6adcc..8db11eaa 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -118,7 +118,7 @@ void Download::executeTask() } request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgent().toUtf8()); - if (APPLICATION->currentCapabilities() & Application::SupportsFlame + if (APPLICATION->capabilities() & Application::SupportsFlame && request.url().host().contains("api.curseforge.com")) { request.setRawHeader("x-api-key", APPLICATION->getFlameAPIKey().toUtf8()); }; diff --git a/launcher/net/Upload.cpp b/launcher/net/Upload.cpp index cfda4b4e..f3b19022 100644 --- a/launcher/net/Upload.cpp +++ b/launcher/net/Upload.cpp @@ -216,7 +216,7 @@ namespace Net { } request.setHeader(QNetworkRequest::UserAgentHeader, APPLICATION->getUserAgent().toUtf8()); - if (APPLICATION->currentCapabilities() & Application::SupportsFlame + if (APPLICATION->capabilities() & Application::SupportsFlame && request.url().host().contains("api.curseforge.com")) { request.setRawHeader("x-api-key", APPLICATION->getFlameAPIKey().toUtf8()); } diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp index e4fc3ecc..bc5e5206 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.cpp +++ b/launcher/ui/dialogs/ModDownloadDialog.cpp @@ -122,7 +122,7 @@ QList ModDownloadDialog::getPages() QList pages; pages.append(new ModrinthModPage(this, m_instance)); - if (APPLICATION->currentCapabilities() & Application::SupportsFlame) + if (APPLICATION->capabilities() & Application::SupportsFlame) pages.append(new FlameModPage(this, m_instance)); return pages; diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp index 35bba9be..675f8b15 100644 --- a/launcher/ui/dialogs/NewInstanceDialog.cpp +++ b/launcher/ui/dialogs/NewInstanceDialog.cpp @@ -157,7 +157,7 @@ QList NewInstanceDialog::getPages() pages.append(new VanillaPage(this)); pages.append(importPage); pages.append(new AtlPage(this)); - if (APPLICATION->currentCapabilities() & Application::SupportsFlame) + if (APPLICATION->capabilities() & Application::SupportsFlame) pages.append(new FlamePage(this)); pages.append(new FtbPage(this)); pages.append(new LegacyFTB::Page(this)); diff --git a/launcher/ui/pages/global/AccountListPage.cpp b/launcher/ui/pages/global/AccountListPage.cpp index fcc43add..a4f4dfb9 100644 --- a/launcher/ui/pages/global/AccountListPage.cpp +++ b/launcher/ui/pages/global/AccountListPage.cpp @@ -96,7 +96,7 @@ AccountListPage::AccountListPage(QWidget *parent) updateButtonStates(); // Xbox authentication won't work without a client identifier, so disable the button if it is missing - if (~APPLICATION->currentCapabilities() & Application::SupportsMSA) { + if (~APPLICATION->capabilities() & Application::SupportsMSA) { ui->actionAddMicrosoft->setVisible(false); ui->actionAddMicrosoft->setToolTip(tr("No Microsoft Authentication client ID was set.")); } From 68f3f98bc3974667812ed778d572a0aeb0c89bec Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Mon, 8 Aug 2022 21:03:02 +0200 Subject: [PATCH 099/273] feat: detect GameMode and MangoHud's presence Signed-off-by: Sefa Eyeoglu --- launcher/Application.cpp | 22 +++++++++++++++++++ launcher/Application.h | 2 ++ launcher/ui/pages/global/MinecraftPage.cpp | 10 +++++++++ .../pages/instance/InstanceSettingsPage.cpp | 14 ++++++++++-- 4 files changed, 46 insertions(+), 2 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 710fa38d..31e9d1a3 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -113,6 +113,11 @@ #include +#ifdef Q_OS_LINUX +#include +#include "gamemode_client.h" +#endif + #if defined Q_OS_WIN32 #ifndef WIN32_LEAN_AND_MEAN @@ -1573,6 +1578,23 @@ void Application::updateCapabilities() m_capabilities |= SupportsMSA; if (!getFlameAPIKey().isEmpty()) m_capabilities |= SupportsFlame; + +#ifdef Q_OS_LINUX + if (gamemode_query_status() >= 0) + m_capabilities |= SupportsGameMode; + + { + void *dummy = dlopen("libMangoHud_dlsym.so", RTLD_LAZY); + // try normal variant as well + if (dummy == NULL) + dummy = dlopen("libMangoHud.so", RTLD_LAZY); + + if (dummy != NULL) { + dlclose(dummy); + m_capabilities |= SupportsMangoHud; + } + } +#endif } QString Application::getJarPath(QString jarFile) diff --git a/launcher/Application.h b/launcher/Application.h index 05525bcf..41fd4c47 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -95,6 +95,8 @@ public: SupportsMSA = 1 << 0, SupportsFlame = 1 << 1, + SupportsGameMode = 1 << 2, + SupportsMangoHud = 1 << 3, }; Q_DECLARE_FLAGS(Capabilities, Capability) diff --git a/launcher/ui/pages/global/MinecraftPage.cpp b/launcher/ui/pages/global/MinecraftPage.cpp index e3ac7e7c..cc597fe0 100644 --- a/launcher/ui/pages/global/MinecraftPage.cpp +++ b/launcher/ui/pages/global/MinecraftPage.cpp @@ -122,6 +122,16 @@ void MinecraftPage::loadSettings() ui->perfomanceGroupBox->setVisible(false); #endif + if (!(APPLICATION->capabilities() & Application::SupportsGameMode)) { + ui->enableFeralGamemodeCheck->setDisabled(true); + ui->enableFeralGamemodeCheck->setToolTip(tr("Feral Interactive's GameMode could not be found on your system.")); + } + + if (!(APPLICATION->capabilities() & Application::SupportsMangoHud)) { + ui->enableMangoHud->setDisabled(true); + ui->enableMangoHud->setToolTip(tr("MangoHud could not be found on your system.")); + } + ui->showGameTime->setChecked(s->get("ShowGameTime").toBool()); ui->showGlobalGameTime->setChecked(s->get("ShowGlobalGameTime").toBool()); ui->recordGameTime->setChecked(s->get("RecordGameTime").toBool()); diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index f11cf992..03910745 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp @@ -348,9 +348,19 @@ void InstanceSettingsPage::loadSettings() ui->enableMangoHud->setChecked(m_settings->get("EnableMangoHud").toBool()); ui->useDiscreteGpuCheck->setChecked(m_settings->get("UseDiscreteGpu").toBool()); - #if !defined(Q_OS_LINUX) +#if !defined(Q_OS_LINUX) ui->settingsTabs->setTabVisible(ui->settingsTabs->indexOf(ui->performancePage), false); - #endif +#endif + + if (!(APPLICATION->capabilities() & Application::SupportsGameMode)) { + ui->enableFeralGamemodeCheck->setDisabled(true); + ui->enableFeralGamemodeCheck->setToolTip(tr("Feral Interactive's GameMode could not be found on your system.")); + } + + if (!(APPLICATION->capabilities() & Application::SupportsMangoHud)) { + ui->enableMangoHud->setDisabled(true); + ui->enableMangoHud->setToolTip(tr("MangoHud could not be found on your system.")); + } // Miscellanous ui->gameTimeGroupBox->setChecked(m_settings->get("OverrideGameTime").toBool()); From 33af0c6a7c2f9722883fbe65a8683731bccc50cf Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Mon, 8 Aug 2022 21:05:02 +0200 Subject: [PATCH 100/273] refactor: don't include mangohud's library path This could cause issues on some environments. Users should just put MangoHud libs into global LD_LIBRARY_PATH, just like with any other library Signed-off-by: Sefa Eyeoglu --- launcher/minecraft/MinecraftInstance.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 5a6f8de0..67a9332e 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -450,10 +450,8 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment() if (settings()->get("EnableMangoHud").toBool()) { auto preload = env.value("LD_PRELOAD", "") + ":libMangoHud_dlsym.so:libMangoHud.so"; - auto lib_path = env.value("LD_LIBRARY_PATH", "") + ":/usr/local/$LIB/mangohud/:/usr/$LIB/mangohud/"; env.insert("LD_PRELOAD", preload); - env.insert("LD_LIBRARY_PATH", lib_path); env.insert("MANGOHUD", "1"); } From d82bb29919d1cc6a9ead4a4e4a18d7b02c5221eb Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Mon, 8 Aug 2022 21:19:42 +0200 Subject: [PATCH 101/273] fix: don't apply GameMode/MangoHud, if they aren't supported Signed-off-by: Sefa Eyeoglu --- launcher/minecraft/MinecraftInstance.cpp | 2 +- launcher/minecraft/launch/DirectJavaLaunch.cpp | 4 +++- launcher/minecraft/launch/LauncherPartLaunch.cpp | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 67a9332e..822186b5 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -447,7 +447,7 @@ QProcessEnvironment MinecraftInstance::createLaunchEnvironment() QProcessEnvironment env = createEnvironment(); #ifdef Q_OS_LINUX - if (settings()->get("EnableMangoHud").toBool()) + if (settings()->get("EnableMangoHud").toBool() && APPLICATION->capabilities() & Application::SupportsMangoHud) { auto preload = env.value("LD_PRELOAD", "") + ":libMangoHud_dlsym.so:libMangoHud.so"; diff --git a/launcher/minecraft/launch/DirectJavaLaunch.cpp b/launcher/minecraft/launch/DirectJavaLaunch.cpp index 152485b3..ca55cd2e 100644 --- a/launcher/minecraft/launch/DirectJavaLaunch.cpp +++ b/launcher/minecraft/launch/DirectJavaLaunch.cpp @@ -21,6 +21,8 @@ #include #include +#include "Application.h" + #ifdef Q_OS_LINUX #include "gamemode_client.h" #endif @@ -86,7 +88,7 @@ void DirectJavaLaunch::executeTask() } #ifdef Q_OS_LINUX - if (instance->settings()->get("EnableFeralGamemode").toBool()) + if (instance->settings()->get("EnableFeralGamemode").toBool() && APPLICATION->capabilities() & Application::SupportsGameMode) { auto pid = m_process.processId(); if (pid) diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp index 63e4d90f..ce477ad7 100644 --- a/launcher/minecraft/launch/LauncherPartLaunch.cpp +++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp @@ -181,7 +181,7 @@ void LauncherPartLaunch::executeTask() } #ifdef Q_OS_LINUX - if (instance->settings()->get("EnableFeralGamemode").toBool()) + if (instance->settings()->get("EnableFeralGamemode").toBool() && APPLICATION->capabilities() & Application::SupportsGameMode) { auto pid = m_process.processId(); if (pid) From a14476c5fbd87f2a765b3f8807950bd728f282b2 Mon Sep 17 00:00:00 2001 From: Mitchell Skaggs Date: Mon, 8 Aug 2022 23:54:01 -0500 Subject: [PATCH 102/273] Replace local 8-bit decoding with UTF-8 decoding Handles incomplete byte sequences using `QTextDecoder` Signed-off-by: Mitchell Skaggs --- launcher/LoggedProcess.cpp | 28 +++++++--------------------- launcher/LoggedProcess.h | 5 +++-- 2 files changed, 10 insertions(+), 23 deletions(-) diff --git a/launcher/LoggedProcess.cpp b/launcher/LoggedProcess.cpp index fbdeed8f..5ad310a3 100644 --- a/launcher/LoggedProcess.cpp +++ b/launcher/LoggedProcess.cpp @@ -34,8 +34,9 @@ */ #include "LoggedProcess.h" -#include "MessageLevel.h" #include +#include +#include "MessageLevel.h" LoggedProcess::LoggedProcess(QObject *parent) : QProcess(parent) { @@ -59,25 +60,22 @@ LoggedProcess::~LoggedProcess() } } -QStringList reprocess(const QByteArray & data, QString & leftover) +QStringList reprocess(const QByteArray& data, QTextDecoder& decoder) { - QString str = leftover + QString::fromLocal8Bit(data); - - str.remove('\r'); - QStringList lines = str.split("\n"); - leftover = lines.takeLast(); + auto str = decoder.toUnicode(data); + auto lines = str.remove(QChar::CarriageReturn).split(QChar::LineFeed, Qt::SkipEmptyParts); return lines; } void LoggedProcess::on_stdErr() { - auto lines = reprocess(readAllStandardError(), m_err_leftover); + auto lines = reprocess(readAllStandardError(), m_err_decoder); emit log(lines, MessageLevel::StdErr); } void LoggedProcess::on_stdOut() { - auto lines = reprocess(readAllStandardOutput(), m_out_leftover); + auto lines = reprocess(readAllStandardOutput(), m_out_decoder); emit log(lines, MessageLevel::StdOut); } @@ -86,18 +84,6 @@ void LoggedProcess::on_exit(int exit_code, QProcess::ExitStatus status) // save the exit code m_exit_code = exit_code; - // Flush console window - if (!m_err_leftover.isEmpty()) - { - emit log({m_err_leftover}, MessageLevel::StdErr); - m_err_leftover.clear(); - } - if (!m_out_leftover.isEmpty()) - { - emit log({m_err_leftover}, MessageLevel::StdOut); - m_out_leftover.clear(); - } - // based on state, send signals if (!m_is_aborting) { diff --git a/launcher/LoggedProcess.h b/launcher/LoggedProcess.h index 61e74bd9..91667b91 100644 --- a/launcher/LoggedProcess.h +++ b/launcher/LoggedProcess.h @@ -36,6 +36,7 @@ #pragma once #include +#include #include "MessageLevel.h" /* @@ -88,8 +89,8 @@ private: void changeState(LoggedProcess::State state); private: - QString m_err_leftover; - QString m_out_leftover; + QTextDecoder m_err_decoder = QTextDecoder(QTextCodec::codecForName("UTF-8")); + QTextDecoder m_out_decoder = QTextDecoder(QTextCodec::codecForName("UTF-8")); bool m_killed = false; State m_state = NotRunning; int m_exit_code = 0; From 94df4ceb36513ee07a1821cb06debb4e6a4bb05c Mon Sep 17 00:00:00 2001 From: Mitchell Skaggs Date: Tue, 9 Aug 2022 00:17:53 -0500 Subject: [PATCH 103/273] Fix use of Qt 5.14 enum Signed-off-by: Mitchell Skaggs --- launcher/LoggedProcess.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/launcher/LoggedProcess.cpp b/launcher/LoggedProcess.cpp index 5ad310a3..6447f5c6 100644 --- a/launcher/LoggedProcess.cpp +++ b/launcher/LoggedProcess.cpp @@ -63,7 +63,11 @@ LoggedProcess::~LoggedProcess() QStringList reprocess(const QByteArray& data, QTextDecoder& decoder) { auto str = decoder.toUnicode(data); +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) + auto lines = str.remove(QChar::CarriageReturn).split(QChar::LineFeed, QString::SkipEmptyParts); +#else auto lines = str.remove(QChar::CarriageReturn).split(QChar::LineFeed, Qt::SkipEmptyParts); +#endif return lines; } From cd30f75173a8062a02413420ecbbc51ce15007e1 Mon Sep 17 00:00:00 2001 From: Robb Date: Tue, 9 Aug 2022 16:01:21 -0500 Subject: [PATCH 104/273] fix: Make world safety nag title text match the action being performed instead of always saying 'Copy World' Signed-off-by: Robb --- launcher/ui/pages/instance/WorldListPage.cpp | 12 ++++++------ launcher/ui/pages/instance/WorldListPage.h | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index 647b04a7..a5c692ac 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -211,7 +211,7 @@ void WorldListPage::on_actionDatapacks_triggered() return; } - if(!worldSafetyNagQuestion()) + if(!worldSafetyNagQuestion("Open World Datapacks Folder")) return; auto fullPath = m_worlds->data(index, WorldList::FolderRole).toString(); @@ -269,7 +269,7 @@ void WorldListPage::on_actionMCEdit_triggered() return; } - if(!worldSafetyNagQuestion()) + if(!worldSafetyNagQuestion("Open World in MCEdit")) return; auto fullPath = m_worlds->data(index, WorldList::FolderRole).toString(); @@ -373,11 +373,11 @@ bool WorldListPage::isWorldSafe(QModelIndex) return !m_inst->isRunning(); } -bool WorldListPage::worldSafetyNagQuestion() +bool WorldListPage::worldSafetyNagQuestion(const QString &actionType) { if(!isWorldSafe(getSelectedWorld())) { - auto result = QMessageBox::question(this, tr("Copy World"), tr("Changing a world while Minecraft is running is potentially unsafe.\nDo you wish to proceed?")); + auto result = QMessageBox::question(this, tr("%1").arg(actionType), tr("Changing a world while Minecraft is running is potentially unsafe.\nDo you wish to proceed?")); if(result == QMessageBox::No) { return false; @@ -395,7 +395,7 @@ void WorldListPage::on_actionCopy_triggered() return; } - if(!worldSafetyNagQuestion()) + if(!worldSafetyNagQuestion("Copy World")) return; auto worldVariant = m_worlds->data(index, WorldList::ObjectRole); @@ -417,7 +417,7 @@ void WorldListPage::on_actionRename_triggered() return; } - if(!worldSafetyNagQuestion()) + if(!worldSafetyNagQuestion("Rename World")) return; auto worldVariant = m_worlds->data(index, WorldList::ObjectRole); diff --git a/launcher/ui/pages/instance/WorldListPage.h b/launcher/ui/pages/instance/WorldListPage.h index 17e36a08..1dc9e53e 100644 --- a/launcher/ui/pages/instance/WorldListPage.h +++ b/launcher/ui/pages/instance/WorldListPage.h @@ -93,7 +93,7 @@ protected: private: QModelIndex getSelectedWorld(); bool isWorldSafe(QModelIndex index); - bool worldSafetyNagQuestion(); + bool worldSafetyNagQuestion(const QString &actionType); void mceditError(); private: From cebac3c10e177317c3df812bc87b83d999b0a37d Mon Sep 17 00:00:00 2001 From: Robb Date: Wed, 10 Aug 2022 12:07:24 -0500 Subject: [PATCH 105/273] Make new title strings translatable Signed-off-by: Robb --- launcher/ui/pages/instance/WorldListPage.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/launcher/ui/pages/instance/WorldListPage.cpp b/launcher/ui/pages/instance/WorldListPage.cpp index a5c692ac..85cc01ff 100644 --- a/launcher/ui/pages/instance/WorldListPage.cpp +++ b/launcher/ui/pages/instance/WorldListPage.cpp @@ -211,7 +211,7 @@ void WorldListPage::on_actionDatapacks_triggered() return; } - if(!worldSafetyNagQuestion("Open World Datapacks Folder")) + if(!worldSafetyNagQuestion(tr("Open World Datapacks Folder"))) return; auto fullPath = m_worlds->data(index, WorldList::FolderRole).toString(); @@ -269,7 +269,7 @@ void WorldListPage::on_actionMCEdit_triggered() return; } - if(!worldSafetyNagQuestion("Open World in MCEdit")) + if(!worldSafetyNagQuestion(tr("Open World in MCEdit"))) return; auto fullPath = m_worlds->data(index, WorldList::FolderRole).toString(); @@ -377,7 +377,7 @@ bool WorldListPage::worldSafetyNagQuestion(const QString &actionType) { if(!isWorldSafe(getSelectedWorld())) { - auto result = QMessageBox::question(this, tr("%1").arg(actionType), tr("Changing a world while Minecraft is running is potentially unsafe.\nDo you wish to proceed?")); + auto result = QMessageBox::question(this, actionType, tr("Changing a world while Minecraft is running is potentially unsafe.\nDo you wish to proceed?")); if(result == QMessageBox::No) { return false; @@ -395,7 +395,7 @@ void WorldListPage::on_actionCopy_triggered() return; } - if(!worldSafetyNagQuestion("Copy World")) + if(!worldSafetyNagQuestion(tr("Copy World"))) return; auto worldVariant = m_worlds->data(index, WorldList::ObjectRole); @@ -417,7 +417,7 @@ void WorldListPage::on_actionRename_triggered() return; } - if(!worldSafetyNagQuestion("Rename World")) + if(!worldSafetyNagQuestion(tr("Rename World"))) return; auto worldVariant = m_worlds->data(index, WorldList::ObjectRole); From 2f5e55bea05c600d0ff44678f7a79ea7e276ee6d Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 11 Aug 2022 13:00:09 -0300 Subject: [PATCH 106/273] fix: only remove orphaned metadata on first opening This avoids deleting the metadata while one is updating their mods. Signed-off-by: flow --- launcher/minecraft/mod/ModFolderModel.cpp | 6 +++++- launcher/minecraft/mod/ModFolderModel.h | 1 + .../minecraft/mod/tasks/ModFolderLoadTask.cpp | 18 ++++++++++-------- .../minecraft/mod/tasks/ModFolderLoadTask.h | 3 ++- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 112d219e..d4c5e819 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -63,6 +63,9 @@ void ModFolderModel::startWatching() if(is_watching) return; + // Remove orphaned metadata next time + m_first_folder_load = true; + update(); // Watch the mods folder @@ -113,7 +116,8 @@ bool ModFolderModel::update() } auto index_dir = indexDir(); - auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed); + auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load); + m_first_folder_load = false; m_update = task->result(); diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index a7d3ece0..3d6efac3 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -172,6 +172,7 @@ protected: bool interaction_disabled = false; QDir m_dir; bool m_is_indexed; + bool m_first_folder_load = true; QMap modsIndex; QMap activeTickets; int nextResolutionTicket = 0; diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp index 9b70e7a1..015ead80 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp @@ -38,8 +38,8 @@ #include "minecraft/mod/MetadataHandler.h" -ModFolderLoadTask::ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed) - : m_mods_dir(mods_dir), m_index_dir(index_dir), m_is_indexed(is_indexed), m_result(new Result()) +ModFolderLoadTask::ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed, bool clean_orphan) + : m_mods_dir(mods_dir), m_index_dir(index_dir), m_is_indexed(is_indexed), m_clean_orphan(clean_orphan), m_result(new Result()) {} void ModFolderLoadTask::run() @@ -85,12 +85,14 @@ void ModFolderLoadTask::run() // Remove orphan metadata to prevent issues // See https://github.com/PolyMC/PolyMC/issues/996 - QMutableMapIterator iter(m_result->mods); - while (iter.hasNext()) { - auto mod = iter.next().value(); - if (mod->status() == ModStatus::NotInstalled) { - mod->destroy(m_index_dir, false); - iter.remove(); + if (m_clean_orphan) { + QMutableMapIterator iter(m_result->mods); + while (iter.hasNext()) { + auto mod = iter.next().value(); + if (mod->status() == ModStatus::NotInstalled) { + mod->destroy(m_index_dir, false); + iter.remove(); + } } } diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h index 0b6bb6cc..1f2015d2 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h @@ -56,7 +56,7 @@ public: } public: - ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed); + ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed, bool clean_orphan = false); void run(); signals: void succeeded(); @@ -67,5 +67,6 @@ private: private: QDir& m_mods_dir, m_index_dir; bool m_is_indexed; + bool m_clean_orphan; ResultPtr m_result; }; From 1175461030f7bd90b69cb622f547879d76b54f3d Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sat, 23 Jul 2022 14:20:52 +0200 Subject: [PATCH 107/273] refactor: switch to new versioning scheme The new versioning system is based on the versioning system used by the GNOME Foundation for the GNOME desktop. We are dropping the "major version" as defined by SemVer and move to a version number with a most and least significant number. The most significant number must be incremented, if there are new features or significant changes since last major release. Otherwise, the least significant number must be incremented, if there are only minor changes since the last release. New features or significant changes mustn't be introduced by a bump of the least significant number. If a minor change would introduce small user-facing changes (like a message-box or slight UI changes), it could still be classified as a minor change. At the end of the day, a human shall decide, if a change is minor or significant, as there is no clear line that would separate a "minor" and a "significant" change in a GUI-application. Definitions: feature: New user-facing functionality significant change: Something that changes user-facing behavior minor change: Something that fixes unexpected behavior Signed-off-by: Sefa Eyeoglu --- CMakeLists.txt | 25 +++++++------------ buildconfig/BuildConfig.cpp.in | 14 +++-------- buildconfig/BuildConfig.h | 10 +++----- launcher/Application.cpp | 2 +- launcher/ui/dialogs/AboutDialog.cpp | 11 +++++--- launcher/ui/dialogs/AboutDialog.ui | 20 +++++++++++++-- launcher/updater/UpdateChecker.cpp | 3 +-- launcher/updater/UpdateChecker.h | 2 +- program_info/CMakeLists.txt | 2 +- .../org.polymc.PolyMC.metainfo.xml.in | 2 +- program_info/polymc.manifest.in | 2 +- program_info/polymc.rc.in | 6 ++--- program_info/win_install.nsi.in | 12 ++++----- 13 files changed, 56 insertions(+), 55 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 33c53b82..fa78d546 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,12 +79,12 @@ set(Launcher_NEWS_OPEN_URL "https://polymc.org/news" CACHE STRING "URL that gets set(Launcher_HELP_URL "https://polymc.org/wiki/help-pages/%1" CACHE STRING "URL (with arg %1 to be substituted with page-id) that gets opened when the user requests help") ######## Set version numbers ######## -set(Launcher_VERSION_MAJOR 1) -set(Launcher_VERSION_MINOR 4) -set(Launcher_VERSION_HOTFIX 0) +set(Launcher_VERSION_MAJOR 5) +set(Launcher_VERSION_MINOR 0) -# Build number -set(Launcher_VERSION_BUILD -1 CACHE STRING "Build number. -1 for no build number.") +set(Launcher_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}") +set(Launcher_VERSION_NAME4 "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.0.0") +set(Launcher_VERSION_NAME4_COMMA "${Launcher_VERSION_MAJOR},${Launcher_VERSION_MINOR},0,0") # Build platform. set(Launcher_BUILD_PLATFORM "" CACHE STRING "A short string identifying the platform that this build was built for. Only used to display in the about dialog.") @@ -143,15 +143,8 @@ message(STATUS "Git commit: ${Launcher_GIT_COMMIT}") message(STATUS "Git tag: ${Launcher_GIT_TAG}") message(STATUS "Git refspec: ${Launcher_GIT_REFSPEC}") -set(Launcher_RELEASE_VERSION_NAME "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_HOTFIX}") -set(Launcher_RELEASE_VERSION_NAME4 "${Launcher_RELEASE_VERSION_NAME}.0") -set(Launcher_RELEASE_VERSION_NAME4_COMMA "${Launcher_VERSION_MAJOR},${Launcher_VERSION_MINOR},${Launcher_VERSION_HOTFIX},0") string(TIMESTAMP TODAY "%Y-%m-%d") -set(Launcher_RELEASE_TIMESTAMP "${TODAY}") - -#### Custom target to just print the version. -add_custom_target(version echo "Version: ${Launcher_RELEASE_VERSION_NAME}") -add_custom_target(tcversion echo "\\#\\#teamcity[setParameter name=\\'env.LAUNCHER_VERSION\\' value=\\'${Launcher_RELEASE_VERSION_NAME}\\']") +set(Launcher_BUILD_TIMESTAMP "${TODAY}") ################################ 3rd Party Libs ################################ @@ -226,9 +219,9 @@ if(UNIX AND APPLE) set(MACOSX_BUNDLE_BUNDLE_NAME "${Launcher_Name}") set(MACOSX_BUNDLE_INFO_STRING "${Launcher_Name}: A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once.") set(MACOSX_BUNDLE_GUI_IDENTIFIER "org.polymc.${Launcher_Name}") - set(MACOSX_BUNDLE_BUNDLE_VERSION "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_HOTFIX}") - set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_HOTFIX}") - set(MACOSX_BUNDLE_LONG_VERSION_STRING "${Launcher_VERSION_MAJOR}.${Launcher_VERSION_MINOR}.${Launcher_VERSION_HOTFIX}") + set(MACOSX_BUNDLE_BUNDLE_VERSION "${Launcher_VERSION_NAME}") + set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${Launcher_VERSION_NAME}") + set(MACOSX_BUNDLE_LONG_VERSION_STRING "${Launcher_VERSION_NAME}") set(MACOSX_BUNDLE_ICON_FILE ${Launcher_Name}.icns) set(MACOSX_BUNDLE_COPYRIGHT "Copyright 2021-2022 ${Launcher_Copyright}") set(MACOSX_SPARKLE_UPDATE_PUBLIC_KEY "idALcUIazingvKSSsEa9U7coDVxZVx/ORpOEE/QtJfg=") diff --git a/buildconfig/BuildConfig.cpp.in b/buildconfig/BuildConfig.cpp.in index 7da66f36..50e5e8a4 100644 --- a/buildconfig/BuildConfig.cpp.in +++ b/buildconfig/BuildConfig.cpp.in @@ -55,10 +55,9 @@ Config::Config() // Version information VERSION_MAJOR = @Launcher_VERSION_MAJOR@; VERSION_MINOR = @Launcher_VERSION_MINOR@; - VERSION_HOTFIX = @Launcher_VERSION_HOTFIX@; - VERSION_BUILD = @Launcher_VERSION_BUILD@; BUILD_PLATFORM = "@Launcher_BUILD_PLATFORM@"; + BUILD_DATE = "@Launcher_BUILD_TIMESTAMP@"; UPDATER_BASE = "@Launcher_UPDATER_BASE@"; MAC_SPARKLE_PUB_KEY = "@MACOSX_SPARKLE_UPDATE_PUBLIC_KEY@"; @@ -85,7 +84,7 @@ Config::Config() { VERSION_CHANNEL = GIT_REFSPEC; VERSION_CHANNEL.remove("refs/heads/"); - if(!UPDATER_BASE.isEmpty() && !BUILD_PLATFORM.isEmpty() && VERSION_BUILD >= 0) { + if(!UPDATER_BASE.isEmpty() && !BUILD_PLATFORM.isEmpty()) { UPDATER_ENABLED = true; } } @@ -98,7 +97,6 @@ Config::Config() VERSION_CHANNEL = "unknown"; } - VERSION_STR = "@Launcher_VERSION_STRING@"; NEWS_RSS_URL = "@Launcher_NEWS_RSS_URL@"; NEWS_OPEN_URL = "@Launcher_NEWS_OPEN_URL@"; HELP_URL = "@Launcher_HELP_URL@"; @@ -116,7 +114,7 @@ Config::Config() QString Config::versionString() const { - return QString("%1.%2.%3").arg(VERSION_MAJOR).arg(VERSION_MINOR).arg(VERSION_HOTFIX); + return QString("%1.%2").arg(VERSION_MAJOR).arg(VERSION_MINOR); } QString Config::printableVersionString() const @@ -128,11 +126,5 @@ QString Config::printableVersionString() const { vstr += "-" + VERSION_CHANNEL; } - - // if a build number is set, also add it to the end - if(VERSION_BUILD >= 0) - { - vstr += "+build." + QString::number(VERSION_BUILD); - } return vstr; } diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h index 95786d82..de66cec4 100644 --- a/buildconfig/BuildConfig.h +++ b/buildconfig/BuildConfig.h @@ -55,10 +55,6 @@ class Config { int VERSION_MAJOR; /// The minor version number. int VERSION_MINOR; - /// The hotfix number. - int VERSION_HOTFIX; - /// The build number. - int VERSION_BUILD; /** * The version channel @@ -71,6 +67,9 @@ class Config { /// A short string identifying this build's platform. For example, "lin64" or "win32". QString BUILD_PLATFORM; + /// A string containing the build timestamp + QString BUILD_DATE; + /// URL for the updater's channel QString UPDATER_BASE; @@ -95,9 +94,6 @@ class Config { /// The git refspec of this build QString GIT_REFSPEC; - /// This is printed on start to standard output - QString VERSION_STR; - /** * This is used to fetch the news RSS feed. * It defaults in CMakeLists.txt to "https://multimc.org/rss.xml" diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 2bd91fd7..b0a795ca 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -774,7 +774,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) auto platform = getIdealPlatform(BuildConfig.BUILD_PLATFORM); auto channelUrl = BuildConfig.UPDATER_BASE + platform + "/channels.json"; qDebug() << "Initializing updater with platform: " << platform << " -- " << channelUrl; - m_updateChecker.reset(new UpdateChecker(m_network, channelUrl, BuildConfig.VERSION_CHANNEL, BuildConfig.VERSION_BUILD)); + m_updateChecker.reset(new UpdateChecker(m_network, channelUrl, BuildConfig.VERSION_CHANNEL)); qDebug() << "<> Updater started."; } diff --git a/launcher/ui/dialogs/AboutDialog.cpp b/launcher/ui/dialogs/AboutDialog.cpp index c5367d5b..743c34f1 100644 --- a/launcher/ui/dialogs/AboutDialog.cpp +++ b/launcher/ui/dialogs/AboutDialog.cpp @@ -147,10 +147,15 @@ AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent), ui(new Ui::AboutDia else ui->platformLabel->setVisible(false); - if (BuildConfig.VERSION_BUILD >= 0) - ui->buildNumLabel->setText(tr("Build Number") +": " + QString::number(BuildConfig.VERSION_BUILD)); + if (!BuildConfig.GIT_COMMIT.isEmpty()) + ui->commitLabel->setText(tr("Commit: %1").arg(BuildConfig.GIT_COMMIT)); else - ui->buildNumLabel->setVisible(false); + ui->commitLabel->setVisible(false); + + if (!BuildConfig.BUILD_DATE.isEmpty()) + ui->buildDateLabel->setText(tr("Build date: %1").arg(BuildConfig.BUILD_DATE)); + else + ui->buildDateLabel->setVisible(false); if (!BuildConfig.VERSION_CHANNEL.isEmpty()) ui->channelLabel->setText(tr("Channel") +": " + BuildConfig.VERSION_CHANNEL); diff --git a/launcher/ui/dialogs/AboutDialog.ui b/launcher/ui/dialogs/AboutDialog.ui index 6323992b..6eaa0c4e 100644 --- a/launcher/ui/dialogs/AboutDialog.ui +++ b/launcher/ui/dialogs/AboutDialog.ui @@ -184,12 +184,28 @@
- + IBeamCursor - Build Number: + Build Date: + + + Qt::AlignCenter + + + Qt::TextSelectableByMouse + + + + + + + IBeamCursor + + + Commit: Qt::AlignCenter diff --git a/launcher/updater/UpdateChecker.cpp b/launcher/updater/UpdateChecker.cpp index fa6e5a97..78d979ff 100644 --- a/launcher/updater/UpdateChecker.cpp +++ b/launcher/updater/UpdateChecker.cpp @@ -25,12 +25,11 @@ #include "BuildConfig.h" -UpdateChecker::UpdateChecker(shared_qobject_ptr nam, QString channelUrl, QString currentChannel, int currentBuild) +UpdateChecker::UpdateChecker(shared_qobject_ptr nam, QString channelUrl, QString currentChannel) { m_network = nam; m_channelUrl = channelUrl; m_currentChannel = currentChannel; - m_currentBuild = currentBuild; #ifdef Q_OS_MAC m_externalUpdater = new MacSparkleUpdater(); diff --git a/launcher/updater/UpdateChecker.h b/launcher/updater/UpdateChecker.h index 94e4312b..42ef318b 100644 --- a/launcher/updater/UpdateChecker.h +++ b/launcher/updater/UpdateChecker.h @@ -28,7 +28,7 @@ class UpdateChecker : public QObject Q_OBJECT public: - UpdateChecker(shared_qobject_ptr nam, QString channelUrl, QString currentChannel, int currentBuild); + UpdateChecker(shared_qobject_ptr nam, QString channelUrl, QString currentChannel); void checkForUpdate(const QString& updateChannel, bool notifyNoUpdate); /*! diff --git a/program_info/CMakeLists.txt b/program_info/CMakeLists.txt index b1ba89df..ac8ea6ce 100644 --- a/program_info/CMakeLists.txt +++ b/program_info/CMakeLists.txt @@ -15,7 +15,7 @@ set(Launcher_Copyright "${Launcher_Copyright}" PARENT_SCOPE) set(Launcher_Domain "polymc.org" PARENT_SCOPE) set(Launcher_Name "${Launcher_CommonName}" PARENT_SCOPE) set(Launcher_DisplayName "${Launcher_CommonName}" PARENT_SCOPE) -set(Launcher_UserAgent "${Launcher_CommonName}/${Launcher_RELEASE_VERSION_NAME}" PARENT_SCOPE) +set(Launcher_UserAgent "${Launcher_CommonName}/${Launcher_VERSION_NAME}" PARENT_SCOPE) set(Launcher_ConfigFile "polymc.cfg" PARENT_SCOPE) set(Launcher_Git "https://github.com/PolyMC/PolyMC" PARENT_SCOPE) set(Launcher_DesktopFileName "org.polymc.PolyMC.desktop" PARENT_SCOPE) diff --git a/program_info/org.polymc.PolyMC.metainfo.xml.in b/program_info/org.polymc.PolyMC.metainfo.xml.in index ea665655..0ce6c2db 100644 --- a/program_info/org.polymc.PolyMC.metainfo.xml.in +++ b/program_info/org.polymc.PolyMC.metainfo.xml.in @@ -48,7 +48,7 @@ - + moderate diff --git a/program_info/polymc.manifest.in b/program_info/polymc.manifest.in index 0eefacac..b85b6d46 100644 --- a/program_info/polymc.manifest.in +++ b/program_info/polymc.manifest.in @@ -1,6 +1,6 @@ - + diff --git a/program_info/polymc.rc.in b/program_info/polymc.rc.in index 0ea9b73a..be51ad71 100644 --- a/program_info/polymc.rc.in +++ b/program_info/polymc.rc.in @@ -7,7 +7,7 @@ IDI_ICON1 ICON DISCARDABLE "polymc.ico" 1 RT_MANIFEST "polymc.manifest" VS_VERSION_INFO VERSIONINFO -FILEVERSION @Launcher_RELEASE_VERSION_NAME4_COMMA@ +FILEVERSION @Launcher_VERSION_NAME4_COMMA@ FILEOS VOS_NT_WINDOWS32 FILETYPE VFT_APP BEGIN @@ -17,9 +17,9 @@ BEGIN BEGIN VALUE "CompanyName", "MultiMC & PolyMC Contributors" VALUE "FileDescription", "PolyMC" - VALUE "FileVersion", "@Launcher_RELEASE_VERSION_NAME4@" + VALUE "FileVersion", "@Launcher_VERSION_NAME4@" VALUE "ProductName", "PolyMC" - VALUE "ProductVersion", "@Launcher_RELEASE_VERSION_NAME4@" + VALUE "ProductVersion", "@Launcher_VERSION_NAME4@" END END BLOCK "VarFileInfo" diff --git a/program_info/win_install.nsi.in b/program_info/win_install.nsi.in index 84c3766e..87e266f8 100644 --- a/program_info/win_install.nsi.in +++ b/program_info/win_install.nsi.in @@ -102,13 +102,13 @@ OutFile "../@Launcher_CommonName@-Setup.exe" ;-------------------------------- ; Version info -VIProductVersion "@Launcher_RELEASE_VERSION_NAME4@" -VIFileVersion "@Launcher_RELEASE_VERSION_NAME4@" +VIProductVersion "@Launcher_VERSION_NAME4@" +VIFileVersion "@Launcher_VERSION_NAME4@" VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductName" "@Launcher_CommonName@" VIAddVersionKey /LANG=${LANG_ENGLISH} "FileDescription" "@Launcher_CommonName@ Installer" VIAddVersionKey /LANG=${LANG_ENGLISH} "LegalCopyright" "@Launcher_Copyright@" -VIAddVersionKey /LANG=${LANG_ENGLISH} "FileVersion" "@Launcher_RELEASE_VERSION_NAME4@" -VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductVersion" "@Launcher_RELEASE_VERSION_NAME4@" +VIAddVersionKey /LANG=${LANG_ENGLISH} "FileVersion" "@Launcher_VERSION_NAME4@" +VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductVersion" "@Launcher_VERSION_NAME4@" ;-------------------------------- @@ -145,8 +145,8 @@ Section "@Launcher_CommonName@" WriteRegStr HKCU "${UNINST_KEY}" "QuietUninstallString" '"$INSTDIR\uninstall.exe" /S' WriteRegStr HKCU "${UNINST_KEY}" "InstallLocation" "$INSTDIR" WriteRegStr HKCU "${UNINST_KEY}" "Publisher" "@Launcher_CommonName@ Contributors" - WriteRegStr HKCU "${UNINST_KEY}" "Version" "@Launcher_RELEASE_VERSION_NAME4@" - WriteRegStr HKCU "${UNINST_KEY}" "DisplayVersion" "@Launcher_RELEASE_VERSION_NAME@" + WriteRegStr HKCU "${UNINST_KEY}" "Version" "@Launcher_VERSION_NAME4@" + WriteRegStr HKCU "${UNINST_KEY}" "DisplayVersion" "@Launcher_VERSION_NAME@" WriteRegStr HKCU "${UNINST_KEY}" "VersionMajor" "@Launcher_VERSION_MAJOR@" WriteRegStr HKCU "${UNINST_KEY}" "VersionMinor" "@Launcher_VERSION_MINOR@" ${GetSize} "$INSTDIR" "/S=0K" $0 $1 $2 From 93507a263bc1f90355de7e5978dc762c832f61d3 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 12 Aug 2022 17:33:10 -0300 Subject: [PATCH 108/273] fix: hide 'More news...' button if the news aren't loaded yet Signed-off-by: flow --- launcher/ui/MainWindow.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index c3d95599..299401f5 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1465,6 +1465,7 @@ void MainWindow::updateNewsLabel() { newsLabel->setText(tr("Loading news...")); newsLabel->setEnabled(false); + ui->actionMoreNews->setVisible(false); } else { @@ -1473,11 +1474,13 @@ void MainWindow::updateNewsLabel() { newsLabel->setText(entries[0]->title); newsLabel->setEnabled(true); + ui->actionMoreNews->setVisible(true); } else { newsLabel->setText(tr("No news available.")); newsLabel->setEnabled(false); + ui->actionMoreNews->setVisible(false); } } } From 3c4b45c9e76db3373baccefda59bae01c460df28 Mon Sep 17 00:00:00 2001 From: Mitchell Skaggs Date: Sat, 13 Aug 2022 10:39:05 -0500 Subject: [PATCH 109/273] Use C locale codec for decoding This should correctly decode multi-byte non-UTF-8 text, such as Windows-936 (Simplified Chinese) Signed-off-by: Mitchell Skaggs --- launcher/LoggedProcess.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/LoggedProcess.h b/launcher/LoggedProcess.h index 91667b91..2360d1ea 100644 --- a/launcher/LoggedProcess.h +++ b/launcher/LoggedProcess.h @@ -89,8 +89,8 @@ private: void changeState(LoggedProcess::State state); private: - QTextDecoder m_err_decoder = QTextDecoder(QTextCodec::codecForName("UTF-8")); - QTextDecoder m_out_decoder = QTextDecoder(QTextCodec::codecForName("UTF-8")); + QTextDecoder m_err_decoder = QTextDecoder(QTextCodec::codecForLocale()); + QTextDecoder m_out_decoder = QTextDecoder(QTextCodec::codecForLocale()); bool m_killed = false; State m_state = NotRunning; int m_exit_code = 0; From 51c664a6784916ef0c421c341c920498eb3bbc48 Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Mon, 15 Aug 2022 20:34:56 +0200 Subject: [PATCH 110/273] fix: update org.polymc.PolyMC.metainfo.xml.in to not make flatpak break next release Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> --- .../org.polymc.PolyMC.metainfo.xml.in | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/program_info/org.polymc.PolyMC.metainfo.xml.in b/program_info/org.polymc.PolyMC.metainfo.xml.in index ea665655..0ddd7f43 100644 --- a/program_info/org.polymc.PolyMC.metainfo.xml.in +++ b/program_info/org.polymc.PolyMC.metainfo.xml.in @@ -6,7 +6,7 @@ org.polymc.PolyMC.desktop PolyMC - PolyMC Team + PolyMC A custom launcher for Minecraft that allows you to easily manage multiple installations of Minecraft at once CC0-1.0 GPL-3.0-only @@ -16,35 +16,39 @@

PolyMC is a custom launcher for Minecraft that focuses on predictability, long term stability and simplicity.

Features:

    -
  • Easily install game modifications, such as Fabric or Forge
  • +
  • Easily install game modifications, such as Fabric, Forge and Quilt
  • Control your java settings
  • Manage worlds and resource packs from the launcher
  • See logs and other details easily
  • Kill Minecraft in case of a crash/freeze
  • Isolate minecraft instances to keep everything clean
  • -
  • Install mods directly from the launcher
  • +
  • Install and update mods directly from the launcher
The main PolyMC window - https://polymc.org/img/screenshots/LauncherDark.png + https://polymc.org/img/screenshots/LauncherDark.png Modpack installation - https://polymc.org/img/screenshots/ModpackInstallDark.png + https://polymc.org/img/screenshots/ModpackInstallDark.png Mod installation - https://polymc.org/img/screenshots/ModInstallDark.png + https://polymc.org/img/screenshots/ModInstallDark.png + + + Mod updating + https://polymc.org/img/screenshots/ModUpdateDark.png Instance management - https://polymc.org/img/screenshots/PropertiesDark.png + https://polymc.org/img/screenshots/PropertiesDark.png Cat :) - https://polymc.org/img/screenshots/LauncherCatDark.png + https://polymc.org/img/screenshots/LauncherCatDark.png From 01505910f44a193510012a3b1b1f57b84d1d37c6 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Thu, 18 Aug 2022 18:06:45 +0200 Subject: [PATCH 111/273] refactor: move classpath definition into NewLaunch itself Signed-off-by: Sefa Eyeoglu --- launcher/minecraft/MinecraftInstance.cpp | 5 ++ .../minecraft/launch/LauncherPartLaunch.cpp | 25 +----- .../org/polymc/impl/OneSixLauncher.java | 86 +++++++++++++++---- 3 files changed, 76 insertions(+), 40 deletions(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 5a6f8de0..036659da 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -562,6 +562,11 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS if(!profile) return QString(); + for (auto cp : getClassPath()) + { + launchScript += "classPath " + cp + "\n"; + } + auto mainClass = getMainClass(); if (!mainClass.isEmpty()) { diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp index 63e4d90f..3b905bf5 100644 --- a/launcher/minecraft/launch/LauncherPartLaunch.cpp +++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp @@ -95,8 +95,8 @@ bool fitsInLocal8bit(const QString & string) void LauncherPartLaunch::executeTask() { - QString jarPath = APPLICATION->getJarPath("NewLaunch.jar"); - if (jarPath.isEmpty()) + QString newLaunchJar = APPLICATION->getJarPath("NewLaunch.jar"); + if (newLaunchJar.isEmpty()) { const char *reason = QT_TR_NOOP("Launcher library could not be found. Please check your installation."); emit logLine(tr(reason), MessageLevel::Fatal); @@ -119,9 +119,6 @@ void LauncherPartLaunch::executeTask() // make detachable - this will keep the process running even if the object is destroyed m_process.setDetachable(true); - auto classPath = minecraftInstance->getClassPath(); - classPath.prepend(jarPath); - auto natPath = minecraftInstance->getNativePath(); #ifdef Q_OS_WIN if (!fitsInLocal8bit(natPath)) @@ -137,23 +134,7 @@ void LauncherPartLaunch::executeTask() #endif args << "-cp"; -#ifdef Q_OS_WIN - QStringList processed; - for(auto & item: classPath) - { - if (!fitsInLocal8bit(item)) - { - processed << shortPathName(item); - } - else - { - processed << item; - } - } - args << processed.join(';'); -#else - args << classPath.join(':'); -#endif + args << newLaunchJar; args << "org.polymc.EntryPoint"; qDebug() << args.join(' '); diff --git a/libraries/launcher/org/polymc/impl/OneSixLauncher.java b/libraries/launcher/org/polymc/impl/OneSixLauncher.java index 362ff8d6..250fe0f2 100644 --- a/libraries/launcher/org/polymc/impl/OneSixLauncher.java +++ b/libraries/launcher/org/polymc/impl/OneSixLauncher.java @@ -1,16 +1,53 @@ -/* Copyright 2012-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. * - * http://www.apache.org/licenses/LICENSE-2.0 + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Linking this library statically or dynamically with other modules is + * making a combined work based on this library. Thus, the terms and + * conditions of the GNU General Public License cover the whole + * combination. + * + * As a special exception, the copyright holders of this library give + * you permission to link this library with independent modules to + * produce an executable, regardless of the license terms of these + * independent modules, and to copy and distribute the resulting + * executable under terms of your choice, provided that you also meet, + * for each linked independent module, the terms and conditions of the + * license of that module. An independent module is a module which is + * not derived from or based on this library. If you modify this + * library, you may extend this exception to your version of the + * library, but you are not obliged to do so. If you do not wish to do + * so, delete this exception statement from your version. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.polymc.impl; @@ -24,6 +61,9 @@ import java.applet.Applet; import java.io.File; import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Paths; import java.util.Collections; import java.util.List; import java.util.logging.Level; @@ -37,6 +77,7 @@ public final class OneSixLauncher implements Launcher { private static final Logger LOGGER = Logger.getLogger("OneSixLauncher"); // parameters, separated from ParamBucket + private final List classPath; private final List mcParams; private final List traits; private final String appletClass; @@ -53,11 +94,8 @@ public final class OneSixLauncher implements Launcher { private final String serverAddress; private final String serverPort; - private final ClassLoader classLoader; - public OneSixLauncher(Parameters params) { - classLoader = ClassLoader.getSystemClassLoader(); - + classPath = params.allSafe("classPath", Collections.emptyList()); mcParams = params.allSafe("param", Collections.emptyList()); mainClass = params.firstSafe("mainClass", "net.minecraft.client.Minecraft"); appletClass = params.firstSafe("appletClass", "net.minecraft.client.MinecraftApplet"); @@ -104,7 +142,7 @@ public final class OneSixLauncher implements Launcher { method.invoke(null, (Object) mcParams.toArray(new String[0])); } - private void legacyLaunch() throws Exception { + private void legacyLaunch(ClassLoader classLoader) throws Exception { // Get the Minecraft Class and set the base folder Class minecraftClass = classLoader.loadClass(mainClass); @@ -151,7 +189,7 @@ public final class OneSixLauncher implements Launcher { invokeMain(minecraftClass); } - private void launchWithMainClass() throws Exception { + private void launchWithMainClass(ClassLoader classLoader) throws Exception { // window size, title and state, onesix // FIXME: there is no good way to maximize the minecraft window in onesix. @@ -177,12 +215,24 @@ public final class OneSixLauncher implements Launcher { @Override public void launch() throws Exception { + URL[] classPathURLs = new URL[classPath.size()]; + for (int i = 0; i < classPath.size(); i++) { + File f = new File(classPath.get(i)); + classPathURLs[i] = f.toURI().toURL(); + } + // Some mod loaders (Fabric) read this property to determine the classpath. + String systemClassPath = System.getProperty("java.class.path"); + systemClassPath += File.pathSeparator + String.join(File.pathSeparator, classPath); + System.setProperty("java.class.path", systemClassPath); + + ClassLoader classLoader = new URLClassLoader(classPathURLs, getClass().getClassLoader()); + if (traits.contains("legacyLaunch") || traits.contains("alphaLaunch")) { // legacy launch uses the applet wrapper - legacyLaunch(); + legacyLaunch(classLoader); } else { // normal launch just calls main() - launchWithMainClass(); + launchWithMainClass(classLoader); } } From bb4861cf0d5b133da5b18a77700a3b827d2c7d13 Mon Sep 17 00:00:00 2001 From: Tayou Date: Thu, 18 Aug 2022 18:19:28 +0200 Subject: [PATCH 112/273] check for java installs in PATH on windows this should find java installs from scoop as well as any other installer, that registers java in the PATH environment variable. Signed-off-by: Tayou --- launcher/java/JavaUtils.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp index 2b19fca0..078b22b9 100644 --- a/launcher/java/JavaUtils.cpp +++ b/launcher/java/JavaUtils.cpp @@ -177,6 +177,12 @@ QStringList addJavasFromEnv(QList javas) QByteArray env = qgetenv("POLYMC_JAVA_PATHS"); #if defined(Q_OS_WIN32) QList javaPaths = QString::fromLocal8Bit(env).replace("\\", "/").split(QLatin1String(";")); + + QByteArray envPath = qgetenv("PATH"); + QList javaPathsfromPath = QString::fromLocal8Bit(envPath).replace("\\", "/").split(QLatin1String(";")); + for (QString string : javaPathsfromPath) { + javaPaths.append(string + "/javaw.exe"); + } #else QList javaPaths = QString::fromLocal8Bit(env).split(QLatin1String(":")); #endif From 26f31e9288f65a9e4eb507382cc00ad940e46b09 Mon Sep 17 00:00:00 2001 From: Tayou Date: Thu, 18 Aug 2022 23:16:50 +0200 Subject: [PATCH 113/273] use qEnvironmentVariable instead of qgetenv in JavaUtils Signed-off-by: Tayou --- launcher/java/JavaUtils.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/launcher/java/JavaUtils.cpp b/launcher/java/JavaUtils.cpp index 078b22b9..2f91605b 100644 --- a/launcher/java/JavaUtils.cpp +++ b/launcher/java/JavaUtils.cpp @@ -174,17 +174,17 @@ JavaInstallPtr JavaUtils::GetDefaultJava() QStringList addJavasFromEnv(QList javas) { - QByteArray env = qgetenv("POLYMC_JAVA_PATHS"); + auto env = qEnvironmentVariable("POLYMC_JAVA_PATHS"); #if defined(Q_OS_WIN32) - QList javaPaths = QString::fromLocal8Bit(env).replace("\\", "/").split(QLatin1String(";")); + QList javaPaths = env.replace("\\", "/").split(QLatin1String(";")); - QByteArray envPath = qgetenv("PATH"); - QList javaPathsfromPath = QString::fromLocal8Bit(envPath).replace("\\", "/").split(QLatin1String(";")); + auto envPath = qEnvironmentVariable("PATH"); + QList javaPathsfromPath = envPath.replace("\\", "/").split(QLatin1String(";")); for (QString string : javaPathsfromPath) { javaPaths.append(string + "/javaw.exe"); } #else - QList javaPaths = QString::fromLocal8Bit(env).split(QLatin1String(":")); + QList javaPaths = env.split(QLatin1String(":")); #endif for(QString i : javaPaths) { From 6b6a095b911955524cf8f6bda769bb8ed048f30f Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Fri, 19 Aug 2022 18:42:26 +0200 Subject: [PATCH 114/273] fix(COPYING): fix COPYING.md by adding some missing copyright notices Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> --- COPYING.md | 83 +++++++++++++++++++++++++++++++++++---------- libraries/README.md | 10 +++--- 2 files changed, 71 insertions(+), 22 deletions(-) diff --git a/COPYING.md b/COPYING.md index 1674a620..c94c51c3 100644 --- a/COPYING.md +++ b/COPYING.md @@ -32,27 +32,47 @@ See the License for the specific language governing permissions and limitations under the License. -## MinGW runtime (Windows) +## MinGW-w64 runtime (Windows) - Copyright (c) 2012 MinGW.org project + Copyright (c) 2009, 2010, 2011, 2012, 2013 by the mingw-w64 project - Permission is hereby granted, free of charge, to any person obtaining a - copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: + This license has been certified as open source. It has also been designated + as GPL compatible by the Free Software Foundation (FSF). - The above copyright notice, this permission notice and the below disclaimer - shall be included in all copies or substantial portions of the Software. + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - DEALINGS IN THE SOFTWARE. + 1. Redistributions in source code must retain the accompanying copyright + notice, this list of conditions, and the following disclaimer. + 2. Redistributions in binary form must reproduce the accompanying + copyright notice, this list of conditions, and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + 3. Names of the copyright holders must not be used to endorse or promote + products derived from this software without prior written permission + from the copyright holders. + 4. The right to distribute this software or to use it for any purpose does + not give you the right to use Servicemarks (sm) or Trademarks (tm) of + the copyright holders. Use of them is covered by separate agreement + with the copyright holders. + 5. If any files are modified, you must cause the modified files to carry + prominent notices stating that you changed the files and the date of + any change. + + Disclaimer + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY EXPRESSED + OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + Information on third party licenses used in MinGW-w64 can be found in its COPYING.MinGW-w64-runtime.txt. ## Qt 5/6 @@ -345,3 +365,32 @@ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +## Gamemode + + Copyright (c) 2017-2022, Feral Interactive + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + * Neither the name of Feral Interactive nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + POSSIBILITY OF SUCH DAMAGE. diff --git a/libraries/README.md b/libraries/README.md index e58f2273..8e4bd61b 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -16,7 +16,7 @@ A performance optimization daemon. See [github repo](https://github.com/FeralInteractive/gamemode). -BSD licensed +BSD-3-Clause licensed ## hoedown @@ -161,9 +161,9 @@ Public domain (the author disclaimed the copyright). ## quazip -A zip manipulation library, forked for MultiMC's use. +A zip manipulation library. -LGPL 2.1 +LGPL 2.1 with linking exception. ## rainbow @@ -173,7 +173,7 @@ Available either under LGPL version 2.1 or later. ## systeminfo -A MultiMC-specific library for probing system information. +A PolyMC-specific library for probing system information. Apache 2.0 @@ -187,6 +187,6 @@ Licenced under the MIT licence. ## xz-embedded -Tiny implementation of LZMA2 de/compression. This format is only used by Forge to save bandwidth. +Tiny implementation of LZMA2 de/compression. This format was only used by Forge to save bandwidth. Public domain. From afb9ebcd999e2f290ddf81f13ce4a5b4126c40c2 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Mon, 8 Aug 2022 23:40:25 +0200 Subject: [PATCH 115/273] fix: distinguish Coremods Signed-off-by: Sefa Eyeoglu --- launcher/ui/pages/instance/ModFolderPage.cpp | 50 ++++++++++---------- launcher/ui/pages/instance/ModFolderPage.h | 8 +++- 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 1b2cde0c..45678db1 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -112,10 +112,6 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr } } -CoreModFolderPage::CoreModFolderPage(BaseInstance* inst, std::shared_ptr mods, QWidget* parent) - : ModFolderPage(inst, mods, parent) -{} - void ModFolderPage::runningStateChanged(bool running) { ExternalResourcesPage::runningStateChanged(running); @@ -128,27 +124,6 @@ bool ModFolderPage::shouldDisplay() const return true; } -bool CoreModFolderPage::shouldDisplay() const -{ - if (ModFolderPage::shouldDisplay()) { - auto inst = dynamic_cast(m_instance); - if (!inst) - return true; - - auto version = inst->getPackProfile(); - - if (!version) - return true; - if (!version->getComponent("net.minecraftforge")) - return false; - if (!version->getComponent("net.minecraft")) - return false; - if (version->getComponent("net.minecraft")->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate) - return true; - } - return false; -} - void ModFolderPage::installMods() { if (!m_controlsEnabled) @@ -252,3 +227,28 @@ void ModFolderPage::updateMods() m_model->update(); } } + +CoreModFolderPage::CoreModFolderPage(BaseInstance* inst, std::shared_ptr mods, QWidget* parent) + : ModFolderPage(inst, mods, parent) +{} + +bool CoreModFolderPage::shouldDisplay() const +{ + if (ModFolderPage::shouldDisplay()) { + auto inst = dynamic_cast(m_instance); + if (!inst) + return true; + + auto version = inst->getPackProfile(); + + if (!version) + return true; + if (!version->getComponent("net.minecraftforge")) + return false; + if (!version->getComponent("net.minecraft")) + return false; + if (version->getComponent("net.minecraft")->getReleaseDateTime() < g_VersionFilterData.legacyCutoffDate) + return true; + } + return false; +} diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index 93889707..7e305951 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -64,5 +64,11 @@ class CoreModFolderPage : public ModFolderPage { public: explicit CoreModFolderPage(BaseInstance* inst, std::shared_ptr mods, QWidget* parent = 0); virtual ~CoreModFolderPage() = default; - virtual bool shouldDisplay() const; + + virtual QString displayName() const override { return tr("Core mods"); } + virtual QIcon icon() const override { return APPLICATION->getThemedIcon("coremods"); } + virtual QString id() const override { return "coremods"; } + virtual QString helpPage() const override { return "Core-mods"; } + + virtual bool shouldDisplay() const override; }; From 2d63c860227f4a539526a85d8999f867ae67ce43 Mon Sep 17 00:00:00 2001 From: flow Date: Tue, 9 Aug 2022 01:26:53 -0300 Subject: [PATCH 116/273] feat: make Task a QRunnable This makes it possible to run a task in another thread. I added a variable to toggle debug prints because they seem to trigger an assertion on Qt internals when the task in on another thread. Of course, this isn't awesome, but can wait until we improve our logging. Signed-off-by: flow --- launcher/tasks/Task.cpp | 24 ++++++++++++++++-------- launcher/tasks/Task.h | 12 ++++++++++-- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/launcher/tasks/Task.cpp b/launcher/tasks/Task.cpp index bb71b98c..b4babdd4 100644 --- a/launcher/tasks/Task.cpp +++ b/launcher/tasks/Task.cpp @@ -37,8 +37,9 @@ #include -Task::Task(QObject *parent) : QObject(parent) +Task::Task(QObject *parent, bool show_debug) : QObject(parent), m_show_debug(show_debug) { + setAutoDelete(false); } void Task::setStatus(const QString &new_status) @@ -63,27 +64,32 @@ void Task::start() { case State::Inactive: { - qDebug() << "Task" << describe() << "starting for the first time"; + if (m_show_debug) + qDebug() << "Task" << describe() << "starting for the first time"; break; } case State::AbortedByUser: { - qDebug() << "Task" << describe() << "restarting for after being aborted by user"; + if (m_show_debug) + qDebug() << "Task" << describe() << "restarting for after being aborted by user"; break; } case State::Failed: { - qDebug() << "Task" << describe() << "restarting for after failing at first"; + if (m_show_debug) + qDebug() << "Task" << describe() << "restarting for after failing at first"; break; } case State::Succeeded: { - qDebug() << "Task" << describe() << "restarting for after succeeding at first"; + if (m_show_debug) + qDebug() << "Task" << describe() << "restarting for after succeeding at first"; break; } case State::Running: { - qWarning() << "The launcher tried to start task" << describe() << "while it was already running!"; + if (m_show_debug) + qWarning() << "The launcher tried to start task" << describe() << "while it was already running!"; return; } } @@ -118,7 +124,8 @@ void Task::emitAborted() } m_state = State::AbortedByUser; m_failReason = "Aborted."; - qDebug() << "Task" << describe() << "aborted."; + if (m_show_debug) + qDebug() << "Task" << describe() << "aborted."; emit aborted(); emit finished(); } @@ -132,7 +139,8 @@ void Task::emitSucceeded() return; } m_state = State::Succeeded; - qDebug() << "Task" << describe() << "succeeded"; + if (m_show_debug) + qDebug() << "Task" << describe() << "succeeded"; emit succeeded(); emit finished(); } diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index aafaf68c..2baf0188 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -35,9 +35,11 @@ #pragma once +#include + #include "QObjectPtr.h" -class Task : public QObject { +class Task : public QObject, public QRunnable { Q_OBJECT public: using Ptr = shared_qobject_ptr; @@ -45,7 +47,7 @@ class Task : public QObject { enum class State { Inactive, Running, Succeeded, Failed, AbortedByUser }; public: - explicit Task(QObject* parent = 0); + explicit Task(QObject* parent = 0, bool show_debug_log = true); virtual ~Task() = default; bool isRunning() const; @@ -95,6 +97,9 @@ class Task : public QObject { void stepStatus(QString status); public slots: + // QRunnable's interface + void run() override { start(); } + virtual void start(); virtual bool abort() { if(canAbort()) emitAborted(); return canAbort(); }; @@ -117,4 +122,7 @@ class Task : public QObject { QString m_status; int m_progress = 0; int m_progressTotal = 100; + + // TODO: Nuke in favor of QLoggingCategory + bool m_show_debug = true; }; From 3225f514f64533394e14bf7aee4e61c19a72ed2f Mon Sep 17 00:00:00 2001 From: flow Date: Tue, 9 Aug 2022 01:53:50 -0300 Subject: [PATCH 117/273] refactor: move general info from Mod to Resource This allows us to create other resources that are not Mods, but can still share a significant portion of code. Signed-off-by: flow --- launcher/CMakeLists.txt | 2 + launcher/MMCZip.cpp | 6 +- launcher/minecraft/MinecraftInstance.cpp | 2 +- launcher/minecraft/mod/Mod.cpp | 74 +++------------------ launcher/minecraft/mod/Mod.h | 45 ++----------- launcher/minecraft/mod/Resource.cpp | 53 +++++++++++++++ launcher/minecraft/mod/Resource.h | 74 +++++++++++++++++++++ launcher/modplatform/EnsureMetadataTask.cpp | 2 +- launcher/ui/widgets/MCModInfoFrame.cpp | 2 +- 9 files changed, 150 insertions(+), 110 deletions(-) create mode 100644 launcher/minecraft/mod/Resource.cpp create mode 100644 launcher/minecraft/mod/Resource.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index cff07b4b..70997e51 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -318,6 +318,8 @@ set(MINECRAFT_SOURCES minecraft/mod/ModDetails.h minecraft/mod/ModFolderModel.h minecraft/mod/ModFolderModel.cpp + minecraft/mod/Resource.h + minecraft/mod/Resource.cpp minecraft/mod/ResourcePackFolderModel.h minecraft/mod/ResourcePackFolderModel.cpp minecraft/mod/TexturePackFolderModel.h diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index 04ca5094..9f4e968f 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -148,7 +148,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const // do not merge disabled mods. if (!mod->enabled()) continue; - if (mod->type() == Mod::MOD_ZIPFILE) + if (mod->type() == ResourceType::ZIPFILE) { if (!mergeZipFiles(&zipOut, mod->fileinfo(), addedFiles)) { @@ -158,7 +158,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const return false; } } - else if (mod->type() == Mod::MOD_SINGLEFILE) + else if (mod->type() == ResourceType::SINGLEFILE) { // FIXME: buggy - does not work with addedFiles auto filename = mod->fileinfo(); @@ -171,7 +171,7 @@ bool MMCZip::createModdedJar(QString sourceJarPath, QString targetJarPath, const } addedFiles.insert(filename.fileName()); } - else if (mod->type() == Mod::MOD_FOLDER) + else if (mod->type() == ResourceType::FOLDER) { // untested, but seems to be unused / not possible to reach // FIXME: buggy - does not work with addedFiles diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index c677b677..b42aeda3 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -714,7 +714,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr }); for(auto mod: modList) { - if(mod->type() == Mod::MOD_FOLDER) + if(mod->type() == ResourceType::FOLDER) { out << u8" [🖿] " + mod->fileinfo().completeBaseName() + " (folder)"; continue; diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index 588d76e3..f28fd32a 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -36,13 +36,10 @@ #include "Mod.h" +#include #include #include -#include -#include - -#include "Application.h" #include "MetadataHandler.h" namespace { @@ -51,75 +48,27 @@ ModDetails invalidDetails; } -Mod::Mod(const QFileInfo& file) +Mod::Mod(const QFileInfo& file) : Resource(file) { - repath(file); - m_changedDateTime = file.lastModified(); + m_enabled = (file.suffix() != "disabled"); } Mod::Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata) - : m_file(mods_dir.absoluteFilePath(metadata.filename)) - , m_internal_id(metadata.filename) - , m_name(metadata.name) + : Mod(mods_dir.absoluteFilePath(metadata.filename)) { - if (m_file.isDir()) { - m_type = MOD_FOLDER; - } else { - if (metadata.filename.endsWith(".zip") || metadata.filename.endsWith(".jar")) - m_type = MOD_ZIPFILE; - else if (metadata.filename.endsWith(".litemod")) - m_type = MOD_LITEMOD; - else - m_type = MOD_SINGLEFILE; - } - - m_enabled = true; - m_changedDateTime = m_file.lastModified(); - + m_name = metadata.name; m_temp_metadata = std::make_shared(std::move(metadata)); } -void Mod::repath(const QFileInfo& file) -{ - m_file = file; - QString name_base = file.fileName(); - - m_type = Mod::MOD_UNKNOWN; - - m_internal_id = name_base; - - if (m_file.isDir()) { - m_type = MOD_FOLDER; - m_name = name_base; - } else if (m_file.isFile()) { - if (name_base.endsWith(".disabled")) { - m_enabled = false; - name_base.chop(9); - } else { - m_enabled = true; - } - if (name_base.endsWith(".zip") || name_base.endsWith(".jar")) { - m_type = MOD_ZIPFILE; - name_base.chop(4); - } else if (name_base.endsWith(".litemod")) { - m_type = MOD_LITEMOD; - name_base.chop(8); - } else { - m_type = MOD_SINGLEFILE; - } - m_name = name_base; - } -} - auto Mod::enable(bool value) -> bool { - if (m_type == Mod::MOD_UNKNOWN || m_type == Mod::MOD_FOLDER) + if (m_type == ResourceType::UNKNOWN || m_type == ResourceType::FOLDER) return false; if (m_enabled == value) return false; - QString path = m_file.absoluteFilePath(); + QString path = m_file_info.absoluteFilePath(); QFile file(path); if (value) { if (!path.endsWith(".disabled")) @@ -136,7 +85,7 @@ auto Mod::enable(bool value) -> bool } if (status() == ModStatus::NoMetadata) - repath(QFileInfo(path)); + setFile(QFileInfo(path)); m_enabled = value; return true; @@ -175,8 +124,7 @@ auto Mod::destroy(QDir& index_dir, bool preserve_metadata) -> bool } } - m_type = MOD_UNKNOWN; - return FS::deletePath(m_file.filePath()); + return Resource::destroy(); } auto Mod::details() const -> const ModDetails& @@ -239,8 +187,8 @@ auto Mod::metadata() const -> const std::shared_ptr void Mod::finishResolvingWithDetails(std::shared_ptr details) { - m_resolving = false; - m_resolved = true; + m_is_resolving = false; + m_is_resolved = true; m_localDetails = details; setStatus(m_temp_status); diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index 7a13e44b..313c478c 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -39,38 +39,23 @@ #include #include -#include "QObjectPtr.h" +#include "Resource.h" #include "ModDetails.h" -class Mod : public QObject +class Mod : public Resource { Q_OBJECT public: - enum ModType - { - MOD_UNKNOWN, //!< Indicates an unspecified mod type. - MOD_ZIPFILE, //!< The mod is a zip file containing the mod's class files. - MOD_SINGLEFILE, //!< The mod is a single file (not a zip file). - MOD_FOLDER, //!< The mod is in a folder on the filesystem. - MOD_LITEMOD, //!< The mod is a litemod - }; - using Ptr = shared_qobject_ptr; Mod() = default; Mod(const QFileInfo &file); explicit Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata); - auto fileinfo() const -> QFileInfo { return m_file; } - auto dateTimeChanged() const -> QDateTime { return m_changedDateTime; } - auto internal_id() const -> QString { return m_internal_id; } - auto type() const -> ModType { return m_type; } auto enabled() const -> bool { return m_enabled; } - auto valid() const -> bool { return m_type != MOD_UNKNOWN; } - auto details() const -> const ModDetails&; - auto name() const -> QString; + auto name() const -> QString override; auto version() const -> QString; auto homeurl() const -> QString; auto description() const -> QString; @@ -85,31 +70,12 @@ public: auto enable(bool value) -> bool; - // delete all the files of this mod + // Delete all the files of this mod auto destroy(QDir& index_dir, bool preserve_metadata = false) -> bool; - // change the mod's filesystem path (used by mod lists for *MAGIC* purposes) - void repath(const QFileInfo &file); - - auto shouldResolve() const -> bool { return !m_resolving && !m_resolved; } - auto isResolving() const -> bool { return m_resolving; } - auto resolutionTicket() const -> int { return m_resolutionTicket; } - - void setResolving(bool resolving, int resolutionTicket) { - m_resolving = resolving; - m_resolutionTicket = resolutionTicket; - } void finishResolvingWithDetails(std::shared_ptr details); protected: - QFileInfo m_file; - QDateTime m_changedDateTime; - - QString m_internal_id; - /* Name as reported via the file name */ - QString m_name; - ModType m_type = MOD_UNKNOWN; - /* If the mod has metadata, this will be filled in the constructor, and passed to * the ModDetails when calling finishResolvingWithDetails */ std::shared_ptr m_temp_metadata; @@ -120,7 +86,4 @@ protected: std::shared_ptr m_localDetails; bool m_enabled = true; - bool m_resolving = false; - bool m_resolved = false; - int m_resolutionTicket = 0; }; diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp new file mode 100644 index 00000000..8771a20f --- /dev/null +++ b/launcher/minecraft/mod/Resource.cpp @@ -0,0 +1,53 @@ +#include "Resource.h" + +#include "FileSystem.h" + +Resource::Resource(QObject* parent) : QObject(parent) {} + +Resource::Resource(QFileInfo file_info) : QObject() +{ + setFile(file_info); +} + +void Resource::setFile(QFileInfo file_info) +{ + m_file_info = file_info; + parseFile(); +} + +void Resource::parseFile() +{ + QString file_name{ m_file_info.fileName() }; + + m_type = ResourceType::UNKNOWN; + + m_internal_id = file_name; + + if (m_file_info.isDir()) { + m_type = ResourceType::FOLDER; + m_name = file_name; + } else if (m_file_info.isFile()) { + if (file_name.endsWith(".disabled")) + file_name.chop(9); + + if (file_name.endsWith(".zip") || file_name.endsWith(".jar")) { + m_type = ResourceType::ZIPFILE; + file_name.chop(4); + } else if (file_name.endsWith(".litemod")) { + m_type = ResourceType::LITEMOD; + file_name.chop(8); + } else { + m_type = ResourceType::SINGLEFILE; + } + + m_name = file_name; + } + + m_changed_date_time = m_file_info.lastModified(); +} + +bool Resource::destroy() +{ + m_type = ResourceType::UNKNOWN; + return FS::deletePath(m_file_info.filePath()); +} diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h new file mode 100644 index 00000000..c348c7e2 --- /dev/null +++ b/launcher/minecraft/mod/Resource.h @@ -0,0 +1,74 @@ +#pragma once + +#include +#include +#include + +#include "QObjectPtr.h" + +enum class ResourceType { + UNKNOWN, //!< Indicates an unspecified resource type. + ZIPFILE, //!< The resource is a zip file containing the resource's class files. + SINGLEFILE, //!< The resource is a single file (not a zip file). + FOLDER, //!< The resource is in a folder on the filesystem. + LITEMOD, //!< The resource is a litemod +}; + +/** General class for managed resources. It mirrors a file in disk, with some more info + * for display and house-keeping purposes. + * + * Subclass it to add additional data / behavior, such as Mods or Resource packs. + */ +class Resource : public QObject { + Q_OBJECT + Q_DISABLE_COPY(Resource) + public: + using Ptr = shared_qobject_ptr; + + Resource(QObject* parent = nullptr); + Resource(QFileInfo file_info); + ~Resource() override = default; + + void setFile(QFileInfo file_info); + void parseFile(); + + [[nodiscard]] auto fileinfo() const -> QFileInfo { return m_file_info; } + [[nodiscard]] auto dateTimeChanged() const -> QDateTime { return m_changed_date_time; } + [[nodiscard]] auto internal_id() const -> QString { return m_internal_id; } + [[nodiscard]] auto type() const -> ResourceType { return m_type; } + + [[nodiscard]] virtual auto name() const -> QString { return m_name; } + [[nodiscard]] virtual bool valid() const { return m_type != ResourceType::UNKNOWN; } + + [[nodiscard]] auto shouldResolve() const -> bool { return !m_is_resolving && !m_is_resolved; } + [[nodiscard]] auto isResolving() const -> bool { return m_is_resolving; } + [[nodiscard]] auto resolutionTicket() const -> int { return m_resolution_ticket; } + + void setResolving(bool resolving, int resolutionTicket) + { + m_is_resolving = resolving; + m_resolution_ticket = resolutionTicket; + } + + // Delete all files of this resource. + bool destroy(); + + protected: + /* The file corresponding to this resource. */ + QFileInfo m_file_info; + /* The cached date when this file was last changed. */ + QDateTime m_changed_date_time; + + /* Internal ID for internal purposes. Properties such as human-readability should not be assumed. */ + QString m_internal_id; + /* Name as reported via the file name. In the absence of a better name, this is shown to the user. */ + QString m_name; + + /* The type of file we're dealing with. */ + ResourceType m_type = ResourceType::UNKNOWN; + + /* Used to keep trach of pending / concluded actions on the resource. */ + bool m_is_resolving = false; + bool m_is_resolved = false; + int m_resolution_ticket = 0; +}; diff --git a/launcher/modplatform/EnsureMetadataTask.cpp b/launcher/modplatform/EnsureMetadataTask.cpp index 60c54c4e..21c20b28 100644 --- a/launcher/modplatform/EnsureMetadataTask.cpp +++ b/launcher/modplatform/EnsureMetadataTask.cpp @@ -110,7 +110,7 @@ void EnsureMetadataTask::executeTask() } // Folders don't have metadata - if (mod->type() == Mod::MOD_FOLDER) { + if (mod->type() == ResourceType::FOLDER) { emitReady(mod); } } diff --git a/launcher/ui/widgets/MCModInfoFrame.cpp b/launcher/ui/widgets/MCModInfoFrame.cpp index 7d78006b..22475abc 100644 --- a/launcher/ui/widgets/MCModInfoFrame.cpp +++ b/launcher/ui/widgets/MCModInfoFrame.cpp @@ -23,7 +23,7 @@ void MCModInfoFrame::updateWithMod(Mod &m) { - if (m.type() == m.MOD_FOLDER) + if (m.type() == ResourceType::FOLDER) { clear(); return; From ec62d8e97334d3b5a30cea00858e7035468f3609 Mon Sep 17 00:00:00 2001 From: flow Date: Tue, 9 Aug 2022 01:58:22 -0300 Subject: [PATCH 118/273] refactor: move general code from mod model to its own model This aims to continue decoupling other types of resources (e.g. resource packs, shader packs, etc) from mods, so that we don't have to continuously watch our backs for changes to one of them affecting the others. To do so, this creates a more general list model for resources, based on the mods one, that allows you to extend it with functionality for other resources. I had to do some template and preprocessor stuff to get around the QObject limitation of not allowing templated classes, so that's sadge :c On the other hand, I tried cleaning up most general-purpose code in the mod model, and added some documentation, because it looks nice :D Signed-off-by: flow --- launcher/CMakeLists.txt | 3 + launcher/minecraft/mod/ModFolderModel.cpp | 426 +++--------------- launcher/minecraft/mod/ModFolderModel.h | 93 +--- .../minecraft/mod/ResourceFolderModel.cpp | 336 ++++++++++++++ launcher/minecraft/mod/ResourceFolderModel.h | 274 +++++++++++ .../minecraft/mod/tasks/BasicFolderLoadTask.h | 44 ++ .../minecraft/mod/tasks/LocalModParseTask.cpp | 16 +- .../minecraft/mod/tasks/LocalModParseTask.h | 15 +- .../minecraft/mod/tasks/ModFolderLoadTask.cpp | 8 +- .../minecraft/mod/tasks/ModFolderLoadTask.h | 12 +- .../pages/instance/ExternalResourcesPage.cpp | 8 +- 11 files changed, 779 insertions(+), 456 deletions(-) create mode 100644 launcher/minecraft/mod/ResourceFolderModel.cpp create mode 100644 launcher/minecraft/mod/ResourceFolderModel.h create mode 100644 launcher/minecraft/mod/tasks/BasicFolderLoadTask.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 70997e51..d1e0befd 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -320,10 +320,13 @@ set(MINECRAFT_SOURCES minecraft/mod/ModFolderModel.cpp minecraft/mod/Resource.h minecraft/mod/Resource.cpp + minecraft/mod/ResourceFolderModel.h + minecraft/mod/ResourceFolderModel.cpp minecraft/mod/ResourcePackFolderModel.h minecraft/mod/ResourcePackFolderModel.cpp minecraft/mod/TexturePackFolderModel.h minecraft/mod/TexturePackFolderModel.cpp + minecraft/mod/tasks/BasicFolderLoadTask.h minecraft/mod/tasks/ModFolderLoadTask.h minecraft/mod/tasks/ModFolderLoadTask.cpp minecraft/mod/tasks/LocalModParseTask.h diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index d4c5e819..597f9807 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -49,226 +49,91 @@ #include "minecraft/mod/tasks/LocalModParseTask.h" #include "minecraft/mod/tasks/ModFolderLoadTask.h" -ModFolderModel::ModFolderModel(const QString &dir, bool is_indexed) : QAbstractListModel(), m_dir(dir), m_is_indexed(is_indexed) +ModFolderModel::ModFolderModel(const QString &dir, bool is_indexed) : ResourceFolderModel(dir), m_is_indexed(is_indexed) { FS::ensureFolderPathExists(m_dir.absolutePath()); - m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); - m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); - m_watcher = new QFileSystemWatcher(this); - connect(m_watcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString))); +} + +Task* ModFolderModel::createUpdateTask() +{ + auto index_dir = indexDir(); + auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load); + m_first_folder_load = false; + return task; } void ModFolderModel::startWatching() { - if(is_watching) - return; - // Remove orphaned metadata next time m_first_folder_load = true; - - update(); - - // Watch the mods folder - is_watching = m_watcher->addPath(m_dir.absolutePath()); - if (is_watching) { - qDebug() << "Started watching " << m_dir.absolutePath(); - } else { - qDebug() << "Failed to start watching " << m_dir.absolutePath(); - } - - // Watch the mods index folder - is_watching = m_watcher->addPath(indexDir().absolutePath()); - if (is_watching) { - qDebug() << "Started watching " << indexDir().absolutePath(); - } else { - qDebug() << "Failed to start watching " << indexDir().absolutePath(); - } + ResourceFolderModel::startWatching({ m_dir.absolutePath(), indexDir().absolutePath() }); } void ModFolderModel::stopWatching() { - if(!is_watching) - return; - - is_watching = !m_watcher->removePath(m_dir.absolutePath()); - if (!is_watching) { - qDebug() << "Stopped watching " << m_dir.absolutePath(); - } else { - qDebug() << "Failed to stop watching " << m_dir.absolutePath(); - } - - is_watching = !m_watcher->removePath(indexDir().absolutePath()); - if (!is_watching) { - qDebug() << "Stopped watching " << indexDir().absolutePath(); - } else { - qDebug() << "Failed to stop watching " << indexDir().absolutePath(); - } + ResourceFolderModel::stopWatching({ m_dir.absolutePath(), indexDir().absolutePath() }); } -bool ModFolderModel::update() +void ModFolderModel::onUpdateSucceeded() { - if (!isValid()) { - return false; - } - if(m_update) { - scheduled_update = true; - return true; - } + auto update_results = static_cast(m_current_update_task.get())->result(); - auto index_dir = indexDir(); - auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load); - m_first_folder_load = false; + auto& new_mods = update_results->mods; - m_update = task->result(); - - QThreadPool *threadPool = QThreadPool::globalInstance(); - connect(task, &ModFolderLoadTask::succeeded, this, &ModFolderModel::finishUpdate); - - threadPool->start(task); - return true; -} - -void ModFolderModel::finishUpdate() -{ #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) - auto currentList = modsIndex.keys(); - QSet currentSet(currentList.begin(), currentList.end()); - auto & newMods = m_update->mods; - auto newList = newMods.keys(); - QSet newSet(newList.begin(), newList.end()); + auto current_list = m_resources_index.keys(); + QSet current_set(current_list.begin(), current_list.end()); + + auto new_list = new_mods.keys(); + QSet new_set(new_list.begin(), new_list.end()); #else - QSet currentSet = modsIndex.keys().toSet(); - auto& newMods = m_update->mods; - QSet newSet = newMods.keys().toSet(); + QSet current_set(m_resources_index.keys().toSet()); + QSet new_set(new_mods.keys().toSet()); #endif - // see if the kept mods changed in some way - { - QSet kept = currentSet; - kept.intersect(newSet); - for(auto& keptMod : kept) { - auto newMod = newMods[keptMod]; - auto row = modsIndex[keptMod]; - auto currentMod = mods[row]; - if(newMod->dateTimeChanged() == currentMod->dateTimeChanged()) { - // no significant change, ignore... - continue; - } - auto oldMod = mods[row]; - if(oldMod->isResolving()) { - activeTickets.remove(oldMod->resolutionTicket()); - } - - mods[row] = newMod; - resolveMod(mods[row]); - emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1)); - } - } - - // remove mods no longer present - { - QSet removed = currentSet; - QList removedRows; - removed.subtract(newSet); - for(auto & removedMod: removed) { - removedRows.append(modsIndex[removedMod]); - } - std::sort(removedRows.begin(), removedRows.end(), std::greater()); - for(auto iter = removedRows.begin(); iter != removedRows.end(); iter++) { - int removedIndex = *iter; - beginRemoveRows(QModelIndex(), removedIndex, removedIndex); - auto removedIter = mods.begin() + removedIndex; - if((*removedIter)->isResolving()) { - activeTickets.remove((*removedIter)->resolutionTicket()); - } - - mods.erase(removedIter); - endRemoveRows(); - } - } - - // add new mods to the end - { - QSet added = newSet; - added.subtract(currentSet); - - // When you have a Qt build with assertions turned on, proceeding here will abort the application - if (added.size() > 0) { - beginInsertRows(QModelIndex(), mods.size(), mods.size() + added.size() - 1); - for (auto& addedMod : added) { - mods.append(newMods[addedMod]); - resolveMod(mods.last()); - } - endInsertRows(); - } - } - - // update index - { - modsIndex.clear(); - int idx = 0; - for(auto mod: mods) { - modsIndex[mod->internal_id()] = idx; - idx++; - } - } - - m_update.reset(); + applyUpdates(current_set, new_set, new_mods); + + update_results.reset(); + m_current_update_task.reset(); emit updateFinished(); - if(scheduled_update) { - scheduled_update = false; + if(m_scheduled_update) { + m_scheduled_update = false; update(); } } -void ModFolderModel::resolveMod(Mod::Ptr m) +Task* ModFolderModel::createParseTask(Resource const& resource) { - if(!m->shouldResolve()) { - return; - } - - auto task = new LocalModParseTask(nextResolutionTicket, m->type(), m->fileinfo()); - auto result = task->result(); - result->id = m->internal_id(); - activeTickets.insert(nextResolutionTicket, result); - m->setResolving(true, nextResolutionTicket); - nextResolutionTicket++; - QThreadPool *threadPool = QThreadPool::globalInstance(); - connect(task, &LocalModParseTask::finished, this, &ModFolderModel::finishModParse); - threadPool->start(task); + return new LocalModParseTask(m_next_resolution_ticket, resource.type(), resource.fileinfo()); } -void ModFolderModel::finishModParse(int token) +void ModFolderModel::onParseSucceeded(int ticket, QString mod_id) { - auto iter = activeTickets.find(token); - if(iter == activeTickets.end()) { + auto iter = m_active_parse_tasks.constFind(ticket); + if (iter == m_active_parse_tasks.constEnd()) return; - } - auto result = *iter; - activeTickets.remove(token); - int row = modsIndex[result->id]; - auto mod = mods[row]; - mod->finishResolvingWithDetails(result->details); + + int row = m_resources_index[mod_id]; + + auto parse_task = *iter; + auto cast_task = static_cast(parse_task.get()); + + Q_ASSERT(cast_task->token() == ticket); + + auto resource = find(mod_id); + + auto result = cast_task->result(); + if (result && resource) + resource->finishResolvingWithDetails(result->details); + emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1)); + + parse_task->deleteLater(); + m_active_parse_tasks.remove(ticket); } -void ModFolderModel::disableInteraction(bool disabled) -{ - if (interaction_disabled == disabled) { - return; - } - interaction_disabled = disabled; - if(size()) { - emit dataChanged(index(0), index(size() - 1)); - } -} - -void ModFolderModel::directoryChanged(QString path) -{ - update(); -} bool ModFolderModel::isValid() { @@ -277,104 +142,28 @@ bool ModFolderModel::isValid() auto ModFolderModel::selectedMods(QModelIndexList& indexes) -> QList { - QList selected_mods; + QList selected_resources; for (auto i : indexes) { if(i.column() != 0) continue; - selected_mods.push_back(mods[i.row()]); + selected_resources.push_back(at(i.row())); } - return selected_mods; + return selected_resources; } -// FIXME: this does not take disabled mod (with extra .disable extension) into account... -bool ModFolderModel::installMod(const QString &filename) +auto ModFolderModel::allMods() -> QList { - if(interaction_disabled) { - return false; - } + QList mods; - // NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName - auto originalPath = FS::NormalizePath(filename); - QFileInfo fileinfo(originalPath); + for (auto res : m_resources) + mods.append(static_cast(res.get())); - if (!fileinfo.exists() || !fileinfo.isReadable()) - { - qWarning() << "Caught attempt to install non-existing file or file-like object:" << originalPath; - return false; - } - qDebug() << "installing: " << fileinfo.absoluteFilePath(); - - Mod installedMod(fileinfo); - if (!installedMod.valid()) - { - qDebug() << originalPath << "is not a valid mod. Ignoring it."; - return false; - } - - auto type = installedMod.type(); - if (type == Mod::MOD_UNKNOWN) - { - qDebug() << "Cannot recognize mod type of" << originalPath << ", ignoring it."; - return false; - } - - auto newpath = FS::NormalizePath(FS::PathCombine(m_dir.path(), fileinfo.fileName())); - if(originalPath == newpath) - { - qDebug() << "Overwriting the mod (" << originalPath << ") with itself makes no sense..."; - return false; - } - - if (type == Mod::MOD_SINGLEFILE || type == Mod::MOD_ZIPFILE || type == Mod::MOD_LITEMOD) - { - if(QFile::exists(newpath) || QFile::exists(newpath + QString(".disabled"))) - { - if(!QFile::remove(newpath)) - { - // FIXME: report error in a user-visible way - qWarning() << "Copy from" << originalPath << "to" << newpath << "has failed."; - return false; - } - qDebug() << newpath << "has been deleted."; - } - if (!QFile::copy(fileinfo.filePath(), newpath)) - { - qWarning() << "Copy from" << originalPath << "to" << newpath << "has failed."; - // FIXME: report error in a user-visible way - return false; - } - FS::updateTimestamp(newpath); - QFileInfo newpathInfo(newpath); - installedMod.repath(newpathInfo); - update(); - return true; - } - else if (type == Mod::MOD_FOLDER) - { - QString from = fileinfo.filePath(); - if(QFile::exists(newpath)) - { - qDebug() << "Ignoring folder " << from << ", it would merge with " << newpath; - return false; - } - - if (!FS::copy(from, newpath)()) - { - qWarning() << "Copy of folder from" << originalPath << "to" << newpath << "has (potentially partially) failed."; - return false; - } - QFileInfo newpathInfo(newpath); - installedMod.repath(newpathInfo); - update(); - return true; - } - return false; + return mods; } bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadata) { - for(auto mod : allMods()){ if(mod->fileinfo().fileName() == filename){ auto index_dir = indexDir(); @@ -388,7 +177,7 @@ bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadat bool ModFolderModel::setModStatus(const QModelIndexList& indexes, ModStatusAction enable) { - if(interaction_disabled) { + if(!m_can_interact) { return false; } @@ -407,7 +196,7 @@ bool ModFolderModel::setModStatus(const QModelIndexList& indexes, ModStatusActio bool ModFolderModel::deleteMods(const QModelIndexList& indexes) { - if(interaction_disabled) { + if(!m_can_interact) { return false; } @@ -419,7 +208,7 @@ bool ModFolderModel::deleteMods(const QModelIndexList& indexes) if(i.column() != 0) { continue; } - auto m = mods[i.row()]; + auto m = at(i.row()); auto index_dir = indexDir(); m->destroy(index_dir); } @@ -433,48 +222,45 @@ int ModFolderModel::columnCount(const QModelIndex &parent) const QVariant ModFolderModel::data(const QModelIndex &index, int role) const { - if (!index.isValid()) - return QVariant(); + if (!validateIndex(index)) + return {}; int row = index.row(); int column = index.column(); - if (row < 0 || row >= mods.size()) - return QVariant(); - switch (role) { case Qt::DisplayRole: switch (column) { case NameColumn: - return mods[row]->name(); + return m_resources[row]->name(); case VersionColumn: { - switch(mods[row]->type()) { - case Mod::MOD_FOLDER: + switch(m_resources[row]->type()) { + case ResourceType::FOLDER: return tr("Folder"); - case Mod::MOD_SINGLEFILE: + case ResourceType::SINGLEFILE: return tr("File"); default: break; } - return mods[row]->version(); + return at(row)->version(); } case DateColumn: - return mods[row]->dateTimeChanged(); + return m_resources[row]->dateTimeChanged(); default: return QVariant(); } case Qt::ToolTipRole: - return mods[row]->internal_id(); + return m_resources[row]->internal_id(); case Qt::CheckStateRole: switch (column) { case ActiveColumn: - return mods[row]->enabled() ? Qt::Checked : Qt::Unchecked; + return at(row)->enabled() ? Qt::Checked : Qt::Unchecked; default: return QVariant(); } @@ -499,11 +285,11 @@ bool ModFolderModel::setData(const QModelIndex &index, const QVariant &value, in bool ModFolderModel::setModStatus(int row, ModFolderModel::ModStatusAction action) { - if(row < 0 || row >= mods.size()) { + if(row < 0 || row >= m_resources.size()) { return false; } - auto &mod = mods[row]; + auto mod = at(row); bool desiredStatus; switch(action) { case Enable: @@ -528,12 +314,12 @@ bool ModFolderModel::setModStatus(int row, ModFolderModel::ModStatusAction actio return false; } auto newId = mod->internal_id(); - if(modsIndex.contains(newId)) { + if(m_resources_index.contains(newId)) { // NOTE: this could handle a corner case, where we are overwriting a file, because the same 'mod' exists both enabled and disabled // But is it necessary? } - modsIndex.remove(oldId); - modsIndex[newId] = row; + m_resources_index.remove(oldId); + m_resources_index[newId] = row; emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1)); return true; } @@ -577,65 +363,3 @@ QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, in return QVariant(); } -Qt::ItemFlags ModFolderModel::flags(const QModelIndex &index) const -{ - Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); - auto flags = defaultFlags; - if(interaction_disabled) { - flags &= ~Qt::ItemIsDropEnabled; - } - else - { - flags |= Qt::ItemIsDropEnabled; - if(index.isValid()) { - flags |= Qt::ItemIsUserCheckable; - } - } - return flags; -} - -Qt::DropActions ModFolderModel::supportedDropActions() const -{ - // copy from outside, move from within and other mod lists - return Qt::CopyAction | Qt::MoveAction; -} - -QStringList ModFolderModel::mimeTypes() const -{ - QStringList types; - types << "text/uri-list"; - return types; -} - -bool ModFolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex&) -{ - if (action == Qt::IgnoreAction) - { - return true; - } - - // check if the action is supported - if (!data || !(action & supportedDropActions())) - { - return false; - } - - // files dropped from outside? - if (data->hasUrls()) - { - auto urls = data->urls(); - for (auto url : urls) - { - // only local files may be dropped... - if (!url.isLocalFile()) - { - continue; - } - // TODO: implement not only copy, but also move - // FIXME: handle errors here - installMod(url.toLocalFile()); - } - return true; - } - return false; -} diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index 3d6efac3..a90457d5 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -44,6 +44,7 @@ #include #include "Mod.h" +#include "ResourceFolderModel.h" #include "minecraft/mod/tasks/ModFolderLoadTask.h" #include "minecraft/mod/tasks/LocalModParseTask.h" @@ -56,7 +57,7 @@ class QFileSystemWatcher; * A legacy mod list. * Backed by a folder. */ -class ModFolderModel : public QAbstractListModel +class ModFolderModel : public ResourceFolderModel { Q_OBJECT public: @@ -75,48 +76,18 @@ public: }; ModFolderModel(const QString &dir, bool is_indexed = false); - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; - Qt::DropActions supportedDropActions() const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; - /// flags, mostly to support drag&drop - virtual Qt::ItemFlags flags(const QModelIndex &index) const override; - QStringList mimeTypes() const override; - bool dropMimeData(const QMimeData * data, Qt::DropAction action, int row, int column, const QModelIndex & parent) override; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + int columnCount(const QModelIndex &parent) const override; - virtual int rowCount(const QModelIndex &) const override - { - return size(); - } + [[nodiscard]] Task* createUpdateTask() override; + [[nodiscard]] Task* createParseTask(Resource const&) override; - virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; - virtual int columnCount(const QModelIndex &parent) const override; - - size_t size() const - { - return mods.size(); - } - ; - bool empty() const - { - return size() == 0; - } - Mod& operator[](size_t index) - { - return *mods[index]; - } - const Mod& at(size_t index) const - { - return *mods.at(index); - } - - /// Reloads the mod list and returns true if the list changed. - bool update(); - - /** - * Adds the given mod to the list at the given index - if the list supports custom ordering - */ - bool installMod(const QString& filename); + // Alias for old code, consider those deprecated and don't use in new code :gun: + bool installMod(QString file_path) { return ResourceFolderModel::installResource(file_path); } + void disableInteraction(bool disabled) { ResourceFolderModel::enableInteraction(!disabled); } bool uninstallMod(const QString& filename, bool preserve_metadata = false); @@ -126,55 +97,27 @@ public: /// Enable or disable listed mods bool setModStatus(const QModelIndexList &indexes, ModStatusAction action); + bool isValid(); + void startWatching(); void stopWatching(); - bool isValid(); - - QDir& dir() - { - return m_dir; - } - - QDir indexDir() - { - return { QString("%1/.index").arg(dir().absolutePath()) }; - } - - const QList& allMods() - { - return mods; - } + QDir indexDir() { return { QString("%1/.index").arg(dir().absolutePath()) }; } auto selectedMods(QModelIndexList& indexes) -> QList; + auto allMods() -> QList; -public slots: - void disableInteraction(bool disabled); + RESOURCE_HELPERS(Mod) private slots: - void directoryChanged(QString path); - void finishUpdate(); - void finishModParse(int token); - -signals: - void updateFinished(); + void onUpdateSucceeded() override; + void onParseSucceeded(int ticket, QString resource_id) override; private: - void resolveMod(Mod::Ptr m); bool setModStatus(int index, ModStatusAction action); protected: - QFileSystemWatcher *m_watcher; - bool is_watching = false; - ModFolderLoadTask::ResultPtr m_update; - bool scheduled_update = false; - bool interaction_disabled = false; - QDir m_dir; bool m_is_indexed; bool m_first_folder_load = true; - QMap modsIndex; - QMap activeTickets; - int nextResolutionTicket = 0; - QList mods; }; diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp new file mode 100644 index 00000000..4867a8c2 --- /dev/null +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -0,0 +1,336 @@ +#include "ResourceFolderModel.h" + +#include +#include +#include +#include + +#include "FileSystem.h" + +#include "minecraft/mod/tasks/BasicFolderLoadTask.h" + +#include "tasks/Task.h" + +ResourceFolderModel::ResourceFolderModel(QDir dir, QObject* parent) : QAbstractListModel(parent), m_dir(dir), m_watcher(this) +{ + m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); + m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); + + connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &ResourceFolderModel::directoryChanged); +} + +bool ResourceFolderModel::startWatching(const QStringList paths) +{ + if (m_is_watching) + return false; + + update(); + + auto couldnt_be_watched = m_watcher.addPaths(paths); + for (auto path : paths) { + if (couldnt_be_watched.contains(path)) + qDebug() << "Failed to start watching " << path; + else + qDebug() << "Started watching " << path; + } + + m_is_watching = !m_is_watching; + return m_is_watching; +} + +bool ResourceFolderModel::stopWatching(const QStringList paths) +{ + if (!m_is_watching) + return false; + + auto couldnt_be_stopped = m_watcher.removePaths(paths); + for (auto path : paths) { + if (couldnt_be_stopped.contains(path)) + qDebug() << "Failed to stop watching " << path; + else + qDebug() << "Stopped watching " << path; + } + + m_is_watching = !m_is_watching; + return !m_is_watching; +} + +bool ResourceFolderModel::installResource(QString original_path) +{ + if (!m_can_interact) { + return false; + } + + // NOTE: fix for GH-1178: remove trailing slash to avoid issues with using the empty result of QFileInfo::fileName + original_path = FS::NormalizePath(original_path); + QFileInfo file_info(original_path); + + if (!file_info.exists() || !file_info.isReadable()) { + qWarning() << "Caught attempt to install non-existing file or file-like object:" << original_path; + return false; + } + qDebug() << "Installing: " << file_info.absoluteFilePath(); + + Resource resource(file_info); + if (!resource.valid()) { + qWarning() << original_path << "is not a valid resource. Ignoring it."; + return false; + } + + auto new_path = FS::NormalizePath(m_dir.filePath(file_info.fileName())); + if (original_path == new_path) { + qWarning() << "Overwriting the mod (" << original_path << ") with itself makes no sense..."; + return false; + } + + switch (resource.type()) { + case ResourceType::SINGLEFILE: + case ResourceType::ZIPFILE: + case ResourceType::LITEMOD: { + if (QFile::exists(new_path) || QFile::exists(new_path + QString(".disabled"))) { + if (!QFile::remove(new_path)) { + qCritical() << "Cleaning up new location (" << new_path << ") was unsuccessful!"; + return false; + } + qDebug() << new_path << "has been deleted."; + } + + if (!QFile::copy(original_path, new_path)) { + qCritical() << "Copy from" << original_path << "to" << new_path << "has failed."; + return false; + } + + FS::updateTimestamp(new_path); + + QFileInfo new_path_file_info(new_path); + resource.setFile(new_path_file_info); + + update(); + + return true; + } + case ResourceType::FOLDER: { + if (QFile::exists(new_path)) { + qDebug() << "Ignoring folder '" << original_path << "', it would merge with" << new_path; + return false; + } + + if (!FS::copy(original_path, new_path)()) { + qWarning() << "Copy of folder from" << original_path << "to" << new_path << "has (potentially partially) failed."; + return false; + } + + QFileInfo newpathInfo(new_path); + resource.setFile(newpathInfo); + + update(); + + return true; + } + default: + break; + } + return false; +} + +bool ResourceFolderModel::uninstallResource(QString file_name) +{ + for (auto resource : m_resources) { + if (resource->fileinfo().fileName() == file_name) + return resource->destroy(); + } + return false; +} + +bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes) +{ + if (!m_can_interact) + return false; + + if (indexes.isEmpty()) + return true; + + for (auto i : indexes) { + if (i.column() != 0) { + continue; + } + + auto resource = m_resources.at(i.row()); + resource->destroy(); + } + return true; +} + +bool ResourceFolderModel::update() +{ + // Already updating, so we schedule a future update and return. + if (m_current_update_task) { + m_scheduled_update = true; + return false; + } + + m_current_update_task.reset(createUpdateTask()); + + connect(m_current_update_task.get(), &Task::succeeded, this, &ResourceFolderModel::onUpdateSucceeded, Qt::ConnectionType::QueuedConnection); + connect(m_current_update_task.get(), &Task::failed, this, &ResourceFolderModel::onUpdateFailed, Qt::ConnectionType::QueuedConnection); + + auto* thread_pool = QThreadPool::globalInstance(); + thread_pool->start(m_current_update_task.get()); + + return true; +} + +void ResourceFolderModel::resolveResource(Resource::Ptr res) +{ + if (!res->shouldResolve()) { + return; + } + + auto task = createParseTask(*res); + + m_ticket_mutex.lock(); + int ticket = m_next_resolution_ticket; + m_next_resolution_ticket += 1; + m_ticket_mutex.unlock(); + + res->setResolving(true, ticket); + m_active_parse_tasks.insert(ticket, task); + + connect(task, &Task::succeeded, this, [=] { onParseSucceeded(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection); + connect(task, &Task::failed, this, [=] { onParseFailed(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection); + + auto* thread_pool = QThreadPool::globalInstance(); + thread_pool->start(task); +} + +void ResourceFolderModel::onUpdateSucceeded() +{ + auto update_results = static_cast(m_current_update_task.get())->result(); + + auto& new_resources = update_results->resources; + +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + auto current_list = m_resources_index.keys(); + QSet current_set(current_list.begin(), current_list.end()); + + auto new_list = new_resources.keys(); + QSet new_set(new_list.begin(), new_list.end()); +#else + QSet current_set(m_resources_index.keys().toSet()); + QSet new_set(new_resources.keys().toSet()); +#endif + + applyUpdates(current_set, new_set, new_resources); + + update_results.reset(); + m_current_update_task->deleteLater(); + m_current_update_task.reset(); + + emit updateFinished(); + + if (m_scheduled_update) { + m_scheduled_update = false; + update(); + } +} + +void ResourceFolderModel::onParseSucceeded(int ticket, QString resource_id) +{ + auto iter = m_active_parse_tasks.constFind(ticket); + if (iter == m_active_parse_tasks.constEnd()) + return; + + (*iter)->deleteLater(); + m_active_parse_tasks.remove(ticket); + + int row = m_resources_index[resource_id]; + emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1)); +} + +Task* ResourceFolderModel::createUpdateTask() +{ + return new BasicFolderLoadTask(m_dir); +} + +void ResourceFolderModel::directoryChanged(QString path) +{ + update(); +} + +Qt::DropActions ResourceFolderModel::supportedDropActions() const +{ + // copy from outside, move from within and other resource lists + return Qt::CopyAction | Qt::MoveAction; +} + +Qt::ItemFlags ResourceFolderModel::flags(const QModelIndex& index) const +{ + Qt::ItemFlags defaultFlags = QAbstractListModel::flags(index); + auto flags = defaultFlags; + if (!m_can_interact) { + flags &= ~Qt::ItemIsDropEnabled; + } else { + flags |= Qt::ItemIsDropEnabled; + if (index.isValid()) { + flags |= Qt::ItemIsUserCheckable; + } + } + return flags; +} + +QStringList ResourceFolderModel::mimeTypes() const +{ + QStringList types; + types << "text/uri-list"; + return types; +} + +bool ResourceFolderModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int, int, const QModelIndex&) +{ + if (action == Qt::IgnoreAction) { + return true; + } + + // check if the action is supported + if (!data || !(action & supportedDropActions())) { + return false; + } + + // files dropped from outside? + if (data->hasUrls()) { + auto urls = data->urls(); + for (auto url : urls) { + // only local files may be dropped... + if (!url.isLocalFile()) { + continue; + } + // TODO: implement not only copy, but also move + // FIXME: handle errors here + installResource(url.toLocalFile()); + } + return true; + } + return false; +} + +bool ResourceFolderModel::validateIndex(const QModelIndex& index) const +{ + if (!index.isValid()) + return false; + + size_t row = index.row(); + if (row < 0 || row >= size()) + return false; + + return true; +} + +void ResourceFolderModel::enableInteraction(bool enabled) +{ + if (m_can_interact == enabled) + return; + + m_can_interact = enabled; + if (size()) + emit dataChanged(index(0), index(size() - 1)); +} diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h new file mode 100644 index 00000000..31fd7414 --- /dev/null +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -0,0 +1,274 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "Resource.h" + +#include "tasks/Task.h" + +class QRunnable; + +/** A basic model for external resources. + * + * To implement one such model, you need to implement, at the very minimum: + * - columnCount: The number of columns in your model. + * - data: How the model data is displayed and accessed. + * - headerData: Display properties of the header. + */ +class ResourceFolderModel : public QAbstractListModel { + Q_OBJECT + public: + ResourceFolderModel(QDir, QObject* parent = nullptr); + + /** Starts watching the paths for changes. + * + * Returns whether starting to watch all the paths was successful. + * If one or more fails, it returns false. + */ + bool startWatching(const QStringList paths); + + /** Stops watching the paths for changes. + * + * Returns whether stopping to watch all the paths was successful. + * If one or more fails, it returns false. + */ + bool stopWatching(const QStringList paths); + + /** Given a path in the system, install that resource, moving it to its place in the + * instance file hierarchy. + * + * Returns whether the installation was succcessful. + */ + virtual bool installResource(QString path); + + /** Uninstall (i.e. remove all data about it) a resource, given its file name. + * + * Returns whether the removal was successful. + */ + virtual bool uninstallResource(QString file_name); + + virtual bool deleteResources(const QModelIndexList&); + + /** Creates a new update task and start it. Returns false if no update was done, like when an update is already underway. */ + virtual bool update(); + + /** Creates a new parse task, if needed, for 'res' and start it.*/ + virtual void resolveResource(Resource::Ptr res); + + [[nodiscard]] size_t size() const { return m_resources.size(); }; + [[nodiscard]] bool empty() const { return size() == 0; } + + [[nodiscard]] QDir const& dir() const { return m_dir; } + + /* Qt behavior */ + + [[nodiscard]] int rowCount(const QModelIndex&) const override { return size(); } + [[nodiscard]] int columnCount(const QModelIndex&) const override = 0; + + [[nodiscard]] Qt::DropActions supportedDropActions() const override; + + /// flags, mostly to support drag&drop + [[nodiscard]] Qt::ItemFlags flags(const QModelIndex& index) const override; + [[nodiscard]] QStringList mimeTypes() const override; + bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) override; + + [[nodiscard]] bool validateIndex(const QModelIndex& index) const; + + [[nodiscard]] QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override = 0; + bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override { return false; }; + + [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override = 0; + + public slots: + void enableInteraction(bool enabled); + + signals: + void updateFinished(); + + protected: + /** This creates a new update task to be executed by update(). + * + * The task should load and parse all resources necessary, and provide a way of accessing such results. + * + * This Task is normally executed when opening a page, so it shouldn't contain much heavy work. + * If such work is needed, try using it in the Task create by createParseTask() instead! + */ + [[nodiscard]] virtual Task* createUpdateTask(); + + /** This creates a new parse task to be executed by onUpdateSucceeded(). + * + * This task should load and parse all heavy info needed by a resource, such as parsing a manifest. It gets executed + * in the background, so it slowly updates the UI as tasks get done. + */ + [[nodiscard]] virtual Task* createParseTask(Resource const&) { return nullptr; }; + + /** Standard implementation of the model update logic. + * + * It uses set operations to find differences between the current state and the updated state, + * to act only on those disparities. + * + * The implementation is at the end of this header. + */ + template + void applyUpdates(QSet& current_set, QSet& new_set, QMap& new_resources); + + protected slots: + void directoryChanged(QString); + + /** Called when the update task is successful. + * + * This usually calls static_cast on the specific Task type returned by createUpdateTask, + * so care must be taken in such cases. + * TODO: Figure out a way to express this relationship better without templated classes (Q_OBJECT macro dissalows that). + */ + virtual void onUpdateSucceeded(); + virtual void onUpdateFailed() {} + + /** Called when the parse task with the given ticket is successful. + * + * This is just a simple reference implementation. You probably want to override it with your own logic in a subclass + * 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) {} + + protected: + bool m_can_interact = true; + + QDir m_dir; + QFileSystemWatcher m_watcher; + bool m_is_watching = false; + + Task::Ptr m_current_update_task = nullptr; + bool m_scheduled_update = false; + + QList m_resources; + + // Represents the relationship between a resource's internal ID and it's row position on the model. + QMap m_resources_index; + + QMap m_active_parse_tasks; + int m_next_resolution_ticket = 0; + QMutex m_ticket_mutex; +}; + +/* A macro to define useful functions to handle Resource* -> T* more easily on derived classes */ +#define RESOURCE_HELPERS(T) \ + [[nodiscard]] T* operator[](size_t index) \ + { \ + return static_cast(m_resources[index].get()); \ + } \ + [[nodiscard]] T* at(size_t index) \ + { \ + return static_cast(m_resources[index].get()); \ + } \ + [[nodiscard]] const T* at(size_t index) const \ + { \ + return static_cast(m_resources.at(index).get()); \ + } \ + [[nodiscard]] T* first() \ + { \ + return static_cast(m_resources.first().get()); \ + } \ + [[nodiscard]] T* last() \ + { \ + return static_cast(m_resources.last().get()); \ + } \ + [[nodiscard]] T* find(QString id) \ + { \ + auto iter = std::find_if(m_resources.begin(), m_resources.end(), [&](Resource::Ptr r) { return r->internal_id() == id; }); \ + if (iter == m_resources.end()) \ + return nullptr; \ + return static_cast((*iter).get()); \ + } + +/* Template definition to avoid some code duplication */ +template +void ResourceFolderModel::applyUpdates(QSet& current_set, QSet& new_set, QMap& new_resources) +{ + // see if the kept resources changed in some way + { + QSet kept_set = current_set; + kept_set.intersect(new_set); + + for (auto& kept : kept_set) { + auto row = m_resources_index[kept]; + + auto new_resource = new_resources[kept]; + auto current_resource = m_resources[row]; + + if (new_resource->dateTimeChanged() == current_resource->dateTimeChanged()) { + // no significant change, ignore... + continue; + } + + // If the resource is resolving, but something about it changed, we don't want to + // continue the resolving. + if (current_resource->isResolving()) { + m_active_parse_tasks.remove(current_resource->resolutionTicket()); + } + + m_resources[row] = new_resource; + resolveResource(new_resource); + emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1)); + } + } + + // remove resources no longer present + { + QSet removed_set = current_set; + removed_set.subtract(new_set); + + QList removed_rows; + for (auto& removed : removed_set) + removed_rows.append(m_resources_index[removed]); + + std::sort(removed_rows.begin(), removed_rows.end()); + + for (auto& removed_index : removed_rows) { + beginRemoveRows(QModelIndex(), removed_index, removed_index); + + auto removed_it = m_resources.begin() + removed_index; + if ((*removed_it)->isResolving()) { + m_active_parse_tasks.remove((*removed_it)->resolutionTicket()); + } + + m_resources.erase(removed_it); + + endRemoveRows(); + } + } + + // add new resources to the end + { + QSet added_set = new_set; + added_set.subtract(current_set); + + // When you have a Qt build with assertions turned on, proceeding here will abort the application + if (added_set.size() > 0) { + beginInsertRows(QModelIndex(), m_resources.size(), m_resources.size() + added_set.size() - 1); + + for (auto& added : added_set) { + auto res = new_resources[added]; + m_resources.append(res); + resolveResource(res); + } + + endInsertRows(); + } + } + + // update index + { + m_resources_index.clear(); + int idx = 0; + for (auto mod : m_resources) { + m_resources_index[mod->internal_id()] = idx; + idx++; + } + } +} diff --git a/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h b/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h new file mode 100644 index 00000000..0fd5c292 --- /dev/null +++ b/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include + +#include + +#include "minecraft/mod/Resource.h" + +#include "tasks/Task.h" + +/** Very simple task that just loads a folder's contents directly. + */ +class BasicFolderLoadTask : public Task +{ + Q_OBJECT +public: + struct Result { + QMap resources; + }; + using ResultPtr = std::shared_ptr; + + [[nodiscard]] ResultPtr result() const { + return m_result; + } + +public: + BasicFolderLoadTask(QDir dir) : Task(nullptr, false), m_dir(dir), m_result(new Result) {} + void executeTask() override + { + m_dir.refresh(); + for (auto entry : m_dir.entryInfoList()) { + auto resource = new Resource(entry); + m_result->resources.insert(resource->internal_id(), resource); + } + + emitSucceeded(); + } + +private: + QDir m_dir; + ResultPtr m_result; +}; diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp index 1519f49d..fe3716ce 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp @@ -338,13 +338,13 @@ std::shared_ptr ReadLiteModInfo(QByteArray contents) } -LocalModParseTask::LocalModParseTask(int token, Mod::ModType type, const QFileInfo& modFile): +LocalModParseTask::LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile): + Task(nullptr, false), m_token(token), m_type(type), m_modFile(modFile), m_result(new Result()) -{ -} +{} void LocalModParseTask::processAsZip() { @@ -497,21 +497,21 @@ void LocalModParseTask::processAsLitemod() zip.close(); } -void LocalModParseTask::run() +void LocalModParseTask::executeTask() { switch(m_type) { - case Mod::MOD_ZIPFILE: + case ResourceType::ZIPFILE: processAsZip(); break; - case Mod::MOD_FOLDER: + case ResourceType::FOLDER: processAsFolder(); break; - case Mod::MOD_LITEMOD: + case ResourceType::LITEMOD: processAsLitemod(); break; default: break; } - emit finished(m_token); + emitSucceeded(); } diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.h b/launcher/minecraft/mod/tasks/LocalModParseTask.h index ed92394c..dbecb449 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.h @@ -2,17 +2,17 @@ #include #include -#include #include "minecraft/mod/Mod.h" #include "minecraft/mod/ModDetails.h" -class LocalModParseTask : public QObject, public QRunnable +#include "tasks/Task.h" + +class LocalModParseTask : public Task { Q_OBJECT public: struct Result { - QString id; std::shared_ptr details; }; using ResultPtr = std::shared_ptr; @@ -20,11 +20,10 @@ public: return m_result; } - LocalModParseTask(int token, Mod::ModType type, const QFileInfo & modFile); - void run(); + LocalModParseTask(int token, ResourceType type, const QFileInfo & modFile); + void executeTask() override; -signals: - void finished(int token); + [[nodiscard]] int token() const { return m_token; } private: void processAsZip(); @@ -33,7 +32,7 @@ private: private: int m_token; - Mod::ModType m_type; + ResourceType m_type; QFileInfo m_modFile; ResultPtr m_result; }; diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp index 015ead80..e8180c11 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp @@ -38,11 +38,11 @@ #include "minecraft/mod/MetadataHandler.h" -ModFolderLoadTask::ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed, bool clean_orphan) - : m_mods_dir(mods_dir), m_index_dir(index_dir), m_is_indexed(is_indexed), m_clean_orphan(clean_orphan), m_result(new Result()) +ModFolderLoadTask::ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan) + : Task(nullptr, false), m_mods_dir(mods_dir), m_index_dir(index_dir), m_is_indexed(is_indexed), m_clean_orphan(clean_orphan), m_result(new Result()) {} -void ModFolderLoadTask::run() +void ModFolderLoadTask::executeTask() { if (m_is_indexed) { // Read metadata first @@ -96,7 +96,7 @@ void ModFolderLoadTask::run() } } - emit succeeded(); + emitSucceeded(); } void ModFolderLoadTask::getFromMetadata() diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h index 1f2015d2..86f3f67f 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h @@ -42,8 +42,9 @@ #include #include #include "minecraft/mod/Mod.h" +#include "tasks/Task.h" -class ModFolderLoadTask : public QObject, public QRunnable +class ModFolderLoadTask : public Task { Q_OBJECT public: @@ -56,16 +57,15 @@ public: } public: - ModFolderLoadTask(QDir& mods_dir, QDir& index_dir, bool is_indexed, bool clean_orphan = false); - void run(); -signals: - void succeeded(); + ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan = false); + + void executeTask() override; private: void getFromMetadata(); private: - QDir& m_mods_dir, m_index_dir; + QDir m_mods_dir, m_index_dir; bool m_is_indexed; bool m_clean_orphan; ResultPtr m_result; diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp index 39fbe3e2..da7c4af0 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.cpp +++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp @@ -32,12 +32,12 @@ class SortProxy : public QSortFilterProxyModel { const auto& mod = model->at(source_row); - if (filterRegularExpression().match(mod.name()).hasMatch()) + if (filterRegularExpression().match(mod->name()).hasMatch()) return true; - if (filterRegularExpression().match(mod.description()).hasMatch()) + if (filterRegularExpression().match(mod->description()).hasMatch()) return true; - for (auto& author : mod.authors()) { + for (auto& author : mod->authors()) { if (filterRegularExpression().match(author).hasMatch()) { return true; } @@ -292,6 +292,6 @@ void ExternalResourcesPage::current(const QModelIndex& current, const QModelInde auto sourceCurrent = m_filterModel->mapToSource(current); int row = sourceCurrent.row(); - Mod& m = m_model->operator[](row); + Mod& m = *m_model->operator[](row); ui->frame->updateWithMod(m); } From af2cf2734da211d443b3046fb0f9733f101d9d9d Mon Sep 17 00:00:00 2001 From: flow Date: Tue, 9 Aug 2022 12:56:38 -0300 Subject: [PATCH 119/273] refactor: move things around in the mod model Makes the method order in the cpp file the same as in the header file. Signed-off-by: flow --- launcher/minecraft/mod/ModFolderModel.cpp | 410 +++++++++++----------- launcher/minecraft/mod/ModFolderModel.h | 5 +- 2 files changed, 207 insertions(+), 208 deletions(-) diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 597f9807..8ab60413 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -54,172 +54,6 @@ ModFolderModel::ModFolderModel(const QString &dir, bool is_indexed) : ResourceFo FS::ensureFolderPathExists(m_dir.absolutePath()); } -Task* ModFolderModel::createUpdateTask() -{ - auto index_dir = indexDir(); - auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load); - m_first_folder_load = false; - return task; -} - -void ModFolderModel::startWatching() -{ - // Remove orphaned metadata next time - m_first_folder_load = true; - ResourceFolderModel::startWatching({ m_dir.absolutePath(), indexDir().absolutePath() }); -} - -void ModFolderModel::stopWatching() -{ - ResourceFolderModel::stopWatching({ m_dir.absolutePath(), indexDir().absolutePath() }); -} - -void ModFolderModel::onUpdateSucceeded() -{ - auto update_results = static_cast(m_current_update_task.get())->result(); - - auto& new_mods = update_results->mods; - -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) - auto current_list = m_resources_index.keys(); - QSet current_set(current_list.begin(), current_list.end()); - - auto new_list = new_mods.keys(); - QSet new_set(new_list.begin(), new_list.end()); -#else - QSet current_set(m_resources_index.keys().toSet()); - QSet new_set(new_mods.keys().toSet()); -#endif - - applyUpdates(current_set, new_set, new_mods); - - update_results.reset(); - m_current_update_task.reset(); - - emit updateFinished(); - - if(m_scheduled_update) { - m_scheduled_update = false; - update(); - } -} - -Task* ModFolderModel::createParseTask(Resource const& resource) -{ - return new LocalModParseTask(m_next_resolution_ticket, resource.type(), resource.fileinfo()); -} - -void ModFolderModel::onParseSucceeded(int ticket, QString mod_id) -{ - auto iter = m_active_parse_tasks.constFind(ticket); - if (iter == m_active_parse_tasks.constEnd()) - return; - - int row = m_resources_index[mod_id]; - - auto parse_task = *iter; - auto cast_task = static_cast(parse_task.get()); - - Q_ASSERT(cast_task->token() == ticket); - - auto resource = find(mod_id); - - auto result = cast_task->result(); - if (result && resource) - resource->finishResolvingWithDetails(result->details); - - emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1)); - - parse_task->deleteLater(); - m_active_parse_tasks.remove(ticket); -} - - -bool ModFolderModel::isValid() -{ - return m_dir.exists() && m_dir.isReadable(); -} - -auto ModFolderModel::selectedMods(QModelIndexList& indexes) -> QList -{ - QList selected_resources; - for (auto i : indexes) { - if(i.column() != 0) - continue; - - selected_resources.push_back(at(i.row())); - } - return selected_resources; -} - -auto ModFolderModel::allMods() -> QList -{ - QList mods; - - for (auto res : m_resources) - mods.append(static_cast(res.get())); - - return mods; -} - -bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadata) -{ - for(auto mod : allMods()){ - if(mod->fileinfo().fileName() == filename){ - auto index_dir = indexDir(); - mod->destroy(index_dir, preserve_metadata); - return true; - } - } - - return false; -} - -bool ModFolderModel::setModStatus(const QModelIndexList& indexes, ModStatusAction enable) -{ - if(!m_can_interact) { - return false; - } - - if(indexes.isEmpty()) - return true; - - for (auto index: indexes) - { - if(index.column() != 0) { - continue; - } - setModStatus(index.row(), enable); - } - return true; -} - -bool ModFolderModel::deleteMods(const QModelIndexList& indexes) -{ - if(!m_can_interact) { - return false; - } - - if(indexes.isEmpty()) - return true; - - for (auto i: indexes) - { - if(i.column() != 0) { - continue; - } - auto m = at(i.row()); - auto index_dir = indexDir(); - m->destroy(index_dir); - } - return true; -} - -int ModFolderModel::columnCount(const QModelIndex &parent) const -{ - return NUM_COLUMNS; -} - QVariant ModFolderModel::data(const QModelIndex &index, int role) const { if (!validateIndex(index)) @@ -283,6 +117,211 @@ bool ModFolderModel::setData(const QModelIndex &index, const QVariant &value, in return false; } +QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + switch (role) + { + case Qt::DisplayRole: + switch (section) + { + case ActiveColumn: + return QString(); + case NameColumn: + return tr("Name"); + case VersionColumn: + return tr("Version"); + case DateColumn: + return tr("Last changed"); + default: + return QVariant(); + } + + case Qt::ToolTipRole: + switch (section) + { + case ActiveColumn: + return tr("Is the mod enabled?"); + case NameColumn: + return tr("The name of the mod."); + case VersionColumn: + return tr("The version of the mod."); + case DateColumn: + return tr("The date and time this mod was last changed (or added)."); + default: + return QVariant(); + } + default: + return QVariant(); + } + return QVariant(); +} + +int ModFolderModel::columnCount(const QModelIndex &parent) const +{ + return NUM_COLUMNS; +} + +Task* ModFolderModel::createUpdateTask() +{ + auto index_dir = indexDir(); + auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load); + m_first_folder_load = false; + return task; +} + +Task* ModFolderModel::createParseTask(Resource const& resource) +{ + return new LocalModParseTask(m_next_resolution_ticket, resource.type(), resource.fileinfo()); +} + +bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadata) +{ + for(auto mod : allMods()){ + if(mod->fileinfo().fileName() == filename){ + auto index_dir = indexDir(); + mod->destroy(index_dir, preserve_metadata); + return true; + } + } + + return false; +} + +bool ModFolderModel::deleteMods(const QModelIndexList& indexes) +{ + if(!m_can_interact) { + return false; + } + + if(indexes.isEmpty()) + return true; + + for (auto i: indexes) + { + if(i.column() != 0) { + continue; + } + auto m = at(i.row()); + auto index_dir = indexDir(); + m->destroy(index_dir); + } + return true; +} + +bool ModFolderModel::isValid() +{ + return m_dir.exists() && m_dir.isReadable(); +} + +void ModFolderModel::startWatching() +{ + // Remove orphaned metadata next time + m_first_folder_load = true; + ResourceFolderModel::startWatching({ m_dir.absolutePath(), indexDir().absolutePath() }); +} + +void ModFolderModel::stopWatching() +{ + ResourceFolderModel::stopWatching({ m_dir.absolutePath(), indexDir().absolutePath() }); +} + +auto ModFolderModel::selectedMods(QModelIndexList& indexes) -> QList +{ + QList selected_resources; + for (auto i : indexes) { + if(i.column() != 0) + continue; + + selected_resources.push_back(at(i.row())); + } + return selected_resources; +} + +auto ModFolderModel::allMods() -> QList +{ + QList mods; + + for (auto res : m_resources) + mods.append(static_cast(res.get())); + + return mods; +} + +void ModFolderModel::onUpdateSucceeded() +{ + auto update_results = static_cast(m_current_update_task.get())->result(); + + auto& new_mods = update_results->mods; + +#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) + auto current_list = m_resources_index.keys(); + QSet current_set(current_list.begin(), current_list.end()); + + auto new_list = new_mods.keys(); + QSet new_set(new_list.begin(), new_list.end()); +#else + QSet current_set(m_resources_index.keys().toSet()); + QSet new_set(new_mods.keys().toSet()); +#endif + + applyUpdates(current_set, new_set, new_mods); + + update_results.reset(); + m_current_update_task.reset(); + + emit updateFinished(); + + if(m_scheduled_update) { + m_scheduled_update = false; + update(); + } +} + +void ModFolderModel::onParseSucceeded(int ticket, QString mod_id) +{ + auto iter = m_active_parse_tasks.constFind(ticket); + if (iter == m_active_parse_tasks.constEnd()) + return; + + int row = m_resources_index[mod_id]; + + auto parse_task = *iter; + auto cast_task = static_cast(parse_task.get()); + + Q_ASSERT(cast_task->token() == ticket); + + auto resource = find(mod_id); + + auto result = cast_task->result(); + if (result && resource) + resource->finishResolvingWithDetails(result->details); + + emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1)); + + parse_task->deleteLater(); + m_active_parse_tasks.remove(ticket); +} + + +bool ModFolderModel::setModStatus(const QModelIndexList& indexes, ModStatusAction enable) +{ + if(!m_can_interact) { + return false; + } + + if(indexes.isEmpty()) + return true; + + for (auto index: indexes) + { + if(index.column() != 0) { + continue; + } + setModStatus(index.row(), enable); + } + return true; +} + bool ModFolderModel::setModStatus(int row, ModFolderModel::ModStatusAction action) { if(row < 0 || row >= m_resources.size()) { @@ -324,42 +363,3 @@ bool ModFolderModel::setModStatus(int row, ModFolderModel::ModStatusAction actio return true; } -QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, int role) const -{ - switch (role) - { - case Qt::DisplayRole: - switch (section) - { - case ActiveColumn: - return QString(); - case NameColumn: - return tr("Name"); - case VersionColumn: - return tr("Version"); - case DateColumn: - return tr("Last changed"); - default: - return QVariant(); - } - - case Qt::ToolTipRole: - switch (section) - { - case ActiveColumn: - return tr("Is the mod enabled?"); - case NameColumn: - return tr("The name of the mod."); - case VersionColumn: - return tr("The version of the mod."); - case DateColumn: - return tr("The date and time this mod was last changed (or added)."); - default: - return QVariant(); - } - default: - return QVariant(); - } - return QVariant(); -} - diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index a90457d5..ea9f0000 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -85,15 +85,14 @@ public: [[nodiscard]] Task* createUpdateTask() override; [[nodiscard]] Task* createParseTask(Resource const&) override; - // Alias for old code, consider those deprecated and don't use in new code :gun: bool installMod(QString file_path) { return ResourceFolderModel::installResource(file_path); } - void disableInteraction(bool disabled) { ResourceFolderModel::enableInteraction(!disabled); } - bool uninstallMod(const QString& filename, bool preserve_metadata = false); /// Deletes all the selected mods bool deleteMods(const QModelIndexList &indexes); + void disableInteraction(bool disabled) { ResourceFolderModel::enableInteraction(!disabled); } + /// Enable or disable listed mods bool setModStatus(const QModelIndexList &indexes, ModStatusAction action); From 1e2f0ab3083f002071938275a97b13c0c4633e64 Mon Sep 17 00:00:00 2001 From: flow Date: Wed, 10 Aug 2022 14:42:24 -0300 Subject: [PATCH 120/273] refactor: move more tied logic to model and move logic to the resources This moves the QSortFilterProxyModel to the resource model files, acessible via a factory method, and moves the sorting and filtering to the objects themselves, decoupling the code a bit. This also adds a basic implementation of methods in the ResourceFolderModel, simplifying the process of constructing a new model from it. Signed-off-by: flow --- launcher/minecraft/mod/Mod.cpp | 47 ++++++++ launcher/minecraft/mod/Mod.h | 3 + launcher/minecraft/mod/ModFolderModel.cpp | 9 +- launcher/minecraft/mod/ModFolderModel.h | 6 +- launcher/minecraft/mod/Resource.cpp | 39 ++++++ launcher/minecraft/mod/Resource.h | 21 ++++ .../minecraft/mod/ResourceFolderModel.cpp | 114 +++++++++++++++++- launcher/minecraft/mod/ResourceFolderModel.h | 42 ++++++- 8 files changed, 265 insertions(+), 16 deletions(-) diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index f28fd32a..ed91d999 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -39,8 +39,10 @@ #include #include #include +#include #include "MetadataHandler.h" +#include "Version.h" namespace { @@ -111,6 +113,51 @@ void Mod::setMetadata(const Metadata::ModStruct& metadata) } } +std::pair Mod::compare(const Resource& other, SortType type) const +{ + auto cast_other = dynamic_cast(&other); + if (!cast_other) + return Resource::compare(other, type); + + switch (type) { + default: + case SortType::ENABLED: + if (enabled() && !cast_other->enabled()) + return { 1, type == SortType::ENABLED }; + if (!enabled() && cast_other->enabled()) + return { -1, type == SortType::ENABLED }; + case SortType::NAME: + case SortType::DATE: { + auto res = Resource::compare(other, type); + if (res.first != 0) + return res; + } + case SortType::VERSION: { + auto this_ver = Version(version()); + auto other_ver = Version(cast_other->version()); + if (this_ver > other_ver) + return { 1, type == SortType::VERSION }; + if (this_ver < other_ver) + return { -1, type == SortType::VERSION }; + } + } + return { 0, false }; +} + +bool Mod::applyFilter(QRegularExpression filter) const +{ + if (filter.match(description()).hasMatch()) + return true; + + for (auto& author : authors()) { + if (filter.match(author).hasMatch()) { + return true; + } + } + + return Resource::applyFilter(filter); +} + auto Mod::destroy(QDir& index_dir, bool preserve_metadata) -> bool { if (!preserve_metadata) { diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index 313c478c..25ac88c9 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -70,6 +70,9 @@ public: auto enable(bool value) -> bool; + [[nodiscard]] auto compare(Resource const& other, SortType type) const -> std::pair override; + [[nodiscard]] bool applyFilter(QRegularExpression filter) const override; + // Delete all the files of this mod auto destroy(QDir& index_dir, bool preserve_metadata = false) -> bool; diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 8ab60413..9b8c58bc 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -52,6 +52,7 @@ ModFolderModel::ModFolderModel(const QString &dir, bool is_indexed) : ResourceFolderModel(dir), m_is_indexed(is_indexed) { FS::ensureFolderPathExists(m_dir.absolutePath()); + m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::VERSION, SortType::DATE }; } QVariant ModFolderModel::data(const QModelIndex &index, int role) const @@ -213,16 +214,16 @@ bool ModFolderModel::isValid() return m_dir.exists() && m_dir.isReadable(); } -void ModFolderModel::startWatching() +bool ModFolderModel::startWatching() { // Remove orphaned metadata next time m_first_folder_load = true; - ResourceFolderModel::startWatching({ m_dir.absolutePath(), indexDir().absolutePath() }); + return ResourceFolderModel::startWatching({ m_dir.absolutePath(), indexDir().absolutePath() }); } -void ModFolderModel::stopWatching() +bool ModFolderModel::stopWatching() { - ResourceFolderModel::stopWatching({ m_dir.absolutePath(), indexDir().absolutePath() }); + return ResourceFolderModel::stopWatching({ m_dir.absolutePath(), indexDir().absolutePath() }); } auto ModFolderModel::selectedMods(QModelIndexList& indexes) -> QList diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index ea9f0000..b1f30710 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -91,15 +91,13 @@ public: /// Deletes all the selected mods bool deleteMods(const QModelIndexList &indexes); - void disableInteraction(bool disabled) { ResourceFolderModel::enableInteraction(!disabled); } - /// Enable or disable listed mods bool setModStatus(const QModelIndexList &indexes, ModStatusAction action); bool isValid(); - void startWatching(); - void stopWatching(); + bool startWatching() override; + bool stopWatching() override; QDir indexDir() { return { QString("%1/.index").arg(dir().absolutePath()) }; } diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index 8771a20f..c58df3d8 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -1,5 +1,7 @@ #include "Resource.h" +#include + #include "FileSystem.h" Resource::Resource(QObject* parent) : QObject(parent) {} @@ -46,6 +48,43 @@ void Resource::parseFile() m_changed_date_time = m_file_info.lastModified(); } +static void removeThePrefix(QString& string) +{ + QRegularExpression regex(QStringLiteral("^(?:the|teh) +"), QRegularExpression::CaseInsensitiveOption); + string.remove(regex); + string = string.trimmed(); +} + +std::pair Resource::compare(const Resource& other, SortType type) const +{ + switch (type) { + default: + case SortType::NAME: { + QString this_name{ name() }; + QString other_name{ other.name() }; + + removeThePrefix(this_name); + removeThePrefix(other_name); + + auto compare_result = QString::compare(this_name, other_name, Qt::CaseInsensitive); + if (compare_result != 0) + return { compare_result, type == SortType::NAME }; + } + case SortType::DATE: + if (dateTimeChanged() > other.dateTimeChanged()) + return { 1, type == SortType::DATE }; + if (dateTimeChanged() < other.dateTimeChanged()) + return { -1, type == SortType::DATE }; + } + + return { 0, false }; +} + +bool Resource::applyFilter(QRegularExpression filter) const +{ + return filter.match(name()).hasMatch(); +} + bool Resource::destroy() { m_type = ResourceType::UNKNOWN; diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index c348c7e2..68663ab0 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -14,6 +14,13 @@ enum class ResourceType { LITEMOD, //!< The resource is a litemod }; +enum class SortType { + NAME, + DATE, + VERSION, + ENABLED, +}; + /** General class for managed resources. It mirrors a file in disk, with some more info * for display and house-keeping purposes. * @@ -40,6 +47,20 @@ class Resource : public QObject { [[nodiscard]] virtual auto name() const -> QString { return m_name; } [[nodiscard]] virtual bool valid() const { return m_type != ResourceType::UNKNOWN; } + /** Compares two Resources, for sorting purposes, considering a ascending order, returning: + * > 0: 'this' comes after 'other' + * = 0: 'this' is equal to 'other' + * < 0: 'this' comes before 'other' + * + * The second argument in the pair is true if the sorting type that decided which one is greater was 'type'. + */ + [[nodiscard]] virtual auto compare(Resource const& other, SortType type = SortType::NAME) const -> std::pair; + + /** Returns whether the given filter should filter out 'this' (false), + * or if such filter includes the Resource (true). + */ + [[nodiscard]] virtual bool applyFilter(QRegularExpression filter) const; + [[nodiscard]] auto shouldResolve() const -> bool { return !m_is_resolving && !m_is_resolved; } [[nodiscard]] auto isResolving() const -> bool { return m_is_resolving; } [[nodiscard]] auto resolutionTicket() const -> int { return m_resolution_ticket; } diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 4867a8c2..982915e2 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -170,8 +170,11 @@ bool ResourceFolderModel::update() } m_current_update_task.reset(createUpdateTask()); + if (!m_current_update_task) + return false; - connect(m_current_update_task.get(), &Task::succeeded, this, &ResourceFolderModel::onUpdateSucceeded, Qt::ConnectionType::QueuedConnection); + connect(m_current_update_task.get(), &Task::succeeded, this, &ResourceFolderModel::onUpdateSucceeded, + Qt::ConnectionType::QueuedConnection); connect(m_current_update_task.get(), &Task::failed, this, &ResourceFolderModel::onUpdateFailed, Qt::ConnectionType::QueuedConnection); auto* thread_pool = QThreadPool::globalInstance(); @@ -187,6 +190,8 @@ void ResourceFolderModel::resolveResource(Resource::Ptr res) } auto task = createParseTask(*res); + if (!task) + return; m_ticket_mutex.lock(); int ticket = m_next_resolution_ticket; @@ -196,8 +201,10 @@ void ResourceFolderModel::resolveResource(Resource::Ptr res) res->setResolving(true, ticket); m_active_parse_tasks.insert(ticket, task); - connect(task, &Task::succeeded, this, [=] { onParseSucceeded(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection); - connect(task, &Task::failed, this, [=] { onParseFailed(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection); + connect( + task, &Task::succeeded, this, [=] { onParseSucceeded(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection); + connect( + task, &Task::failed, this, [=] { onParseFailed(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection); auto* thread_pool = QThreadPool::globalInstance(); thread_pool->start(task); @@ -325,6 +332,71 @@ bool ResourceFolderModel::validateIndex(const QModelIndex& index) const return true; } +QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const +{ + if (!validateIndex(index)) + return {}; + + int row = index.row(); + int column = index.column(); + + switch (role) { + case Qt::DisplayRole: + switch (column) { + case NAME_COLUMN: + return m_resources[row]->name(); + case DATE_COLUMN: + return m_resources[row]->dateTimeChanged(); + default: + return {}; + } + case Qt::ToolTipRole: + return m_resources[row]->internal_id(); + default: + return {}; + } +} + +QVariant ResourceFolderModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + switch (role) { + case Qt::DisplayRole: + switch (section) { + case NAME_COLUMN: + return tr("Name"); + case DATE_COLUMN: + return tr("Last modified"); + default: + return {}; + } + case Qt::ToolTipRole: { + switch (section) { + case NAME_COLUMN: + return tr("The name of the resource."); + case DATE_COLUMN: + return tr("The date and time this resource was last changed (or added)."); + default: + return {}; + } + } + default: + break; + } + + return {}; +} + +QSortFilterProxyModel* ResourceFolderModel::createFilterProxyModel(QObject* parent) +{ + return new ProxyModel(parent); +} + +SortType ResourceFolderModel::columnToSortKey(size_t column) const +{ + Q_ASSERT(m_column_sort_keys.size() == columnCount()); + return m_column_sort_keys.at(column); +} + void ResourceFolderModel::enableInteraction(bool enabled) { if (m_can_interact == enabled) @@ -334,3 +406,39 @@ void ResourceFolderModel::enableInteraction(bool enabled) if (size()) emit dataChanged(index(0), index(size() - 1)); } + +/* Standard Proxy Model for createFilterProxyModel */ +[[nodiscard]] bool ResourceFolderModel::ProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const +{ + auto* model = qobject_cast(sourceModel()); + if (!model) + return true; + + const auto& resource = model->at(source_row); + + return resource.applyFilter(filterRegularExpression()); +} + +[[nodiscard]] bool ResourceFolderModel::ProxyModel::lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const +{ + auto* model = qobject_cast(sourceModel()); + if (!model || !source_left.isValid() || !source_right.isValid() || source_left.column() != source_right.column()) { + return QSortFilterProxyModel::lessThan(source_left, source_right); + } + + // we are now guaranteed to have two valid indexes in the same column... we love the provided invariants unconditionally and + // proceed. + + auto column_sort_key = model->columnToSortKey(source_left.column()); + auto const& resource_left = model->at(source_left.row()); + auto const& resource_right = model->at(source_right.row()); + + auto compare_result = resource_left.compare(resource_right, column_sort_key); + if (compare_result.first == 0) + return QSortFilterProxyModel::lessThan(source_left, source_right); + + if (compare_result.second || sortOrder() != Qt::DescendingOrder) + return (compare_result.first < 0); + return (compare_result.first > 0); +} + diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index 31fd7414..2ccf14f0 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -5,12 +5,13 @@ #include #include #include +#include #include "Resource.h" #include "tasks/Task.h" -class QRunnable; +class QSortFilterProxyModel; /** A basic model for external resources. * @@ -38,6 +39,10 @@ class ResourceFolderModel : public QAbstractListModel { */ bool stopWatching(const QStringList paths); + /* Helper methods for subclasses, using a predetermined list of paths. */ + virtual bool startWatching() { return startWatching({ m_dir.absolutePath() }); }; + virtual bool stopWatching() { return stopWatching({ m_dir.absolutePath() }); }; + /** Given a path in the system, install that resource, moving it to its place in the * instance file hierarchy. * @@ -61,13 +66,18 @@ class ResourceFolderModel : public QAbstractListModel { [[nodiscard]] size_t size() const { return m_resources.size(); }; [[nodiscard]] bool empty() const { return size() == 0; } + [[nodiscard]] Resource const& at(int index) const { return *m_resources.at(index); } + [[nodiscard]] QList const& all() const { return m_resources; } [[nodiscard]] QDir const& dir() const { return m_dir; } /* Qt behavior */ - [[nodiscard]] int rowCount(const QModelIndex&) const override { return size(); } - [[nodiscard]] int columnCount(const QModelIndex&) const override = 0; + /* Basic columns */ + enum Columns { NAME_COLUMN = 0, DATE_COLUMN, NUM_COLUMNS }; + + [[nodiscard]] int rowCount(const QModelIndex& = {}) const override { return size(); } + [[nodiscard]] int columnCount(const QModelIndex& = {}) const override { return NUM_COLUMNS; }; [[nodiscard]] Qt::DropActions supportedDropActions() const override; @@ -78,13 +88,31 @@ class ResourceFolderModel : public QAbstractListModel { [[nodiscard]] bool validateIndex(const QModelIndex& index) const; - [[nodiscard]] QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override = 0; + [[nodiscard]] QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override { return false; }; - [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override = 0; + [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + + /** This creates a proxy model to filter / sort the model for a UI. + * + * The actual comparisons and filtering are done directly by the Resource, so to modify behavior go there instead! + */ + QSortFilterProxyModel* createFilterProxyModel(QObject* parent = nullptr); + + [[nodiscard]] SortType columnToSortKey(size_t column) const; + + class ProxyModel : public QSortFilterProxyModel { + public: + explicit ProxyModel(QObject* parent = nullptr) : QSortFilterProxyModel(parent) {} + + protected: + [[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override; + [[nodiscard]] bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const override; + }; public slots: void enableInteraction(bool enabled); + void disableInteraction(bool disabled) { enableInteraction(!disabled); } signals: void updateFinished(); @@ -137,6 +165,10 @@ class ResourceFolderModel : public QAbstractListModel { virtual void onParseFailed(int ticket, QString resource_id) {} protected: + // Represents the relationship between a column's index (represented by the list index), and it's sorting key. + // As such, the order in with they appear is very important! + QList m_column_sort_keys = { SortType::NAME, SortType::DATE }; + bool m_can_interact = true; QDir m_dir; From 256f8094f5fed85ff9136e8d0b9c9677d7b9e9db Mon Sep 17 00:00:00 2001 From: flow Date: Wed, 10 Aug 2022 14:46:28 -0300 Subject: [PATCH 121/273] refactor: make Resource Pack model inherit from ResourceFolderModel Signed-off-by: flow --- .../minecraft/mod/ResourcePackFolderModel.cpp | 21 +------------------ .../minecraft/mod/ResourcePackFolderModel.h | 7 ++----- 2 files changed, 3 insertions(+), 25 deletions(-) diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index 276804ed..64a04469 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -35,24 +35,5 @@ #include "ResourcePackFolderModel.h" -ResourcePackFolderModel::ResourcePackFolderModel(const QString &dir) : ModFolderModel(dir) { -} - -QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orientation, int role) const { - if (role == Qt::ToolTipRole) { - switch (section) { - case ActiveColumn: - return tr("Is the resource pack enabled?"); - case NameColumn: - return tr("The name of the resource pack."); - case VersionColumn: - return tr("The version of the resource pack."); - case DateColumn: - return tr("The date and time this resource pack was last changed (or added)."); - default: - return QVariant(); - } - } - - return ModFolderModel::headerData(section, orientation, role); +ResourcePackFolderModel::ResourcePackFolderModel(const QString &dir) : ResourceFolderModel(dir) { } diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.h b/launcher/minecraft/mod/ResourcePackFolderModel.h index 0cd6214b..d2a5bf18 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.h +++ b/launcher/minecraft/mod/ResourcePackFolderModel.h @@ -1,13 +1,10 @@ #pragma once -#include "ModFolderModel.h" +#include "ResourceFolderModel.h" -class ResourcePackFolderModel : public ModFolderModel +class ResourcePackFolderModel : public ResourceFolderModel { Q_OBJECT - public: explicit ResourcePackFolderModel(const QString &dir); - - QVariant headerData(int section, Qt::Orientation orientation, int role) const override; }; From 97a74d5c1f00a11d331a41b16690f7202fe102a3 Mon Sep 17 00:00:00 2001 From: flow Date: Wed, 10 Aug 2022 14:48:34 -0300 Subject: [PATCH 122/273] refactor: adapt rest of the codebase to the new resource model In order to access the ModFolderModel from the ModFolderPage, i created a new m_model for the correct type, shadowing the m_model of type ResourceFolderModel. This creates two shared_ptr references to the same object, but since they will have the same lifetime, it doesn't generate a memory leak. Signed-off-by: flow --- launcher/CMakeLists.txt | 7 +- launcher/InstancePageProvider.h | 6 +- launcher/minecraft/MinecraftInstance.cpp | 13 +- launcher/minecraft/MinecraftInstance.h | 16 +- launcher/minecraft/mod/Mod.h | 3 +- launcher/minecraft/mod/ModFolderModel.cpp | 2 +- launcher/minecraft/mod/Resource.h | 2 + launcher/minecraft/mod/ResourceFolderModel.h | 3 + launcher/minecraft/mod/ResourcePack.h | 13 ++ .../minecraft/mod/ResourcePackFolderModel.cpp | 3 +- .../minecraft/mod/ResourcePackFolderModel.h | 4 + .../minecraft/mod/ShaderPackFolderModel.h | 10 ++ .../minecraft/mod/TexturePackFolderModel.cpp | 22 +-- .../minecraft/mod/TexturePackFolderModel.h | 6 +- .../pages/instance/ExternalResourcesPage.cpp | 142 +++--------------- .../ui/pages/instance/ExternalResourcesPage.h | 17 ++- .../pages/instance/ExternalResourcesPage.ui | 6 +- launcher/ui/pages/instance/ModFolderPage.cpp | 42 +++++- launcher/ui/pages/instance/ModFolderPage.h | 11 ++ launcher/ui/pages/instance/ResourcePackPage.h | 6 +- launcher/ui/pages/instance/ShaderPackPage.h | 6 +- launcher/ui/pages/instance/TexturePackPage.h | 6 +- launcher/ui/pages/instance/VersionPage.cpp | 6 +- launcher/ui/pages/instance/VersionPage.ui | 6 +- .../{MCModInfoFrame.cpp => InfoFrame.cpp} | 96 ++++++------ .../widgets/{MCModInfoFrame.h => InfoFrame.h} | 36 ++--- .../{MCModInfoFrame.ui => InfoFrame.ui} | 8 +- 27 files changed, 235 insertions(+), 263 deletions(-) create mode 100644 launcher/minecraft/mod/ResourcePack.h create mode 100644 launcher/minecraft/mod/ShaderPackFolderModel.h rename launcher/ui/widgets/{MCModInfoFrame.cpp => InfoFrame.cpp} (55%) rename launcher/ui/widgets/{MCModInfoFrame.h => InfoFrame.h} (59%) rename launcher/ui/widgets/{MCModInfoFrame.ui => InfoFrame.ui} (92%) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index d1e0befd..badd0eaa 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -326,6 +326,7 @@ set(MINECRAFT_SOURCES minecraft/mod/ResourcePackFolderModel.cpp minecraft/mod/TexturePackFolderModel.h minecraft/mod/TexturePackFolderModel.cpp + minecraft/mod/ShaderPackFolderModel.h minecraft/mod/tasks/BasicFolderLoadTask.h minecraft/mod/tasks/ModFolderLoadTask.h minecraft/mod/tasks/ModFolderLoadTask.cpp @@ -884,8 +885,8 @@ SET(LAUNCHER_SOURCES ui/widgets/LineSeparator.h ui/widgets/LogView.cpp ui/widgets/LogView.h - ui/widgets/MCModInfoFrame.cpp - ui/widgets/MCModInfoFrame.h + ui/widgets/InfoFrame.cpp + ui/widgets/InfoFrame.h ui/widgets/ModFilterWidget.cpp ui/widgets/ModFilterWidget.h ui/widgets/ModListView.cpp @@ -947,7 +948,7 @@ qt_wrap_ui(LAUNCHER_UI ui/pages/modplatform/technic/TechnicPage.ui ui/widgets/InstanceCardWidget.ui ui/widgets/CustomCommands.ui - ui/widgets/MCModInfoFrame.ui + ui/widgets/InfoFrame.ui ui/widgets/ModFilterWidget.ui ui/dialogs/CopyInstanceDialog.ui ui/dialogs/ProfileSetupDialog.ui diff --git a/launcher/InstancePageProvider.h b/launcher/InstancePageProvider.h index 78fb7016..bf29377d 100644 --- a/launcher/InstancePageProvider.h +++ b/launcher/InstancePageProvider.h @@ -37,9 +37,9 @@ public: modsPage->setFilter("%1 (*.zip *.jar *.litemod)"); values.append(modsPage); values.append(new CoreModFolderPage(onesix.get(), onesix->coreModList())); - values.append(new ResourcePackPage(onesix.get())); - values.append(new TexturePackPage(onesix.get())); - values.append(new ShaderPackPage(onesix.get())); + values.append(new ResourcePackPage(onesix.get(), onesix->resourcePackList())); + values.append(new TexturePackPage(onesix.get(), onesix->texturePackList())); + values.append(new ShaderPackPage(onesix.get(), onesix->shaderPackList())); values.append(new NotesPage(onesix.get())); values.append(new WorldListPage(onesix.get(), onesix->worldList())); values.append(new ServersPage(onesix)); diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index b42aeda3..94b4776e 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -76,6 +76,7 @@ #include "mod/ModFolderModel.h" #include "mod/ResourcePackFolderModel.h" +#include "mod/ShaderPackFolderModel.h" #include "mod/TexturePackFolderModel.h" #include "WorldList.h" @@ -1092,18 +1093,18 @@ std::shared_ptr MinecraftInstance::coreModList() const return m_core_mod_list; } -std::shared_ptr MinecraftInstance::resourcePackList() const +std::shared_ptr MinecraftInstance::resourcePackList() const { if (!m_resource_pack_list) { m_resource_pack_list.reset(new ResourcePackFolderModel(resourcePacksDir())); - m_resource_pack_list->disableInteraction(isRunning()); - connect(this, &BaseInstance::runningStatusChanged, m_resource_pack_list.get(), &ModFolderModel::disableInteraction); + m_resource_pack_list->enableInteraction(!isRunning()); + connect(this, &BaseInstance::runningStatusChanged, m_resource_pack_list.get(), &ResourcePackFolderModel::disableInteraction); } return m_resource_pack_list; } -std::shared_ptr MinecraftInstance::texturePackList() const +std::shared_ptr MinecraftInstance::texturePackList() const { if (!m_texture_pack_list) { @@ -1114,11 +1115,11 @@ std::shared_ptr MinecraftInstance::texturePackList() const return m_texture_pack_list; } -std::shared_ptr MinecraftInstance::shaderPackList() const +std::shared_ptr MinecraftInstance::shaderPackList() const { if (!m_shader_pack_list) { - m_shader_pack_list.reset(new ResourcePackFolderModel(shaderPacksDir())); + m_shader_pack_list.reset(new ShaderPackFolderModel(shaderPacksDir())); m_shader_pack_list->disableInteraction(isRunning()); connect(this, &BaseInstance::runningStatusChanged, m_shader_pack_list.get(), &ModFolderModel::disableInteraction); } diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h index 7a75f452..d62ac655 100644 --- a/launcher/minecraft/MinecraftInstance.h +++ b/launcher/minecraft/MinecraftInstance.h @@ -7,6 +7,10 @@ #include "minecraft/launch/MinecraftServerTarget.h" class ModFolderModel; +class ResourceFolderModel; +class ResourcePackFolderModel; +class ShaderPackFolderModel; +class TexturePackFolderModel; class WorldList; class GameOptions; class LaunchStep; @@ -72,9 +76,9 @@ public: ////// Mod Lists ////// std::shared_ptr loaderModList() const; std::shared_ptr coreModList() const; - std::shared_ptr resourcePackList() const; - std::shared_ptr texturePackList() const; - std::shared_ptr shaderPackList() const; + std::shared_ptr resourcePackList() const; + std::shared_ptr texturePackList() const; + std::shared_ptr shaderPackList() const; std::shared_ptr worldList() const; std::shared_ptr gameOptionsModel() const; @@ -125,9 +129,9 @@ protected: // data std::shared_ptr m_components; mutable std::shared_ptr m_loader_mod_list; mutable std::shared_ptr m_core_mod_list; - mutable std::shared_ptr m_resource_pack_list; - mutable std::shared_ptr m_shader_pack_list; - mutable std::shared_ptr m_texture_pack_list; + mutable std::shared_ptr m_resource_pack_list; + mutable std::shared_ptr m_shader_pack_list; + mutable std::shared_ptr m_texture_pack_list; mutable std::shared_ptr m_world_list; mutable std::shared_ptr m_game_options; }; diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index 25ac88c9..0835e3b1 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -50,7 +50,8 @@ public: Mod() = default; Mod(const QFileInfo &file); - explicit Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata); + Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata); + Mod(QString file_path) : Mod(QFileInfo(file_path)) {} auto enabled() const -> bool { return m_enabled; } diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 9b8c58bc..c316e710 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -49,7 +49,7 @@ #include "minecraft/mod/tasks/LocalModParseTask.h" #include "minecraft/mod/tasks/ModFolderLoadTask.h" -ModFolderModel::ModFolderModel(const QString &dir, bool is_indexed) : ResourceFolderModel(dir), m_is_indexed(is_indexed) +ModFolderModel::ModFolderModel(const QString &dir, bool is_indexed) : ResourceFolderModel(QDir(dir)), m_is_indexed(is_indexed) { FS::ensureFolderPathExists(m_dir.absolutePath()); m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::VERSION, SortType::DATE }; diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index 68663ab0..bec7490f 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -34,6 +34,8 @@ class Resource : public QObject { Resource(QObject* parent = nullptr); Resource(QFileInfo file_info); + Resource(QString file_path) : Resource(QFileInfo(file_path)) {} + ~Resource() override = default; void setFile(QFileInfo file_info); diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index 2ccf14f0..986d9885 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -261,6 +261,9 @@ void ResourceFolderModel::applyUpdates(QSet& current_set, QSet std::sort(removed_rows.begin(), removed_rows.end()); + for (int i = 0; i < removed_rows.size(); i++) + removed_rows[i] -= i; + for (auto& removed_index : removed_rows) { beginRemoveRows(QModelIndex(), removed_index, removed_index); diff --git a/launcher/minecraft/mod/ResourcePack.h b/launcher/minecraft/mod/ResourcePack.h new file mode 100644 index 00000000..c2cc8690 --- /dev/null +++ b/launcher/minecraft/mod/ResourcePack.h @@ -0,0 +1,13 @@ +#pragma once + +#include "Resource.h" + +class ResourcePack : public Resource { + Q_OBJECT + public: + using Ptr = shared_qobject_ptr; + + ResourcePack(QObject* parent = nullptr) : Resource(parent) {} + ResourcePack(QFileInfo file_info) : Resource(file_info) {} + +}; diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index 64a04469..e92be894 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -35,5 +35,4 @@ #include "ResourcePackFolderModel.h" -ResourcePackFolderModel::ResourcePackFolderModel(const QString &dir) : ResourceFolderModel(dir) { -} +ResourcePackFolderModel::ResourcePackFolderModel(const QString &dir) : ResourceFolderModel(QDir(dir)) {} diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.h b/launcher/minecraft/mod/ResourcePackFolderModel.h index d2a5bf18..1fe82867 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.h +++ b/launcher/minecraft/mod/ResourcePackFolderModel.h @@ -2,9 +2,13 @@ #include "ResourceFolderModel.h" +#include "ResourcePack.h" + class ResourcePackFolderModel : public ResourceFolderModel { Q_OBJECT public: explicit ResourcePackFolderModel(const QString &dir); + + RESOURCE_HELPERS(ResourcePack) }; diff --git a/launcher/minecraft/mod/ShaderPackFolderModel.h b/launcher/minecraft/mod/ShaderPackFolderModel.h new file mode 100644 index 00000000..a3aa958f --- /dev/null +++ b/launcher/minecraft/mod/ShaderPackFolderModel.h @@ -0,0 +1,10 @@ +#pragma once + +#include "ResourceFolderModel.h" + +class ShaderPackFolderModel : public ResourceFolderModel { + Q_OBJECT + + public: + explicit ShaderPackFolderModel(const QString& dir) : ResourceFolderModel(QDir(dir)) {} +}; diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp index e3a22219..2c7c945b 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.cpp +++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp @@ -35,24 +35,4 @@ #include "TexturePackFolderModel.h" -TexturePackFolderModel::TexturePackFolderModel(const QString &dir) : ModFolderModel(dir) { -} - -QVariant TexturePackFolderModel::headerData(int section, Qt::Orientation orientation, int role) const { - if (role == Qt::ToolTipRole) { - switch (section) { - case ActiveColumn: - return tr("Is the texture pack enabled?"); - case NameColumn: - return tr("The name of the texture pack."); - case VersionColumn: - return tr("The version of the texture pack."); - case DateColumn: - return tr("The date and time this texture pack was last changed (or added)."); - default: - return QVariant(); - } - } - - return ModFolderModel::headerData(section, orientation, role); -} +TexturePackFolderModel::TexturePackFolderModel(const QString &dir) : ResourceFolderModel(QDir(dir)) {} diff --git a/launcher/minecraft/mod/TexturePackFolderModel.h b/launcher/minecraft/mod/TexturePackFolderModel.h index a59d5119..69e98661 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.h +++ b/launcher/minecraft/mod/TexturePackFolderModel.h @@ -1,13 +1,11 @@ #pragma once -#include "ModFolderModel.h" +#include "ResourceFolderModel.h" -class TexturePackFolderModel : public ModFolderModel +class TexturePackFolderModel : public ResourceFolderModel { Q_OBJECT public: explicit TexturePackFolderModel(const QString &dir); - - QVariant headerData(int section, Qt::Orientation orientation, int role) const override; }; diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp index da7c4af0..0a3687e5 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.cpp +++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp @@ -3,100 +3,13 @@ #include "DesktopServices.h" #include "Version.h" -#include "minecraft/mod/ModFolderModel.h" +#include "minecraft/mod/ResourceFolderModel.h" #include "ui/GuiUtil.h" #include #include -namespace { -// FIXME: wasteful -void RemoveThePrefix(QString& string) -{ - QRegularExpression regex(QStringLiteral("^(?:the|teh) +"), QRegularExpression::CaseInsensitiveOption); - string.remove(regex); - string = string.trimmed(); -} -} // namespace - -class SortProxy : public QSortFilterProxyModel { - public: - explicit SortProxy(QObject* parent = nullptr) : QSortFilterProxyModel(parent) {} - - protected: - bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const override - { - ModFolderModel* model = qobject_cast(sourceModel()); - if (!model) - return false; - - const auto& mod = model->at(source_row); - - if (filterRegularExpression().match(mod->name()).hasMatch()) - return true; - if (filterRegularExpression().match(mod->description()).hasMatch()) - return true; - - for (auto& author : mod->authors()) { - if (filterRegularExpression().match(author).hasMatch()) { - return true; - } - } - - return false; - } - - bool lessThan(const QModelIndex& source_left, const QModelIndex& source_right) const override - { - ModFolderModel* model = qobject_cast(sourceModel()); - if (!model || !source_left.isValid() || !source_right.isValid() || source_left.column() != source_right.column()) { - return QSortFilterProxyModel::lessThan(source_left, source_right); - } - - // we are now guaranteed to have two valid indexes in the same column... we love the provided invariants unconditionally and - // proceed. - - auto column = (ModFolderModel::Columns) source_left.column(); - bool invert = false; - switch (column) { - // GH-2550 - sort by enabled/disabled - case ModFolderModel::ActiveColumn: { - auto dataL = source_left.data(Qt::CheckStateRole).toBool(); - auto dataR = source_right.data(Qt::CheckStateRole).toBool(); - if (dataL != dataR) - return dataL > dataR; - - // fallthrough - invert = sortOrder() == Qt::DescendingOrder; - } - // GH-2722 - sort mod names in a way that discards "The" prefixes - case ModFolderModel::NameColumn: { - auto dataL = model->data(model->index(source_left.row(), ModFolderModel::NameColumn)).toString(); - RemoveThePrefix(dataL); - auto dataR = model->data(model->index(source_right.row(), ModFolderModel::NameColumn)).toString(); - RemoveThePrefix(dataR); - - auto less = dataL.compare(dataR, sortCaseSensitivity()); - if (less != 0) - return invert ? (less > 0) : (less < 0); - - // fallthrough - invert = sortOrder() == Qt::DescendingOrder; - } - // GH-2762 - sort versions by parsing them as versions - case ModFolderModel::VersionColumn: { - auto dataL = Version(model->data(model->index(source_left.row(), ModFolderModel::VersionColumn)).toString()); - auto dataR = Version(model->data(model->index(source_right.row(), ModFolderModel::VersionColumn)).toString()); - return invert ? (dataL > dataR) : (dataL < dataR); - } - default: { - return QSortFilterProxyModel::lessThan(source_left, source_right); - } - } - } -}; - -ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared_ptr model, QWidget* parent) +ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared_ptr model, QWidget* parent) : QMainWindow(parent), m_instance(instance), ui(new Ui::ExternalResourcesPage), m_model(model) { ui->setupUi(this); @@ -105,7 +18,7 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared ui->actionsToolbar->insertSpacer(ui->actionViewConfigs); - m_filterModel = new SortProxy(this); + m_filterModel = model->createFilterProxyModel(this); m_filterModel->setDynamicSortFilter(true); m_filterModel->setFilterCaseSensitivity(Qt::CaseInsensitive); m_filterModel->setSortCaseSensitivity(Qt::CaseInsensitive); @@ -127,7 +40,6 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared connect(ui->actionViewFolder, &QAction::triggered, this, &ExternalResourcesPage::viewFolder); connect(ui->treeView, &ModListView::customContextMenuRequested, this, &ExternalResourcesPage::ShowContextMenu); - connect(ui->treeView, &ModListView::activated, this, &ExternalResourcesPage::itemActivated); auto selection_model = ui->treeView->selectionModel(); connect(selection_model, &QItemSelectionModel::currentChanged, this, &ExternalResourcesPage::current); @@ -137,19 +49,9 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared ExternalResourcesPage::~ExternalResourcesPage() { - m_model->stopWatching(); delete ui; } -void ExternalResourcesPage::itemActivated(const QModelIndex&) -{ - if (!m_controlsEnabled) - return; - - auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()); - m_model->setModStatus(selection.indexes(), ModFolderModel::Toggle); -} - QMenu* ExternalResourcesPage::createPopupMenu() { QMenu* filteredMenu = QMainWindow::createPopupMenu(); @@ -241,7 +143,7 @@ void ExternalResourcesPage::addItem() if (!list.isEmpty()) { for (auto filename : list) { - m_model->installMod(filename); + m_model->installResource(filename); } } } @@ -252,25 +154,7 @@ void ExternalResourcesPage::removeItem() return; auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()); - m_model->deleteMods(selection.indexes()); -} - -void ExternalResourcesPage::enableItem() -{ - if (!m_controlsEnabled) - return; - - auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()); - m_model->setModStatus(selection.indexes(), ModFolderModel::Enable); -} - -void ExternalResourcesPage::disableItem() -{ - if (!m_controlsEnabled) - return; - - auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()); - m_model->setModStatus(selection.indexes(), ModFolderModel::Disable); + m_model->deleteResources(selection.indexes()); } void ExternalResourcesPage::viewConfigs() @@ -283,15 +167,23 @@ void ExternalResourcesPage::viewFolder() DesktopServices::openDirectory(m_model->dir().absolutePath(), true); } -void ExternalResourcesPage::current(const QModelIndex& current, const QModelIndex& previous) +bool ExternalResourcesPage::current(const QModelIndex& current, const QModelIndex& previous) { if (!current.isValid()) { ui->frame->clear(); - return; + return false; } + return onSelectionChanged(current, previous); +} + +bool ExternalResourcesPage::onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) +{ auto sourceCurrent = m_filterModel->mapToSource(current); int row = sourceCurrent.row(); - Mod& m = *m_model->operator[](row); - ui->frame->updateWithMod(m); + Resource const& resource = m_model->at(row); + ui->frame->updateWithResource(resource); + + return true; } + diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.h b/launcher/ui/pages/instance/ExternalResourcesPage.h index ff294678..280f1542 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.h +++ b/launcher/ui/pages/instance/ExternalResourcesPage.h @@ -7,7 +7,7 @@ #include "minecraft/MinecraftInstance.h" #include "ui/pages/BasePage.h" -class ModFolderModel; +class ResourceFolderModel; namespace Ui { class ExternalResourcesPage; @@ -19,8 +19,7 @@ class ExternalResourcesPage : public QMainWindow, public BasePage { Q_OBJECT public: - // FIXME: Switch to different model (or change the name of this one) - explicit ExternalResourcesPage(BaseInstance* instance, std::shared_ptr model, QWidget* parent = nullptr); + explicit ExternalResourcesPage(BaseInstance* instance, std::shared_ptr model, QWidget* parent = nullptr); virtual ~ExternalResourcesPage(); virtual QString displayName() const override = 0; @@ -41,18 +40,20 @@ class ExternalResourcesPage : public QMainWindow, public BasePage { QMenu* createPopupMenu() override; public slots: - void current(const QModelIndex& current, const QModelIndex& previous); + bool current(const QModelIndex& current, const QModelIndex& previous); + + virtual bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous); protected slots: - void itemActivated(const QModelIndex& index); + virtual void itemActivated(const QModelIndex& index) {}; void filterTextChanged(const QString& newContents); virtual void runningStateChanged(bool running); virtual void addItem(); virtual void removeItem(); - virtual void enableItem(); - virtual void disableItem(); + virtual void enableItem() {}; + virtual void disableItem() {}; virtual void viewFolder(); virtual void viewConfigs(); @@ -63,7 +64,7 @@ class ExternalResourcesPage : public QMainWindow, public BasePage { BaseInstance* m_instance = nullptr; Ui::ExternalResourcesPage* ui = nullptr; - std::shared_ptr m_model; + std::shared_ptr m_model; QSortFilterProxyModel* m_filterModel = nullptr; QString m_fileSelectionFilter; diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.ui b/launcher/ui/pages/instance/ExternalResourcesPage.ui index a13666b2..76f8ec18 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.ui +++ b/launcher/ui/pages/instance/ExternalResourcesPage.ui @@ -43,7 +43,7 @@
- + 0 @@ -166,9 +166,9 @@
ui/widgets/ModListView.h
- MCModInfoFrame + InfoFrame QFrame -
ui/widgets/MCModInfoFrame.h
+
ui/widgets/InfoFrame.h
1
diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 45678db1..63897fb0 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -65,7 +65,7 @@ #include "ui/dialogs/ProgressDialog.h" ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr mods, QWidget* parent) - : ExternalResourcesPage(inst, mods, parent) + : ExternalResourcesPage(inst, mods, parent), m_model(mods) { // This is structured like that so that these changes // do not affect the Resource pack and Shader pack tabs @@ -110,6 +110,8 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr ModFolderPage::runningStateChanged(m_instance && m_instance->isRunning()); } + + connect(ui->treeView, &ModListView::activated, this, &ModFolderPage::itemActivated); } void ModFolderPage::runningStateChanged(bool running) @@ -124,6 +126,44 @@ bool ModFolderPage::shouldDisplay() const return true; } +void ModFolderPage::itemActivated(const QModelIndex&) +{ + if (!m_controlsEnabled) + return; + + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()); + m_model->setModStatus(selection.indexes(), ModFolderModel::Toggle); +} + +void ModFolderPage::enableItem() +{ + if (!m_controlsEnabled) + return; + + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()); + m_model->setModStatus(selection.indexes(), ModFolderModel::Enable); +} + +void ModFolderPage::disableItem() +{ + if (!m_controlsEnabled) + return; + + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()); + m_model->setModStatus(selection.indexes(), ModFolderModel::Disable); +} + +bool ModFolderPage::onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) +{ + auto sourceCurrent = m_filterModel->mapToSource(current); + int row = sourceCurrent.row(); + Mod const* m = m_model->at(row); + if (m) + ui->frame->updateWithMod(*m); + + return true; +} + void ModFolderPage::installMods() { if (!m_controlsEnabled) diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index 7e305951..5da353f0 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -55,9 +55,20 @@ class ModFolderPage : public ExternalResourcesPage { virtual bool shouldDisplay() const override; void runningStateChanged(bool running) override; + public slots: + bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override; + + void itemActivated(const QModelIndex& index) override; + + void enableItem() override; + void disableItem() override; + private slots: void installMods(); void updateMods(); + + protected: + std::shared_ptr m_model; }; class CoreModFolderPage : public ModFolderPage { diff --git a/launcher/ui/pages/instance/ResourcePackPage.h b/launcher/ui/pages/instance/ResourcePackPage.h index a6c9fdd3..2eefc3d3 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.h +++ b/launcher/ui/pages/instance/ResourcePackPage.h @@ -38,12 +38,14 @@ #include "ExternalResourcesPage.h" #include "ui_ExternalResourcesPage.h" +#include "minecraft/mod/ResourcePackFolderModel.h" + class ResourcePackPage : public ExternalResourcesPage { Q_OBJECT public: - explicit ResourcePackPage(MinecraftInstance *instance, QWidget *parent = 0) - : ExternalResourcesPage(instance, instance->resourcePackList(), parent) + explicit ResourcePackPage(MinecraftInstance *instance, std::shared_ptr model, QWidget *parent = 0) + : ExternalResourcesPage(instance, model, parent) { ui->actionViewConfigs->setVisible(false); } diff --git a/launcher/ui/pages/instance/ShaderPackPage.h b/launcher/ui/pages/instance/ShaderPackPage.h index 2cc056c8..7f7ff8c1 100644 --- a/launcher/ui/pages/instance/ShaderPackPage.h +++ b/launcher/ui/pages/instance/ShaderPackPage.h @@ -38,12 +38,14 @@ #include "ExternalResourcesPage.h" #include "ui_ExternalResourcesPage.h" +#include "minecraft/mod/ShaderPackFolderModel.h" + class ShaderPackPage : public ExternalResourcesPage { Q_OBJECT public: - explicit ShaderPackPage(MinecraftInstance *instance, QWidget *parent = 0) - : ExternalResourcesPage(instance, instance->shaderPackList(), parent) + explicit ShaderPackPage(MinecraftInstance *instance, std::shared_ptr model, QWidget *parent = 0) + : ExternalResourcesPage(instance, model, parent) { ui->actionViewConfigs->setVisible(false); } diff --git a/launcher/ui/pages/instance/TexturePackPage.h b/launcher/ui/pages/instance/TexturePackPage.h index f550a5bc..fa219eda 100644 --- a/launcher/ui/pages/instance/TexturePackPage.h +++ b/launcher/ui/pages/instance/TexturePackPage.h @@ -38,12 +38,14 @@ #include "ExternalResourcesPage.h" #include "ui_ExternalResourcesPage.h" +#include "minecraft/mod/TexturePackFolderModel.h" + class TexturePackPage : public ExternalResourcesPage { Q_OBJECT public: - explicit TexturePackPage(MinecraftInstance *instance, QWidget *parent = 0) - : ExternalResourcesPage(instance, instance->texturePackList(), parent) + explicit TexturePackPage(MinecraftInstance *instance, std::shared_ptr model, QWidget *parent = 0) + : ExternalResourcesPage(instance, model, parent) { ui->actionViewConfigs->setVisible(false); } diff --git a/launcher/ui/pages/instance/VersionPage.cpp b/launcher/ui/pages/instance/VersionPage.cpp index 468ff35c..a021c633 100644 --- a/launcher/ui/pages/instance/VersionPage.cpp +++ b/launcher/ui/pages/instance/VersionPage.cpp @@ -196,10 +196,10 @@ void VersionPage::packageCurrent(const QModelIndex ¤t, const QModelIndex & switch(severity) { case ProblemSeverity::Warning: - ui->frame->setModText(tr("%1 possibly has issues.").arg(patch->getName())); + ui->frame->setName(tr("%1 possibly has issues.").arg(patch->getName())); break; case ProblemSeverity::Error: - ui->frame->setModText(tr("%1 has issues!").arg(patch->getName())); + ui->frame->setName(tr("%1 has issues!").arg(patch->getName())); break; default: case ProblemSeverity::None: @@ -222,7 +222,7 @@ void VersionPage::packageCurrent(const QModelIndex ¤t, const QModelIndex & problemOut += problem.m_description; problemOut += "\n"; } - ui->frame->setModDescription(problemOut); + ui->frame->setDescription(problemOut); } void VersionPage::updateRunningStatus(bool running) diff --git a/launcher/ui/pages/instance/VersionPage.ui b/launcher/ui/pages/instance/VersionPage.ui index 489f7218..fcba5598 100644 --- a/launcher/ui/pages/instance/VersionPage.ui +++ b/launcher/ui/pages/instance/VersionPage.ui @@ -64,7 +64,7 @@
- + 0 @@ -278,9 +278,9 @@
ui/widgets/ModListView.h
- MCModInfoFrame + InfoFrame QFrame -
ui/widgets/MCModInfoFrame.h
+
ui/widgets/InfoFrame.h
1
diff --git a/launcher/ui/widgets/MCModInfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp similarity index 55% rename from launcher/ui/widgets/MCModInfoFrame.cpp rename to launcher/ui/widgets/InfoFrame.cpp index 22475abc..821e61a7 100644 --- a/launcher/ui/widgets/MCModInfoFrame.cpp +++ b/launcher/ui/widgets/InfoFrame.cpp @@ -14,14 +14,28 @@ */ #include -#include -#include "MCModInfoFrame.h" -#include "ui_MCModInfoFrame.h" +#include "InfoFrame.h" +#include "ui_InfoFrame.h" #include "ui/dialogs/CustomMessageBox.h" -void MCModInfoFrame::updateWithMod(Mod &m) +InfoFrame::InfoFrame(QWidget *parent) : + QFrame(parent), + ui(new Ui::InfoFrame) +{ + ui->setupUi(this); + ui->descriptionLabel->setHidden(true); + ui->nameLabel->setHidden(true); + updateHiddenState(); +} + +InfoFrame::~InfoFrame() +{ + delete ui; +} + +void InfoFrame::updateWithMod(Mod const& m) { if (m.type() == ResourceType::FOLDER) { @@ -43,42 +57,32 @@ void MCModInfoFrame::updateWithMod(Mod &m) if (!m.authors().isEmpty()) text += " by " + m.authors().join(", "); - setModText(text); + setName(text); if (m.description().isEmpty()) { - setModDescription(QString()); + setDescription(QString()); } else { - setModDescription(m.description()); + setDescription(m.description()); } } -void MCModInfoFrame::clear() +void InfoFrame::updateWithResource(const Resource& resource) { - setModText(QString()); - setModDescription(QString()); + setName(resource.name()); } -MCModInfoFrame::MCModInfoFrame(QWidget *parent) : - QFrame(parent), - ui(new Ui::MCModInfoFrame) +void InfoFrame::clear() { - ui->setupUi(this); - ui->label_ModDescription->setHidden(true); - ui->label_ModText->setHidden(true); - updateHiddenState(); + setName(); + setDescription(); } -MCModInfoFrame::~MCModInfoFrame() +void InfoFrame::updateHiddenState() { - delete ui; -} - -void MCModInfoFrame::updateHiddenState() -{ - if(ui->label_ModDescription->isHidden() && ui->label_ModText->isHidden()) + if(ui->descriptionLabel->isHidden() && ui->nameLabel->isHidden()) { setHidden(true); } @@ -88,34 +92,34 @@ void MCModInfoFrame::updateHiddenState() } } -void MCModInfoFrame::setModText(QString text) +void InfoFrame::setName(QString text) { if(text.isEmpty()) { - ui->label_ModText->setHidden(true); + ui->nameLabel->setHidden(true); } else { - ui->label_ModText->setText(text); - ui->label_ModText->setHidden(false); + ui->nameLabel->setText(text); + ui->nameLabel->setHidden(false); } updateHiddenState(); } -void MCModInfoFrame::setModDescription(QString text) +void InfoFrame::setDescription(QString text) { if(text.isEmpty()) { - ui->label_ModDescription->setHidden(true); + ui->descriptionLabel->setHidden(true); updateHiddenState(); return; } else { - ui->label_ModDescription->setHidden(false); + ui->descriptionLabel->setHidden(false); updateHiddenState(); } - ui->label_ModDescription->setToolTip(""); + ui->descriptionLabel->setToolTip(""); QString intermediatetext = text.trimmed(); bool prev(false); QChar rem('\n'); @@ -133,36 +137,36 @@ void MCModInfoFrame::setModDescription(QString text) labeltext.reserve(300); if(finaltext.length() > 290) { - ui->label_ModDescription->setOpenExternalLinks(false); - ui->label_ModDescription->setTextFormat(Qt::TextFormat::RichText); - desc = text; + ui->descriptionLabel->setOpenExternalLinks(false); + ui->descriptionLabel->setTextFormat(Qt::TextFormat::RichText); + m_description = text; // This allows injecting HTML here. labeltext.append("" + finaltext.left(287) + "..."); - QObject::connect(ui->label_ModDescription, &QLabel::linkActivated, this, &MCModInfoFrame::modDescEllipsisHandler); + QObject::connect(ui->descriptionLabel, &QLabel::linkActivated, this, &InfoFrame::descriptionEllipsisHandler); } else { - ui->label_ModDescription->setTextFormat(Qt::TextFormat::PlainText); + ui->descriptionLabel->setTextFormat(Qt::TextFormat::PlainText); labeltext.append(finaltext); } - ui->label_ModDescription->setText(labeltext); + ui->descriptionLabel->setText(labeltext); } -void MCModInfoFrame::modDescEllipsisHandler(const QString &link) +void InfoFrame::descriptionEllipsisHandler(QString link) { - if(!currentBox) + if(!m_current_box) { - currentBox = CustomMessageBox::selectable(this, QString(), desc); - connect(currentBox, &QMessageBox::finished, this, &MCModInfoFrame::boxClosed); - currentBox->show(); + m_current_box = CustomMessageBox::selectable(this, "", m_description); + connect(m_current_box, &QMessageBox::finished, this, &InfoFrame::boxClosed); + m_current_box->show(); } else { - currentBox->setText(desc); + m_current_box->setText(m_description); } } -void MCModInfoFrame::boxClosed(int result) +void InfoFrame::boxClosed(int result) { - currentBox = nullptr; + m_current_box = nullptr; } diff --git a/launcher/ui/widgets/MCModInfoFrame.h b/launcher/ui/widgets/InfoFrame.h similarity index 59% rename from launcher/ui/widgets/MCModInfoFrame.h rename to launcher/ui/widgets/InfoFrame.h index 0b7ef537..d69dc232 100644 --- a/launcher/ui/widgets/MCModInfoFrame.h +++ b/launcher/ui/widgets/InfoFrame.h @@ -16,37 +16,39 @@ #pragma once #include + #include "minecraft/mod/Mod.h" +#include "minecraft/mod/ResourcePack.h" namespace Ui { -class MCModInfoFrame; +class InfoFrame; } -class MCModInfoFrame : public QFrame -{ +class InfoFrame : public QFrame { Q_OBJECT -public: - explicit MCModInfoFrame(QWidget *parent = 0); - ~MCModInfoFrame(); + public: + InfoFrame(QWidget* parent = nullptr); + ~InfoFrame() override; - void setModText(QString text); - void setModDescription(QString text); + void setName(QString text = {}); + void setDescription(QString text = {}); - void updateWithMod(Mod &m); void clear(); -public slots: - void modDescEllipsisHandler(const QString& link ); + void updateWithMod(Mod const& m); + void updateWithResource(Resource const& resource); + + public slots: + void descriptionEllipsisHandler(QString link); void boxClosed(int result); -private: + private: void updateHiddenState(); -private: - Ui::MCModInfoFrame *ui; - QString desc; - class QMessageBox * currentBox = nullptr; + private: + Ui::InfoFrame* ui; + QString m_description; + class QMessageBox* m_current_box = nullptr; }; - diff --git a/launcher/ui/widgets/MCModInfoFrame.ui b/launcher/ui/widgets/InfoFrame.ui similarity index 92% rename from launcher/ui/widgets/MCModInfoFrame.ui rename to launcher/ui/widgets/InfoFrame.ui index 5ef33379..0d3772d7 100644 --- a/launcher/ui/widgets/MCModInfoFrame.ui +++ b/launcher/ui/widgets/InfoFrame.ui @@ -1,7 +1,7 @@ - MCModInfoFrame - + InfoFrame + 0 @@ -39,7 +39,7 @@ 0 - + @@ -61,7 +61,7 @@ - + From 92aa24ae345c7b50028ea672c0d175d87e906c59 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 11 Aug 2022 18:24:26 -0300 Subject: [PATCH 123/273] fix: don't give shared pointer to obj. external to the model It causes some weird problems and adds refcounting overhead. Signed-off-by: flow --- launcher/minecraft/mod/Mod.h | 1 + launcher/minecraft/mod/ModFolderModel.cpp | 11 +-- launcher/minecraft/mod/ModFolderModel.h | 4 +- launcher/minecraft/mod/Resource.h | 2 + .../minecraft/mod/ResourceFolderModel.cpp | 6 +- launcher/minecraft/mod/ResourceFolderModel.h | 71 ++++++++++--------- .../minecraft/mod/tasks/BasicFolderLoadTask.h | 2 +- .../minecraft/mod/tasks/ModFolderLoadTask.cpp | 2 +- launcher/ui/dialogs/ModUpdateDialog.cpp | 9 ++- launcher/ui/dialogs/ModUpdateDialog.h | 4 +- 10 files changed, 58 insertions(+), 54 deletions(-) diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index 0835e3b1..49326c83 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -47,6 +47,7 @@ class Mod : public Resource Q_OBJECT public: using Ptr = shared_qobject_ptr; + using WeakPtr = QPointer; Mod() = default; Mod(const QFileInfo &file); diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index c316e710..8bdab16e 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -226,9 +226,9 @@ bool ModFolderModel::stopWatching() return ResourceFolderModel::stopWatching({ m_dir.absolutePath(), indexDir().absolutePath() }); } -auto ModFolderModel::selectedMods(QModelIndexList& indexes) -> QList +auto ModFolderModel::selectedMods(QModelIndexList& indexes) -> QList { - QList selected_resources; + QList selected_resources; for (auto i : indexes) { if(i.column() != 0) continue; @@ -238,12 +238,13 @@ auto ModFolderModel::selectedMods(QModelIndexList& indexes) -> QList return selected_resources; } -auto ModFolderModel::allMods() -> QList +auto ModFolderModel::allMods() -> QList { - QList mods; + QList mods; - for (auto res : m_resources) + for (auto& res : m_resources) { mods.append(static_cast(res.get())); + } return mods; } diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index b1f30710..7fe830c2 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -101,8 +101,8 @@ public: QDir indexDir() { return { QString("%1/.index").arg(dir().absolutePath()) }; } - auto selectedMods(QModelIndexList& indexes) -> QList; - auto allMods() -> QList; + auto selectedMods(QModelIndexList& indexes) -> QList; + auto allMods() -> QList; RESOURCE_HELPERS(Mod) diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index bec7490f..e76bc49e 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -3,6 +3,7 @@ #include #include #include +#include #include "QObjectPtr.h" @@ -31,6 +32,7 @@ class Resource : public QObject { Q_DISABLE_COPY(Resource) public: using Ptr = shared_qobject_ptr; + using WeakPtr = QPointer; Resource(QObject* parent = nullptr); Resource(QFileInfo file_info); diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 982915e2..c27a5e2d 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -135,7 +135,7 @@ bool ResourceFolderModel::installResource(QString original_path) bool ResourceFolderModel::uninstallResource(QString file_name) { - for (auto resource : m_resources) { + for (auto& resource : m_resources) { if (resource->fileinfo().fileName() == file_name) return resource->destroy(); } @@ -155,7 +155,7 @@ bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes) continue; } - auto resource = m_resources.at(i.row()); + auto& resource = m_resources.at(i.row()); resource->destroy(); } return true; @@ -183,7 +183,7 @@ bool ResourceFolderModel::update() return true; } -void ResourceFolderModel::resolveResource(Resource::Ptr res) +void ResourceFolderModel::resolveResource(Resource::WeakPtr res) { if (!res->shouldResolve()) { return; diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index 986d9885..59d2388a 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -15,10 +15,10 @@ class QSortFilterProxyModel; /** A basic model for external resources. * - * To implement one such model, you need to implement, at the very minimum: - * - columnCount: The number of columns in your model. - * - data: How the model data is displayed and accessed. - * - headerData: Display properties of the header. + * This model manages a list of resources. As such, external users of such resources do not own them, + * and the resource's lifetime is contingent on the model's lifetime. + * + * TODO: Make the resources unique pointers accessible through weak pointers. */ class ResourceFolderModel : public QAbstractListModel { Q_OBJECT @@ -62,7 +62,7 @@ class ResourceFolderModel : public QAbstractListModel { virtual bool update(); /** Creates a new parse task, if needed, for 'res' and start it.*/ - virtual void resolveResource(Resource::Ptr res); + virtual void resolveResource(Resource::WeakPtr res); [[nodiscard]] size_t size() const { return m_resources.size(); }; [[nodiscard]] bool empty() const { return size() == 0; } @@ -151,7 +151,7 @@ class ResourceFolderModel : public QAbstractListModel { * * This usually calls static_cast on the specific Task type returned by createUpdateTask, * so care must be taken in such cases. - * TODO: Figure out a way to express this relationship better without templated classes (Q_OBJECT macro dissalows that). + * TODO: Figure out a way to express this relationship better without templated classes (Q_OBJECT macro disallows that). */ virtual void onUpdateSucceeded(); virtual void onUpdateFailed() {} @@ -189,33 +189,34 @@ class ResourceFolderModel : public QAbstractListModel { }; /* A macro to define useful functions to handle Resource* -> T* more easily on derived classes */ -#define RESOURCE_HELPERS(T) \ - [[nodiscard]] T* operator[](size_t index) \ - { \ - return static_cast(m_resources[index].get()); \ - } \ - [[nodiscard]] T* at(size_t index) \ - { \ - return static_cast(m_resources[index].get()); \ - } \ - [[nodiscard]] const T* at(size_t index) const \ - { \ - return static_cast(m_resources.at(index).get()); \ - } \ - [[nodiscard]] T* first() \ - { \ - return static_cast(m_resources.first().get()); \ - } \ - [[nodiscard]] T* last() \ - { \ - return static_cast(m_resources.last().get()); \ - } \ - [[nodiscard]] T* find(QString id) \ - { \ - auto iter = std::find_if(m_resources.begin(), m_resources.end(), [&](Resource::Ptr r) { return r->internal_id() == id; }); \ - if (iter == m_resources.end()) \ - return nullptr; \ - return static_cast((*iter).get()); \ +#define RESOURCE_HELPERS(T) \ + [[nodiscard]] T* operator[](size_t index) \ + { \ + return static_cast(m_resources[index].get()); \ + } \ + [[nodiscard]] T* at(size_t index) \ + { \ + return static_cast(m_resources[index].get()); \ + } \ + [[nodiscard]] const T* at(size_t index) const \ + { \ + return static_cast(m_resources.at(index).get()); \ + } \ + [[nodiscard]] T* first() \ + { \ + return static_cast(m_resources.first().get()); \ + } \ + [[nodiscard]] T* last() \ + { \ + return static_cast(m_resources.last().get()); \ + } \ + [[nodiscard]] T* find(QString id) \ + { \ + auto iter = std::find_if(m_resources.constBegin(), m_resources.constEnd(), \ + [&](Resource::Ptr const& r) { return r->internal_id() == id; }); \ + if (iter == m_resources.constEnd()) \ + return nullptr; \ + return static_cast((*iter).get()); \ } /* Template definition to avoid some code duplication */ @@ -231,7 +232,7 @@ void ResourceFolderModel::applyUpdates(QSet& current_set, QSet auto row = m_resources_index[kept]; auto new_resource = new_resources[kept]; - auto current_resource = m_resources[row]; + auto const& current_resource = m_resources[row]; if (new_resource->dateTimeChanged() == current_resource->dateTimeChanged()) { // no significant change, ignore... @@ -301,7 +302,7 @@ void ResourceFolderModel::applyUpdates(QSet& current_set, QSet { m_resources_index.clear(); int idx = 0; - for (auto mod : m_resources) { + for (auto const& mod : m_resources) { m_resources_index[mod->internal_id()] = idx; idx++; } diff --git a/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h b/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h index 0fd5c292..2944c747 100644 --- a/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h +++ b/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h @@ -17,7 +17,7 @@ class BasicFolderLoadTask : public Task Q_OBJECT public: struct Result { - QMap resources; + QMap resources; }; using ResultPtr = std::shared_ptr; diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp index e8180c11..44cb1d5f 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp @@ -52,7 +52,7 @@ void ModFolderLoadTask::executeTask() // Read JAR files that don't have metadata m_mods_dir.refresh(); for (auto entry : m_mods_dir.entryInfoList()) { - Mod::Ptr mod(new Mod(entry)); + Mod* mod(new Mod(entry)); if (mod->enabled()) { if (m_result->mods.contains(mod->internal_id())) { diff --git a/launcher/ui/dialogs/ModUpdateDialog.cpp b/launcher/ui/dialogs/ModUpdateDialog.cpp index d73c8ebb..a91f6e5c 100644 --- a/launcher/ui/dialogs/ModUpdateDialog.cpp +++ b/launcher/ui/dialogs/ModUpdateDialog.cpp @@ -36,7 +36,7 @@ static ModAPI::ModLoaderTypes mcLoaders(BaseInstance* inst) ModUpdateDialog::ModUpdateDialog(QWidget* parent, BaseInstance* instance, const std::shared_ptr mods, - QList& search_for) + QList& search_for) : ReviewMessageBox(parent, tr("Confirm mods to update"), "") , m_parent(parent) , m_mod_model(mods) @@ -226,9 +226,8 @@ auto ModUpdateDialog::ensureMetadata() -> bool }; for (auto candidate : m_candidates) { - auto* candidate_ptr = candidate.get(); if (candidate->status() != ModStatus::NoMetadata) { - onMetadataEnsured(candidate_ptr); + onMetadataEnsured(candidate); continue; } @@ -236,7 +235,7 @@ auto ModUpdateDialog::ensureMetadata() -> bool continue; if (confirm_rest) { - addToTmp(candidate_ptr, provider_rest); + addToTmp(candidate, provider_rest); should_try_others.insert(candidate->internal_id(), try_others_rest); continue; } @@ -261,7 +260,7 @@ auto ModUpdateDialog::ensureMetadata() -> bool should_try_others.insert(candidate->internal_id(), response.try_others); if (confirmed) - addToTmp(candidate_ptr, response.chosen); + addToTmp(candidate, response.chosen); } if (!modrinth_tmp.empty()) { diff --git a/launcher/ui/dialogs/ModUpdateDialog.h b/launcher/ui/dialogs/ModUpdateDialog.h index 76aaab36..bd486f0d 100644 --- a/launcher/ui/dialogs/ModUpdateDialog.h +++ b/launcher/ui/dialogs/ModUpdateDialog.h @@ -19,7 +19,7 @@ class ModUpdateDialog final : public ReviewMessageBox { explicit ModUpdateDialog(QWidget* parent, BaseInstance* instance, const std::shared_ptr mod_model, - QList& search_for); + QList& search_for); void checkCandidates(); @@ -46,7 +46,7 @@ class ModUpdateDialog final : public ReviewMessageBox { const std::shared_ptr m_mod_model; - QList& m_candidates; + QList& m_candidates; QList m_modrinth_to_update; QList m_flame_to_update; From 0c9d03f5dffc37f3eda500fd520907a142ac061f Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 7 Aug 2022 08:34:26 -0300 Subject: [PATCH 124/273] fix(tests): add timeout on ModFolderModel's tests If the update never ends, the signal is not emitted and we become stuck in the event loop forever. So a very lenient timer is added to prevent that. Signed-off-by: flow --- .../minecraft/mod/ModFolderModel_test.cpp | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/launcher/minecraft/mod/ModFolderModel_test.cpp b/launcher/minecraft/mod/ModFolderModel_test.cpp index b4d37ce5..1b50ebd6 100644 --- a/launcher/minecraft/mod/ModFolderModel_test.cpp +++ b/launcher/minecraft/mod/ModFolderModel_test.cpp @@ -35,6 +35,7 @@ #include #include +#include #include "FileSystem.h" #include "minecraft/mod/ModFolderModel.h" @@ -65,11 +66,25 @@ slots: { QString folder = source; QTemporaryDir tempDir; + QEventLoop loop; + ModFolderModel m(tempDir.path(), true); + connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit); + + QTimer expire_timer; + expire_timer.callOnTimeout(&loop, &QEventLoop::quit); + expire_timer.setSingleShot(true); + expire_timer.start(4000); + m.installMod(folder); + loop.exec(); + + QVERIFY2(expire_timer.isActive(), "Timer has expired. The update never finished."); + expire_timer.stop(); + verify(tempDir.path()); } @@ -79,9 +94,21 @@ slots: QTemporaryDir tempDir; QEventLoop loop; ModFolderModel m(tempDir.path(), true); + connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit); + + QTimer expire_timer; + expire_timer.callOnTimeout(&loop, &QEventLoop::quit); + expire_timer.setSingleShot(true); + expire_timer.start(4000); + m.installMod(folder); + loop.exec(); + + QVERIFY2(expire_timer.isActive(), "Timer has expired. The update never finished."); + expire_timer.stop(); + verify(tempDir.path()); } } From e7cf9932a9695417d40d895ac6174186f074f053 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 12 Aug 2022 17:06:20 -0300 Subject: [PATCH 125/273] refactor: simplify Mod structure No need to keep track of pointers left and right. A single one already gives enough headaches! Signed-off-by: flow --- launcher/CMakeLists.txt | 4 +- launcher/minecraft/mod/Mod.cpp | 50 +++----- launcher/minecraft/mod/Mod.h | 14 +-- launcher/minecraft/mod/ModDetails.h | 33 +++-- launcher/minecraft/mod/ModFolderModel.cpp | 2 +- .../minecraft/mod/tasks/LocalModParseTask.cpp | 114 +++++++++--------- .../minecraft/mod/tasks/LocalModParseTask.h | 2 +- .../minecraft/mod/tasks/ModFolderLoadTask.cpp | 4 +- .../minecraft/mod/tasks/ModFolderLoadTask.h | 2 +- 9 files changed, 107 insertions(+), 118 deletions(-) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index badd0eaa..4f5fa2fc 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -381,8 +381,8 @@ ecm_add_test(minecraft/Library_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VER # FIXME: shares data with FileSystem test # TODO: needs testdata -ecm_add_test(minecraft/mod/ModFolderModel_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test - TEST_NAME ModFolderModel) +ecm_add_test(minecraft/mod/ResourceFolderModel_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test + TEST_NAME ResourceFolderModel) ecm_add_test(minecraft/ParseUtils_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME ParseUtils) diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index ed91d999..5e186471 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -44,13 +44,7 @@ #include "MetadataHandler.h" #include "Version.h" -namespace { - -ModDetails invalidDetails; - -} - -Mod::Mod(const QFileInfo& file) : Resource(file) +Mod::Mod(const QFileInfo& file) : Resource(file), m_local_details() { m_enabled = (file.suffix() != "disabled"); } @@ -59,7 +53,7 @@ Mod::Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata) : Mod(mods_dir.absoluteFilePath(metadata.filename)) { m_name = metadata.name; - m_temp_metadata = std::make_shared(std::move(metadata)); + m_local_details.metadata = std::make_shared(std::move(metadata)); } auto Mod::enable(bool value) -> bool @@ -95,22 +89,14 @@ auto Mod::enable(bool value) -> bool void Mod::setStatus(ModStatus status) { - if (m_localDetails) { - m_localDetails->status = status; - } else { - m_temp_status = status; - } + m_local_details.status = status; } -void Mod::setMetadata(const Metadata::ModStruct& metadata) +void Mod::setMetadata(std::shared_ptr&& metadata) { if (status() == ModStatus::NoMetadata) setStatus(ModStatus::Installed); - if (m_localDetails) { - m_localDetails->metadata = std::make_shared(std::move(metadata)); - } else { - m_temp_metadata = std::make_shared(std::move(metadata)); - } + m_local_details.metadata = metadata; } std::pair Mod::compare(const Resource& other, SortType type) const @@ -176,7 +162,7 @@ auto Mod::destroy(QDir& index_dir, bool preserve_metadata) -> bool auto Mod::details() const -> const ModDetails& { - return m_localDetails ? *m_localDetails : invalidDetails; + return m_local_details; } auto Mod::name() const -> QString @@ -213,35 +199,29 @@ auto Mod::authors() const -> QStringList auto Mod::status() const -> ModStatus { - if (!m_localDetails) - return m_temp_status; return details().status; } auto Mod::metadata() -> std::shared_ptr { - if (m_localDetails) - return m_localDetails->metadata; - return m_temp_metadata; + return m_local_details.metadata; } auto Mod::metadata() const -> const std::shared_ptr { - if (m_localDetails) - return m_localDetails->metadata; - return m_temp_metadata; + return m_local_details.metadata; } -void Mod::finishResolvingWithDetails(std::shared_ptr details) +void Mod::finishResolvingWithDetails(ModDetails&& details) { m_is_resolving = false; m_is_resolved = true; - m_localDetails = details; - setStatus(m_temp_status); + std::shared_ptr metadata = details.metadata; + if (details.status == ModStatus::Unknown) + details.status = m_local_details.status; - if (m_localDetails && m_temp_metadata && m_temp_metadata->isValid()) { - setMetadata(*m_temp_metadata); - m_temp_metadata.reset(); - } + m_local_details = std::move(details); + if (metadata) + setMetadata(std::move(metadata)); } diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index 49326c83..b9b57058 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -68,7 +68,8 @@ public: auto metadata() const -> const std::shared_ptr; void setStatus(ModStatus status); - void setMetadata(const Metadata::ModStruct& metadata); + void setMetadata(std::shared_ptr&& metadata); + void setMetadata(const Metadata::ModStruct& metadata) { setMetadata(std::make_shared(metadata)); } auto enable(bool value) -> bool; @@ -78,17 +79,10 @@ public: // Delete all the files of this mod auto destroy(QDir& index_dir, bool preserve_metadata = false) -> bool; - void finishResolvingWithDetails(std::shared_ptr details); + void finishResolvingWithDetails(ModDetails&& details); protected: - /* If the mod has metadata, this will be filled in the constructor, and passed to - * the ModDetails when calling finishResolvingWithDetails */ - std::shared_ptr m_temp_metadata; - - /* Set the mod status while it doesn't have local details just yet */ - ModStatus m_temp_status = ModStatus::NoMetadata; - - std::shared_ptr m_localDetails; + ModDetails m_local_details; bool m_enabled = true; }; diff --git a/launcher/minecraft/mod/ModDetails.h b/launcher/minecraft/mod/ModDetails.h index 3e0a7ab0..118b156f 100644 --- a/launcher/minecraft/mod/ModDetails.h +++ b/launcher/minecraft/mod/ModDetails.h @@ -46,34 +46,49 @@ enum class ModStatus { Installed, // Both JAR and Metadata are present NotInstalled, // Only the Metadata is present NoMetadata, // Only the JAR is present + Unknown, // Default status }; struct ModDetails { /* Mod ID as defined in the ModLoader-specific metadata */ - QString mod_id; + QString mod_id = {}; /* Human-readable name */ - QString name; + QString name = {}; /* Human-readable mod version */ - QString version; + QString version = {}; /* Human-readable minecraft version */ - QString mcversion; + QString mcversion = {}; /* URL for mod's home page */ - QString homeurl; + QString homeurl = {}; /* Human-readable description */ - QString description; + QString description = {}; /* List of the author's names */ - QStringList authors; + QStringList authors = {}; /* Installation status of the mod */ - ModStatus status; + ModStatus status = ModStatus::Unknown; /* Metadata information, if any */ - std::shared_ptr metadata; + std::shared_ptr metadata = nullptr; + + ModDetails() = default; + + /** Metadata should be handled manually to properly set the mod status. */ + ModDetails(ModDetails& other) + : mod_id(other.mod_id) + , name(other.name) + , version(other.version) + , mcversion(other.mcversion) + , homeurl(other.homeurl) + , description(other.description) + , authors(other.authors) + , status(other.status) + {} }; diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 8bdab16e..9e07dc89 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -296,7 +296,7 @@ void ModFolderModel::onParseSucceeded(int ticket, QString mod_id) auto result = cast_task->result(); if (result && resource) - resource->finishResolvingWithDetails(result->details); + resource->finishResolvingWithDetails(std::move(result->details)); emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1)); diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp index fe3716ce..8a0273c9 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp @@ -20,22 +20,22 @@ namespace { // OLD format: // https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc -std::shared_ptr ReadMCModInfo(QByteArray contents) +ModDetails ReadMCModInfo(QByteArray contents) { - auto getInfoFromArray = [&](QJsonArray arr)->std::shared_ptr + auto getInfoFromArray = [&](QJsonArray arr) -> ModDetails { if (!arr.at(0).isObject()) { - return nullptr; + return {}; } - std::shared_ptr details = std::make_shared(); + ModDetails details; auto firstObj = arr.at(0).toObject(); - details->mod_id = firstObj.value("modid").toString(); + details.mod_id = firstObj.value("modid").toString(); auto name = firstObj.value("name").toString(); // NOTE: ignore stupid example mods copies where the author didn't even bother to change the name if(name != "Example Mod") { - details->name = name; + details.name = name; } - details->version = firstObj.value("version").toString(); + details.version = firstObj.value("version").toString(); auto homeurl = firstObj.value("url").toString().trimmed(); if(!homeurl.isEmpty()) { @@ -45,8 +45,8 @@ std::shared_ptr ReadMCModInfo(QByteArray contents) homeurl.prepend("http://"); } } - details->homeurl = homeurl; - details->description = firstObj.value("description").toString(); + details.homeurl = homeurl; + details.description = firstObj.value("description").toString(); QJsonArray authors = firstObj.value("authorList").toArray(); if (authors.size() == 0) { // FIXME: what is the format of this? is there any? @@ -55,7 +55,7 @@ std::shared_ptr ReadMCModInfo(QByteArray contents) for (auto author: authors) { - details->authors.append(author.toString()); + details.authors.append(author.toString()); } return details; }; @@ -83,7 +83,7 @@ std::shared_ptr ReadMCModInfo(QByteArray contents) { qCritical() << "BAD stuff happened to mod json:"; qCritical() << contents; - return nullptr; + return {}; } auto arrVal = jsonDoc.object().value("modlist"); if(arrVal.isUndefined()) { @@ -94,13 +94,13 @@ std::shared_ptr ReadMCModInfo(QByteArray contents) return getInfoFromArray(arrVal.toArray()); } } - return nullptr; + return {}; } // https://github.com/MinecraftForge/Documentation/blob/5ab4ba6cf9abc0ac4c0abd96ad187461aefd72af/docs/gettingstarted/structuring.md -std::shared_ptr ReadMCModTOML(QByteArray contents) +ModDetails ReadMCModTOML(QByteArray contents) { - std::shared_ptr details = std::make_shared(); + ModDetails details; char errbuf[200]; // top-level table @@ -108,7 +108,7 @@ std::shared_ptr ReadMCModTOML(QByteArray contents) if(!tomlData) { - return nullptr; + return {}; } // array defined by [[mods]] @@ -116,7 +116,7 @@ std::shared_ptr ReadMCModTOML(QByteArray contents) if(!tomlModsArr) { qWarning() << "Corrupted mods.toml? Couldn't find [[mods]] array!"; - return nullptr; + return {}; } // we only really care about the first element, since multiple mods in one file is not supported by us at the moment @@ -124,33 +124,33 @@ std::shared_ptr ReadMCModTOML(QByteArray contents) if(!tomlModsTable0) { qWarning() << "Corrupted mods.toml? [[mods]] didn't have an element at index 0!"; - return nullptr; + return {}; } // mandatory properties - always in [[mods]] toml_datum_t modIdDatum = toml_string_in(tomlModsTable0, "modId"); if(modIdDatum.ok) { - details->mod_id = modIdDatum.u.s; + details.mod_id = modIdDatum.u.s; // library says this is required for strings free(modIdDatum.u.s); } toml_datum_t versionDatum = toml_string_in(tomlModsTable0, "version"); if(versionDatum.ok) { - details->version = versionDatum.u.s; + details.version = versionDatum.u.s; free(versionDatum.u.s); } toml_datum_t displayNameDatum = toml_string_in(tomlModsTable0, "displayName"); if(displayNameDatum.ok) { - details->name = displayNameDatum.u.s; + details.name = displayNameDatum.u.s; free(displayNameDatum.u.s); } toml_datum_t descriptionDatum = toml_string_in(tomlModsTable0, "description"); if(descriptionDatum.ok) { - details->description = descriptionDatum.u.s; + details.description = descriptionDatum.u.s; free(descriptionDatum.u.s); } @@ -173,7 +173,7 @@ std::shared_ptr ReadMCModTOML(QByteArray contents) } if(!authors.isEmpty()) { - details->authors.append(authors); + details.authors.append(authors); } toml_datum_t homeurlDatum = toml_string_in(tomlData, "displayURL"); @@ -200,7 +200,7 @@ std::shared_ptr ReadMCModTOML(QByteArray contents) homeurl.prepend("http://"); } } - details->homeurl = homeurl; + details.homeurl = homeurl; // this seems to be recursive, so it should free everything toml_free(tomlData); @@ -209,20 +209,20 @@ std::shared_ptr ReadMCModTOML(QByteArray contents) } // https://fabricmc.net/wiki/documentation:fabric_mod_json -std::shared_ptr ReadFabricModInfo(QByteArray contents) +ModDetails ReadFabricModInfo(QByteArray contents) { QJsonParseError jsonError; QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError); auto object = jsonDoc.object(); auto schemaVersion = object.contains("schemaVersion") ? object.value("schemaVersion").toInt(0) : 0; - std::shared_ptr details = std::make_shared(); + ModDetails details; - details->mod_id = object.value("id").toString(); - details->version = object.value("version").toString(); + details.mod_id = object.value("id").toString(); + details.version = object.value("version").toString(); - details->name = object.contains("name") ? object.value("name").toString() : details->mod_id; - details->description = object.value("description").toString(); + details.name = object.contains("name") ? object.value("name").toString() : details.mod_id; + details.description = object.value("description").toString(); if (schemaVersion >= 1) { @@ -230,10 +230,10 @@ std::shared_ptr ReadFabricModInfo(QByteArray contents) for (auto author: authors) { if(author.isObject()) { - details->authors.append(author.toObject().value("name").toString()); + details.authors.append(author.toObject().value("name").toString()); } else { - details->authors.append(author.toString()); + details.authors.append(author.toString()); } } @@ -243,7 +243,7 @@ std::shared_ptr ReadFabricModInfo(QByteArray contents) if (contact.contains("homepage")) { - details->homeurl = contact.value("homepage").toString(); + details.homeurl = contact.value("homepage").toString(); } } } @@ -251,50 +251,50 @@ std::shared_ptr ReadFabricModInfo(QByteArray contents) } // https://github.com/QuiltMC/rfcs/blob/master/specification/0002-quilt.mod.json.md -std::shared_ptr ReadQuiltModInfo(QByteArray contents) +ModDetails ReadQuiltModInfo(QByteArray contents) { QJsonParseError jsonError; QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError); auto object = Json::requireObject(jsonDoc, "quilt.mod.json"); auto schemaVersion = Json::ensureInteger(object.value("schema_version"), 0, "Quilt schema_version"); - std::shared_ptr details = std::make_shared(); + ModDetails details; // https://github.com/QuiltMC/rfcs/blob/be6ba280d785395fefa90a43db48e5bfc1d15eb4/specification/0002-quilt.mod.json.md if (schemaVersion == 1) { auto modInfo = Json::requireObject(object.value("quilt_loader"), "Quilt mod info"); - details->mod_id = Json::requireString(modInfo.value("id"), "Mod ID"); - details->version = Json::requireString(modInfo.value("version"), "Mod version"); + details.mod_id = Json::requireString(modInfo.value("id"), "Mod ID"); + details.version = Json::requireString(modInfo.value("version"), "Mod version"); auto modMetadata = Json::ensureObject(modInfo.value("metadata")); - details->name = Json::ensureString(modMetadata.value("name"), details->mod_id); - details->description = Json::ensureString(modMetadata.value("description")); + details.name = Json::ensureString(modMetadata.value("name"), details.mod_id); + details.description = Json::ensureString(modMetadata.value("description")); auto modContributors = Json::ensureObject(modMetadata.value("contributors")); // We don't really care about the role of a contributor here - details->authors += modContributors.keys(); + details.authors += modContributors.keys(); auto modContact = Json::ensureObject(modMetadata.value("contact")); if (modContact.contains("homepage")) { - details->homeurl = Json::requireString(modContact.value("homepage")); + details.homeurl = Json::requireString(modContact.value("homepage")); } } return details; } -std::shared_ptr ReadForgeInfo(QByteArray contents) +ModDetails ReadForgeInfo(QByteArray contents) { - std::shared_ptr details = std::make_shared(); + ModDetails details; // Read the data - details->name = "Minecraft Forge"; - details->mod_id = "Forge"; - details->homeurl = "http://www.minecraftforge.net/forum/"; + details.name = "Minecraft Forge"; + details.mod_id = "Forge"; + details.homeurl = "http://www.minecraftforge.net/forum/"; INIFile ini; if (!ini.loadFile(contents)) return details; @@ -304,35 +304,35 @@ std::shared_ptr ReadForgeInfo(QByteArray contents) QString revision = ini.get("forge.revision.number", "0").toString(); QString build = ini.get("forge.build.number", "0").toString(); - details->version = major + "." + minor + "." + revision + "." + build; + details.version = major + "." + minor + "." + revision + "." + build; return details; } -std::shared_ptr ReadLiteModInfo(QByteArray contents) +ModDetails ReadLiteModInfo(QByteArray contents) { - std::shared_ptr details = std::make_shared(); + ModDetails details; QJsonParseError jsonError; QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError); auto object = jsonDoc.object(); if (object.contains("name")) { - details->mod_id = details->name = object.value("name").toString(); + details.mod_id = details.name = object.value("name").toString(); } if (object.contains("version")) { - details->version = object.value("version").toString(""); + details.version = object.value("version").toString(""); } else { - details->version = object.value("revision").toString(""); + details.version = object.value("revision").toString(""); } - details->mcversion = object.value("mcversion").toString(); + details.mcversion = object.value("mcversion").toString(); auto author = object.value("author").toString(); if(!author.isEmpty()) { - details->authors.append(author); + details.authors.append(author); } - details->description = object.value("description").toString(); - details->homeurl = object.value("url").toString(); + details.description = object.value("description").toString(); + details.homeurl = object.value("url").toString(); return details; } @@ -366,7 +366,7 @@ void LocalModParseTask::processAsZip() file.close(); // to replace ${file.jarVersion} with the actual version, as needed - if (m_result->details && m_result->details->version == "${file.jarVersion}") + if (m_result->details.version == "${file.jarVersion}") { if (zip.setCurrentFile("META-INF/MANIFEST.MF")) { @@ -395,7 +395,7 @@ void LocalModParseTask::processAsZip() manifestVersion = "NONE"; } - m_result->details->version = manifestVersion; + m_result->details.version = manifestVersion; file.close(); } diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.h b/launcher/minecraft/mod/tasks/LocalModParseTask.h index dbecb449..e0a10218 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.h @@ -13,7 +13,7 @@ class LocalModParseTask : public Task Q_OBJECT public: struct Result { - std::shared_ptr details; + ModDetails details; }; using ResultPtr = std::shared_ptr; ResultPtr result() const { diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp index 44cb1d5f..a56ba8ab 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp @@ -38,8 +38,8 @@ #include "minecraft/mod/MetadataHandler.h" -ModFolderLoadTask::ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan) - : Task(nullptr, false), m_mods_dir(mods_dir), m_index_dir(index_dir), m_is_indexed(is_indexed), m_clean_orphan(clean_orphan), m_result(new Result()) +ModFolderLoadTask::ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan, QObject* parent) + : Task(parent, false), m_mods_dir(mods_dir), m_index_dir(index_dir), m_is_indexed(is_indexed), m_clean_orphan(clean_orphan), m_result(new Result()) {} void ModFolderLoadTask::executeTask() diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h index 86f3f67f..840e95e1 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h @@ -57,7 +57,7 @@ public: } public: - ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan = false); + ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan = false, QObject* parent = nullptr); void executeTask() override; From c3ceefbafbbeb3d31630ef329405ebaacdf9fce5 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 12 Aug 2022 17:09:56 -0300 Subject: [PATCH 126/273] refactor+fix: add new tests for Resource models and fix issues Signed-off-by: flow --- launcher/minecraft/mod/ModFolderModel.cpp | 18 +- .../minecraft/mod/ModFolderModel_test.cpp | 119 ---------- .../minecraft/mod/ResourceFolderModel.cpp | 46 ++-- launcher/minecraft/mod/ResourceFolderModel.h | 40 ++-- .../mod/ResourceFolderModel_test.cpp | 219 ++++++++++++++++++ .../minecraft/mod/tasks/BasicFolderLoadTask.h | 13 +- .../minecraft/mod/tasks/LocalModParseTask.cpp | 12 +- .../minecraft/mod/tasks/LocalModParseTask.h | 5 + .../minecraft/mod/testdata/supercoolmod.jar | Bin 0 -> 14 bytes 9 files changed, 313 insertions(+), 159 deletions(-) delete mode 100644 launcher/minecraft/mod/ModFolderModel_test.cpp create mode 100644 launcher/minecraft/mod/ResourceFolderModel_test.cpp create mode 100644 launcher/minecraft/mod/testdata/supercoolmod.jar diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 9e07dc89..15c713b8 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -165,7 +165,7 @@ int ModFolderModel::columnCount(const QModelIndex &parent) const Task* ModFolderModel::createUpdateTask() { auto index_dir = indexDir(); - auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load); + auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load, this); m_first_folder_load = false; return task; } @@ -181,6 +181,9 @@ bool ModFolderModel::uninstallMod(const QString& filename, bool preserve_metadat if(mod->fileinfo().fileName() == filename){ auto index_dir = indexDir(); mod->destroy(index_dir, preserve_metadata); + + update(); + return true; } } @@ -206,6 +209,9 @@ bool ModFolderModel::deleteMods(const QModelIndexList& indexes) auto index_dir = indexDir(); m->destroy(index_dir); } + + update(); + return true; } @@ -268,14 +274,13 @@ void ModFolderModel::onUpdateSucceeded() applyUpdates(current_set, new_set, new_mods); - update_results.reset(); m_current_update_task.reset(); - emit updateFinished(); - - if(m_scheduled_update) { + if (m_scheduled_update) { m_scheduled_update = false; update(); + } else { + emit updateFinished(); } } @@ -299,9 +304,6 @@ void ModFolderModel::onParseSucceeded(int ticket, QString mod_id) resource->finishResolvingWithDetails(std::move(result->details)); emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1)); - - parse_task->deleteLater(); - m_active_parse_tasks.remove(ticket); } diff --git a/launcher/minecraft/mod/ModFolderModel_test.cpp b/launcher/minecraft/mod/ModFolderModel_test.cpp deleted file mode 100644 index 1b50ebd6..00000000 --- a/launcher/minecraft/mod/ModFolderModel_test.cpp +++ /dev/null @@ -1,119 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* -* PolyMC - Minecraft Launcher -* Copyright (C) 2022 Sefa Eyeoglu -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, version 3. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see . -* -* This file incorporates work covered by the following copyright and -* permission notice: -* -* Copyright 2013-2021 MultiMC Contributors -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -#include -#include -#include - -#include "FileSystem.h" -#include "minecraft/mod/ModFolderModel.h" - -class ModFolderModelTest : public QObject -{ - Q_OBJECT - -private -slots: - // test for GH-1178 - install a folder with files to a mod list - void test_1178() - { - // source - QString source = QFINDTESTDATA("testdata/test_folder"); - - // sanity check - QVERIFY(!source.endsWith('/')); - - auto verify = [](QString path) - { - QDir target_dir(FS::PathCombine(path, "test_folder")); - QVERIFY(target_dir.entryList().contains("pack.mcmeta")); - QVERIFY(target_dir.entryList().contains("assets")); - }; - - // 1. test with no trailing / - { - QString folder = source; - QTemporaryDir tempDir; - - QEventLoop loop; - - ModFolderModel m(tempDir.path(), true); - - connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit); - - QTimer expire_timer; - expire_timer.callOnTimeout(&loop, &QEventLoop::quit); - expire_timer.setSingleShot(true); - expire_timer.start(4000); - - m.installMod(folder); - - loop.exec(); - - QVERIFY2(expire_timer.isActive(), "Timer has expired. The update never finished."); - expire_timer.stop(); - - verify(tempDir.path()); - } - - // 2. test with trailing / - { - QString folder = source + '/'; - QTemporaryDir tempDir; - QEventLoop loop; - ModFolderModel m(tempDir.path(), true); - - connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit); - - QTimer expire_timer; - expire_timer.callOnTimeout(&loop, &QEventLoop::quit); - expire_timer.setSingleShot(true); - expire_timer.start(4000); - - m.installMod(folder); - - loop.exec(); - - QVERIFY2(expire_timer.isActive(), "Timer has expired. The update never finished."); - expire_timer.stop(); - - verify(tempDir.path()); - } - } -}; - -QTEST_GUILESS_MAIN(ModFolderModelTest) - -#include "ModFolderModel_test.moc" diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index c27a5e2d..b7213c47 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -24,8 +24,6 @@ bool ResourceFolderModel::startWatching(const QStringList paths) if (m_is_watching) return false; - update(); - auto couldnt_be_watched = m_watcher.addPaths(paths); for (auto path : paths) { if (couldnt_be_watched.contains(path)) @@ -34,6 +32,8 @@ bool ResourceFolderModel::startWatching(const QStringList paths) qDebug() << "Started watching " << path; } + update(); + m_is_watching = !m_is_watching; return m_is_watching; } @@ -105,7 +105,8 @@ bool ResourceFolderModel::installResource(QString original_path) QFileInfo new_path_file_info(new_path); resource.setFile(new_path_file_info); - update(); + if (!m_is_watching) + return update(); return true; } @@ -123,7 +124,8 @@ bool ResourceFolderModel::installResource(QString original_path) QFileInfo newpathInfo(new_path); resource.setFile(newpathInfo); - update(); + if (!m_is_watching) + return update(); return true; } @@ -136,8 +138,13 @@ bool ResourceFolderModel::installResource(QString original_path) bool ResourceFolderModel::uninstallResource(QString file_name) { for (auto& resource : m_resources) { - if (resource->fileinfo().fileName() == file_name) - return resource->destroy(); + if (resource->fileinfo().fileName() == file_name) { + auto res = resource->destroy(); + + update(); + + return res; + } } return false; } @@ -156,13 +163,21 @@ bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes) } auto& resource = m_resources.at(i.row()); + resource->destroy(); } + + update(); + return true; } +static QMutex s_update_task_mutex; bool ResourceFolderModel::update() { + // We hold a lock here to prevent race conditions on the m_current_update_task reset. + QMutexLocker lock(&s_update_task_mutex); + // Already updating, so we schedule a future update and return. if (m_current_update_task) { m_scheduled_update = true; @@ -183,7 +198,7 @@ bool ResourceFolderModel::update() return true; } -void ResourceFolderModel::resolveResource(Resource::WeakPtr res) +void ResourceFolderModel::resolveResource(Resource::Ptr res) { if (!res->shouldResolve()) { return; @@ -205,6 +220,8 @@ void ResourceFolderModel::resolveResource(Resource::WeakPtr res) task, &Task::succeeded, this, [=] { onParseSucceeded(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection); connect( task, &Task::failed, this, [=] { onParseFailed(ticket, res->internal_id()); }, Qt::ConnectionType::QueuedConnection); + connect( + task, &Task::finished, this, [=] { m_active_parse_tasks.remove(ticket); }, Qt::ConnectionType::QueuedConnection); auto* thread_pool = QThreadPool::globalInstance(); thread_pool->start(task); @@ -229,15 +246,13 @@ void ResourceFolderModel::onUpdateSucceeded() applyUpdates(current_set, new_set, new_resources); - update_results.reset(); - m_current_update_task->deleteLater(); m_current_update_task.reset(); - emit updateFinished(); - if (m_scheduled_update) { m_scheduled_update = false; update(); + } else { + emit updateFinished(); } } @@ -247,9 +262,6 @@ void ResourceFolderModel::onParseSucceeded(int ticket, QString resource_id) if (iter == m_active_parse_tasks.constEnd()) return; - (*iter)->deleteLater(); - m_active_parse_tasks.remove(ticket); - int row = m_resources_index[resource_id]; emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1)); } @@ -259,6 +271,12 @@ Task* ResourceFolderModel::createUpdateTask() return new BasicFolderLoadTask(m_dir); } + +bool ResourceFolderModel::hasPendingParseTasks() const +{ + return !m_active_parse_tasks.isEmpty(); +} + void ResourceFolderModel::directoryChanged(QString path) { update(); diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index 59d2388a..b3a474ba 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -62,7 +62,7 @@ class ResourceFolderModel : public QAbstractListModel { virtual bool update(); /** Creates a new parse task, if needed, for 'res' and start it.*/ - virtual void resolveResource(Resource::WeakPtr res); + virtual void resolveResource(Resource::Ptr res); [[nodiscard]] size_t size() const { return m_resources.size(); }; [[nodiscard]] bool empty() const { return size() == 0; } @@ -71,6 +71,13 @@ class ResourceFolderModel : public QAbstractListModel { [[nodiscard]] QDir const& dir() const { return m_dir; } + /** Checks whether there's any parse tasks being done. + * + * Since they can be quite expensive, and are usually done in a separate thread, if we were to destroy the model while having + * such tasks would introduce an undefined behavior, most likely resulting in a crash. + */ + [[nodiscard]] bool hasPendingParseTasks() const; + /* Qt behavior */ /* Basic columns */ @@ -228,10 +235,12 @@ void ResourceFolderModel::applyUpdates(QSet& current_set, QSet QSet kept_set = current_set; kept_set.intersect(new_set); - for (auto& kept : kept_set) { - auto row = m_resources_index[kept]; + for (auto const& kept : kept_set) { + auto row_it = m_resources_index.constFind(kept); + Q_ASSERT(row_it != m_resources_index.constEnd()); + auto row = row_it.value(); - auto new_resource = new_resources[kept]; + auto& new_resource = new_resources[kept]; auto const& current_resource = m_resources[row]; if (new_resource->dateTimeChanged() == current_resource->dateTimeChanged()) { @@ -242,11 +251,12 @@ void ResourceFolderModel::applyUpdates(QSet& current_set, QSet // If the resource is resolving, but something about it changed, we don't want to // continue the resolving. if (current_resource->isResolving()) { - m_active_parse_tasks.remove(current_resource->resolutionTicket()); + auto task = (*m_active_parse_tasks.find(current_resource->resolutionTicket())).get(); + task->abort(); } - m_resources[row] = new_resource; - resolveResource(new_resource); + m_resources[row].reset(new_resource); + resolveResource(m_resources.at(row)); emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1)); } } @@ -260,21 +270,21 @@ void ResourceFolderModel::applyUpdates(QSet& current_set, QSet for (auto& removed : removed_set) removed_rows.append(m_resources_index[removed]); - std::sort(removed_rows.begin(), removed_rows.end()); - - for (int i = 0; i < removed_rows.size(); i++) - removed_rows[i] -= i; + std::sort(removed_rows.begin(), removed_rows.end(), std::greater()); for (auto& removed_index : removed_rows) { - beginRemoveRows(QModelIndex(), removed_index, removed_index); - auto removed_it = m_resources.begin() + removed_index; + + Q_ASSERT(removed_it != m_resources.end()); + Q_ASSERT(removed_set.contains(removed_it->get()->internal_id())); + if ((*removed_it)->isResolving()) { - m_active_parse_tasks.remove((*removed_it)->resolutionTicket()); + auto task = (*m_active_parse_tasks.find((*removed_it)->resolutionTicket())).get(); + task->abort(); } + beginRemoveRows(QModelIndex(), removed_index, removed_index); m_resources.erase(removed_it); - endRemoveRows(); } } diff --git a/launcher/minecraft/mod/ResourceFolderModel_test.cpp b/launcher/minecraft/mod/ResourceFolderModel_test.cpp new file mode 100644 index 00000000..5e29e6aa --- /dev/null +++ b/launcher/minecraft/mod/ResourceFolderModel_test.cpp @@ -0,0 +1,219 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* +* PolyMC - Minecraft Launcher +* Copyright (C) 2022 Sefa Eyeoglu +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, version 3. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* +* This file incorporates work covered by the following copyright and +* permission notice: +* +* Copyright 2013-2021 MultiMC Contributors +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +#include +#include +#include + +#include "FileSystem.h" + +#include "minecraft/mod/ModFolderModel.h" +#include "minecraft/mod/ResourceFolderModel.h" + +#define EXEC_UPDATE_TASK(EXEC, VERIFY) \ + QEventLoop loop; \ + \ + connect(&model, &ResourceFolderModel::updateFinished, &loop, &QEventLoop::quit); \ + \ + QTimer expire_timer; \ + expire_timer.callOnTimeout(&loop, &QEventLoop::quit); \ + expire_timer.setSingleShot(true); \ + expire_timer.start(4000); \ + \ + VERIFY(EXEC); \ + loop.exec(); \ + \ + QVERIFY2(expire_timer.isActive(), "Timer has expired. The update never finished."); \ + expire_timer.stop(); \ + \ + disconnect(&model, nullptr, nullptr, nullptr); + +class ResourceFolderModelTest : public QObject +{ + Q_OBJECT + +private +slots: + // test for GH-1178 - install a folder with files to a mod list + void test_1178() + { + // source + QString source = QFINDTESTDATA("testdata/test_folder"); + + // sanity check + QVERIFY(!source.endsWith('/')); + + auto verify = [](QString path) + { + QDir target_dir(FS::PathCombine(path, "test_folder")); + QVERIFY(target_dir.entryList().contains("pack.mcmeta")); + QVERIFY(target_dir.entryList().contains("assets")); + }; + + // 1. test with no trailing / + { + QString folder = source; + QTemporaryDir tempDir; + + QEventLoop loop; + + ModFolderModel m(tempDir.path(), true); + + connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit); + + QTimer expire_timer; + expire_timer.callOnTimeout(&loop, &QEventLoop::quit); + expire_timer.setSingleShot(true); + expire_timer.start(4000); + + m.installMod(folder); + + loop.exec(); + + QVERIFY2(expire_timer.isActive(), "Timer has expired. The update never finished."); + expire_timer.stop(); + + verify(tempDir.path()); + } + + // 2. test with trailing / + { + QString folder = source + '/'; + QTemporaryDir tempDir; + QEventLoop loop; + ModFolderModel m(tempDir.path(), true); + + connect(&m, &ModFolderModel::updateFinished, &loop, &QEventLoop::quit); + + QTimer expire_timer; + expire_timer.callOnTimeout(&loop, &QEventLoop::quit); + expire_timer.setSingleShot(true); + expire_timer.start(4000); + + m.installMod(folder); + + loop.exec(); + + QVERIFY2(expire_timer.isActive(), "Timer has expired. The update never finished."); + expire_timer.stop(); + + verify(tempDir.path()); + } + } + + void test_addFromWatch() + { + QString source = QFINDTESTDATA("testdata"); + + ModFolderModel model(source); + + QCOMPARE(model.size(), 0); + + EXEC_UPDATE_TASK(model.startWatching(), ) + + for (auto mod : model.allMods()) + qDebug() << mod->name(); + + QCOMPARE(model.size(), 2); + + model.stopWatching(); + + while (model.hasPendingParseTasks()) { + QTest::qSleep(20); + QCoreApplication::processEvents(); + } + } + + void test_removeResource() + { + QString folder_resource = QFINDTESTDATA("testdata/test_folder"); + QString file_mod = QFINDTESTDATA("testdata/supercoolmod.jar"); + + QTemporaryDir tmp; + + ResourceFolderModel model(QDir(tmp.path())); + + QCOMPARE(model.size(), 0); + + { + EXEC_UPDATE_TASK(model.installResource(file_mod), QVERIFY) + } + + QCOMPARE(model.size(), 1); + qDebug() << "Added first mod."; + + { + EXEC_UPDATE_TASK(model.startWatching(), ) + } + + QCOMPARE(model.size(), 1); + qDebug() << "Started watching the temp folder."; + + { + EXEC_UPDATE_TASK(model.installResource(folder_resource), QVERIFY) + } + + QCOMPARE(model.size(), 2); + qDebug() << "Added second mod."; + + { + EXEC_UPDATE_TASK(model.uninstallResource("supercoolmod.jar"), QVERIFY); + } + + QCOMPARE(model.size(), 1); + qDebug() << "Removed first mod."; + + QString mod_file_name {model.at(0).fileinfo().fileName()}; + QVERIFY(!mod_file_name.isEmpty()); + + { + EXEC_UPDATE_TASK(model.uninstallResource(mod_file_name), QVERIFY); + } + + QCOMPARE(model.size(), 0); + qDebug() << "Removed second mod."; + + model.stopWatching(); + + while (model.hasPendingParseTasks()) { + QTest::qSleep(20); + QCoreApplication::processEvents(); + } + } +}; + +QTEST_GUILESS_MAIN(ResourceFolderModelTest) + +#include "ResourceFolderModel_test.moc" diff --git a/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h b/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h index 2944c747..cc02a9b9 100644 --- a/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h +++ b/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h @@ -17,7 +17,7 @@ class BasicFolderLoadTask : public Task Q_OBJECT public: struct Result { - QMap resources; + QMap resources; }; using ResultPtr = std::shared_ptr; @@ -27,6 +27,10 @@ public: public: BasicFolderLoadTask(QDir dir) : Task(nullptr, false), m_dir(dir), m_result(new Result) {} + + [[nodiscard]] bool canAbort() const override { return true; } + bool abort() override { m_aborted = true; return true; } + void executeTask() override { m_dir.refresh(); @@ -35,10 +39,15 @@ public: m_result->resources.insert(resource->internal_id(), resource); } - emitSucceeded(); + if (m_aborted) + emitAborted(); + else + emitSucceeded(); } private: QDir m_dir; ResultPtr m_result; + + bool m_aborted = false; }; diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp index 8a0273c9..c486bd46 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp @@ -497,6 +497,12 @@ void LocalModParseTask::processAsLitemod() zip.close(); } +bool LocalModParseTask::abort() +{ + m_aborted = true; + return true; +} + void LocalModParseTask::executeTask() { switch(m_type) @@ -513,5 +519,9 @@ void LocalModParseTask::executeTask() default: break; } - emitSucceeded(); + + if (m_aborted) + emitAborted(); + else + emitSucceeded(); } diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.h b/launcher/minecraft/mod/tasks/LocalModParseTask.h index e0a10218..4bbf3c85 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.h @@ -20,6 +20,9 @@ public: return m_result; } + [[nodiscard]] bool canAbort() const override { return true; } + bool abort() override; + LocalModParseTask(int token, ResourceType type, const QFileInfo & modFile); void executeTask() override; @@ -35,4 +38,6 @@ private: ResourceType m_type; QFileInfo m_modFile; ResultPtr m_result; + + bool m_aborted = false; }; diff --git a/launcher/minecraft/mod/testdata/supercoolmod.jar b/launcher/minecraft/mod/testdata/supercoolmod.jar new file mode 100644 index 0000000000000000000000000000000000000000..d8cf98605c15cb63a70aeb34bc121e1427cd5055 GIT binary patch literal 14 VcmXTPNL5HmEiO^W%}>$e0stl+1gHQ2 literal 0 HcmV?d00001 From e2ab2aea32b6d48ee0c9f90442ed53c47e2e7d96 Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 13 Aug 2022 11:58:39 -0300 Subject: [PATCH 127/273] change: add enable/disable to resources TIL that zip resource packs, when disabled, actually have the effect of not showing up in the game at all. Since this can be useful to someone, I moved the logic for it to the resources. Signed-off-by: flow --- launcher/minecraft/mod/Mod.cpp | 35 --------- launcher/minecraft/mod/Mod.h | 6 -- launcher/minecraft/mod/ModFolderModel.cpp | 76 ------------------- launcher/minecraft/mod/ModFolderModel.h | 7 -- launcher/minecraft/mod/Resource.cpp | 57 +++++++++++++- launcher/minecraft/mod/Resource.h | 16 ++++ .../minecraft/mod/ResourceFolderModel.cpp | 64 +++++++++++++++- launcher/minecraft/mod/ResourceFolderModel.h | 14 +++- .../mod/ResourceFolderModel_test.cpp | 56 ++++++++++++++ .../pages/instance/ExternalResourcesPage.cpp | 28 +++++++ .../ui/pages/instance/ExternalResourcesPage.h | 6 +- launcher/ui/pages/instance/ModFolderPage.cpp | 29 ------- launcher/ui/pages/instance/ModFolderPage.h | 5 -- 13 files changed, 231 insertions(+), 168 deletions(-) diff --git a/launcher/minecraft/mod/Mod.cpp b/launcher/minecraft/mod/Mod.cpp index 5e186471..39023f69 100644 --- a/launcher/minecraft/mod/Mod.cpp +++ b/launcher/minecraft/mod/Mod.cpp @@ -56,37 +56,6 @@ Mod::Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata) m_local_details.metadata = std::make_shared(std::move(metadata)); } -auto Mod::enable(bool value) -> bool -{ - if (m_type == ResourceType::UNKNOWN || m_type == ResourceType::FOLDER) - return false; - - if (m_enabled == value) - return false; - - QString path = m_file_info.absoluteFilePath(); - QFile file(path); - if (value) { - if (!path.endsWith(".disabled")) - return false; - path.chop(9); - - if (!file.rename(path)) - return false; - } else { - path += ".disabled"; - - if (!file.rename(path)) - return false; - } - - if (status() == ModStatus::NoMetadata) - setFile(QFileInfo(path)); - - m_enabled = value; - return true; -} - void Mod::setStatus(ModStatus status) { m_local_details.status = status; @@ -108,10 +77,6 @@ std::pair Mod::compare(const Resource& other, SortType type) const switch (type) { default: case SortType::ENABLED: - if (enabled() && !cast_other->enabled()) - return { 1, type == SortType::ENABLED }; - if (!enabled() && cast_other->enabled()) - return { -1, type == SortType::ENABLED }; case SortType::NAME: case SortType::DATE: { auto res = Resource::compare(other, type); diff --git a/launcher/minecraft/mod/Mod.h b/launcher/minecraft/mod/Mod.h index b9b57058..f336bec4 100644 --- a/launcher/minecraft/mod/Mod.h +++ b/launcher/minecraft/mod/Mod.h @@ -54,8 +54,6 @@ public: Mod(const QDir& mods_dir, const Metadata::ModStruct& metadata); Mod(QString file_path) : Mod(QFileInfo(file_path)) {} - auto enabled() const -> bool { return m_enabled; } - auto details() const -> const ModDetails&; auto name() const -> QString override; auto version() const -> QString; @@ -71,8 +69,6 @@ public: void setMetadata(std::shared_ptr&& metadata); void setMetadata(const Metadata::ModStruct& metadata) { setMetadata(std::make_shared(metadata)); } - auto enable(bool value) -> bool; - [[nodiscard]] auto compare(Resource const& other, SortType type) const -> std::pair override; [[nodiscard]] bool applyFilter(QRegularExpression filter) const override; @@ -83,6 +79,4 @@ public: protected: ModDetails m_local_details; - - bool m_enabled = true; }; diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 15c713b8..4e264a74 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -104,20 +104,6 @@ QVariant ModFolderModel::data(const QModelIndex &index, int role) const } } -bool ModFolderModel::setData(const QModelIndex &index, const QVariant &value, int role) -{ - if (index.row() < 0 || index.row() >= rowCount(index) || !index.isValid()) - { - return false; - } - - if (role == Qt::CheckStateRole) - { - return setModStatus(index.row(), Toggle); - } - return false; -} - QVariant ModFolderModel::headerData(int section, Qt::Orientation orientation, int role) const { switch (role) @@ -305,65 +291,3 @@ void ModFolderModel::onParseSucceeded(int ticket, QString mod_id) emit dataChanged(index(row), index(row, columnCount(QModelIndex()) - 1)); } - - -bool ModFolderModel::setModStatus(const QModelIndexList& indexes, ModStatusAction enable) -{ - if(!m_can_interact) { - return false; - } - - if(indexes.isEmpty()) - return true; - - for (auto index: indexes) - { - if(index.column() != 0) { - continue; - } - setModStatus(index.row(), enable); - } - return true; -} - -bool ModFolderModel::setModStatus(int row, ModFolderModel::ModStatusAction action) -{ - if(row < 0 || row >= m_resources.size()) { - return false; - } - - auto mod = at(row); - bool desiredStatus; - switch(action) { - case Enable: - desiredStatus = true; - break; - case Disable: - desiredStatus = false; - break; - case Toggle: - default: - desiredStatus = !mod->enabled(); - break; - } - - if(desiredStatus == mod->enabled()) { - return true; - } - - // preserve the row, but change its ID - auto oldId = mod->internal_id(); - if(!mod->enable(!mod->enabled())) { - return false; - } - auto newId = mod->internal_id(); - if(m_resources_index.contains(newId)) { - // NOTE: this could handle a corner case, where we are overwriting a file, because the same 'mod' exists both enabled and disabled - // But is it necessary? - } - m_resources_index.remove(oldId); - m_resources_index[newId] = row; - emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1)); - return true; -} - diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index 7fe830c2..c33195ed 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -77,7 +77,6 @@ public: ModFolderModel(const QString &dir, bool is_indexed = false); QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; - bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; int columnCount(const QModelIndex &parent) const override; @@ -91,9 +90,6 @@ public: /// Deletes all the selected mods bool deleteMods(const QModelIndexList &indexes); - /// Enable or disable listed mods - bool setModStatus(const QModelIndexList &indexes, ModStatusAction action); - bool isValid(); bool startWatching() override; @@ -111,9 +107,6 @@ slots: void onUpdateSucceeded() override; void onParseSucceeded(int ticket, QString resource_id) override; -private: - bool setModStatus(int index, ModStatusAction action); - protected: bool m_is_indexed; bool m_first_folder_load = true; diff --git a/launcher/minecraft/mod/Resource.cpp b/launcher/minecraft/mod/Resource.cpp index c58df3d8..0fbcfd7c 100644 --- a/launcher/minecraft/mod/Resource.cpp +++ b/launcher/minecraft/mod/Resource.cpp @@ -29,8 +29,10 @@ void Resource::parseFile() m_type = ResourceType::FOLDER; m_name = file_name; } else if (m_file_info.isFile()) { - if (file_name.endsWith(".disabled")) + if (file_name.endsWith(".disabled")) { file_name.chop(9); + m_enabled = false; + } if (file_name.endsWith(".zip") || file_name.endsWith(".jar")) { m_type = ResourceType::ZIPFILE; @@ -59,6 +61,11 @@ std::pair Resource::compare(const Resource& other, SortType type) con { switch (type) { default: + case SortType::ENABLED: + if (enabled() && !other.enabled()) + return { 1, type == SortType::ENABLED }; + if (!enabled() && other.enabled()) + return { -1, type == SortType::ENABLED }; case SortType::NAME: { QString this_name{ name() }; QString other_name{ other.name() }; @@ -85,6 +92,54 @@ bool Resource::applyFilter(QRegularExpression filter) const return filter.match(name()).hasMatch(); } +bool Resource::enable(EnableAction action) +{ + if (m_type == ResourceType::UNKNOWN || m_type == ResourceType::FOLDER) + return false; + + + QString path = m_file_info.absoluteFilePath(); + QFile file(path); + + bool enable = true; + switch (action) { + case EnableAction::ENABLE: + enable = true; + break; + case EnableAction::DISABLE: + enable = false; + break; + case EnableAction::TOGGLE: + default: + enable = !enabled(); + break; + } + + if (m_enabled == enable) + return false; + + if (enable) { + // m_enabled is false, but there's no '.disabled' suffix. + // TODO: Report error? + if (!path.endsWith(".disabled")) + return false; + path.chop(9); + + if (!file.rename(path)) + return false; + } else { + path += ".disabled"; + + if (!file.rename(path)) + return false; + } + + setFile(QFileInfo(path)); + + m_enabled = enable; + return true; +} + bool Resource::destroy() { m_type = ResourceType::UNKNOWN; diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index e76bc49e..cee1f172 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -22,6 +22,12 @@ enum class SortType { ENABLED, }; +enum class EnableAction { + ENABLE, + DISABLE, + TOGGLE +}; + /** General class for managed resources. It mirrors a file in disk, with some more info * for display and house-keeping purposes. * @@ -47,6 +53,7 @@ class Resource : public QObject { [[nodiscard]] auto dateTimeChanged() const -> QDateTime { return m_changed_date_time; } [[nodiscard]] auto internal_id() const -> QString { return m_internal_id; } [[nodiscard]] auto type() const -> ResourceType { return m_type; } + [[nodiscard]] bool enabled() const { return m_enabled; } [[nodiscard]] virtual auto name() const -> QString { return m_name; } [[nodiscard]] virtual bool valid() const { return m_type != ResourceType::UNKNOWN; } @@ -65,6 +72,12 @@ class Resource : public QObject { */ [[nodiscard]] virtual bool applyFilter(QRegularExpression filter) const; + /** Changes the enabled property, according to 'action'. + * + * Returns whether a change was applied to the Resource's properties. + */ + bool enable(EnableAction action); + [[nodiscard]] auto shouldResolve() const -> bool { return !m_is_resolving && !m_is_resolved; } [[nodiscard]] auto isResolving() const -> bool { return m_is_resolving; } [[nodiscard]] auto resolutionTicket() const -> int { return m_resolution_ticket; } @@ -92,6 +105,9 @@ class Resource : public QObject { /* The type of file we're dealing with. */ ResourceType m_type = ResourceType::UNKNOWN; + /* Whether the resource is enabled (e.g. shows up in the game) or not. */ + bool m_enabled = true; + /* Used to keep trach of pending / concluded actions on the resource. */ bool m_is_resolving = false; bool m_is_resolved = false; diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index b7213c47..31d88eb6 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -172,6 +172,44 @@ bool ResourceFolderModel::deleteResources(const QModelIndexList& indexes) return true; } +bool ResourceFolderModel::setResourceEnabled(const QModelIndexList &indexes, EnableAction action) +{ + if (!m_can_interact) + return false; + + if (indexes.isEmpty()) + return true; + + bool succeeded = true; + for (auto const& idx : indexes) { + if (!validateIndex(idx) || idx.column() != 0) + continue; + + int row = idx.row(); + + auto& resource = m_resources[row]; + + // Preserve the row, but change its ID + auto old_id = resource->internal_id(); + if (!resource->enable(action)) { + succeeded = false; + continue; + } + + auto new_id = resource->internal_id(); + if (m_resources_index.contains(new_id)) { + // FIXME: https://github.com/PolyMC/PolyMC/issues/550 + } + + m_resources_index.remove(old_id); + m_resources_index[new_id] = row; + + emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1)); + } + + return succeeded; +} + static QMutex s_update_task_mutex; bool ResourceFolderModel::update() { @@ -271,7 +309,6 @@ Task* ResourceFolderModel::createUpdateTask() return new BasicFolderLoadTask(m_dir); } - bool ResourceFolderModel::hasPendingParseTasks() const { return !m_active_parse_tasks.isEmpty(); @@ -370,11 +407,30 @@ QVariant ResourceFolderModel::data(const QModelIndex& index, int role) const } case Qt::ToolTipRole: return m_resources[row]->internal_id(); + case Qt::CheckStateRole: + switch (column) { + case ACTIVE_COLUMN: + return m_resources[row]->enabled() ? Qt::Checked : Qt::Unchecked; + default: + return {}; + } default: return {}; } } +bool ResourceFolderModel::setData(const QModelIndex& index, const QVariant& value, int role) +{ + int row = index.row(); + if (row < 0 || row >= rowCount(index) || !index.isValid()) + return false; + + if (role == Qt::CheckStateRole) + return setResourceEnabled({ index }, EnableAction::TOGGLE); + + return false; +} + QVariant ResourceFolderModel::headerData(int section, Qt::Orientation orientation, int role) const { switch (role) { @@ -389,9 +445,14 @@ QVariant ResourceFolderModel::headerData(int section, Qt::Orientation orientatio } case Qt::ToolTipRole: { switch (section) { + case ACTIVE_COLUMN: + //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc. + return tr("Is the resource enabled?"); case NAME_COLUMN: + //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc. return tr("The name of the resource."); case DATE_COLUMN: + //: Here, resource is a generic term for external resources, like Mods, Resource Packs, Shader Packs, etc. return tr("The date and time this resource was last changed (or added)."); default: return {}; @@ -459,4 +520,3 @@ void ResourceFolderModel::enableInteraction(bool enabled) return (compare_result.first < 0); return (compare_result.first > 0); } - diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index b3a474ba..e27b5db6 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -55,9 +55,14 @@ class ResourceFolderModel : public QAbstractListModel { * Returns whether the removal was successful. */ virtual bool uninstallResource(QString file_name); - virtual bool deleteResources(const QModelIndexList&); + /** Applies the given 'action' to the resources in 'indexes'. + * + * Returns whether the action was successfully applied to all resources. + */ + virtual bool setResourceEnabled(const QModelIndexList& indexes, EnableAction action); + /** Creates a new update task and start it. Returns false if no update was done, like when an update is already underway. */ virtual bool update(); @@ -66,6 +71,7 @@ class ResourceFolderModel : public QAbstractListModel { [[nodiscard]] size_t size() const { return m_resources.size(); }; [[nodiscard]] bool empty() const { return size() == 0; } + [[nodiscard]] Resource& at(int index) { return *m_resources.at(index); } [[nodiscard]] Resource const& at(int index) const { return *m_resources.at(index); } [[nodiscard]] QList const& all() const { return m_resources; } @@ -81,7 +87,7 @@ class ResourceFolderModel : public QAbstractListModel { /* Qt behavior */ /* Basic columns */ - enum Columns { NAME_COLUMN = 0, DATE_COLUMN, NUM_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; }; @@ -96,7 +102,7 @@ class ResourceFolderModel : public QAbstractListModel { [[nodiscard]] bool validateIndex(const QModelIndex& index) const; [[nodiscard]] QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override { return false; }; + bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; @@ -174,7 +180,7 @@ class ResourceFolderModel : public QAbstractListModel { protected: // Represents the relationship between a column's index (represented by the list index), and it's sorting key. // As such, the order in with they appear is very important! - QList m_column_sort_keys = { SortType::NAME, SortType::DATE }; + QList m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::DATE }; bool m_can_interact = true; diff --git a/launcher/minecraft/mod/ResourceFolderModel_test.cpp b/launcher/minecraft/mod/ResourceFolderModel_test.cpp index 5e29e6aa..fe98552e 100644 --- a/launcher/minecraft/mod/ResourceFolderModel_test.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel_test.cpp @@ -212,6 +212,62 @@ slots: QCoreApplication::processEvents(); } } + + void test_enable_disable() + { + QString folder_resource = QFINDTESTDATA("testdata/test_folder"); + QString file_mod = QFINDTESTDATA("testdata/supercoolmod.jar"); + + QTemporaryDir tmp; + ResourceFolderModel model(tmp.path()); + + QCOMPARE(model.size(), 0); + + { + EXEC_UPDATE_TASK(model.installResource(folder_resource), QVERIFY) + } + { + EXEC_UPDATE_TASK(model.installResource(file_mod), QVERIFY) + } + + for (auto res : model.all()) + qDebug() << res->name(); + + QCOMPARE(model.size(), 2); + + auto& res_1 = model.at(0).type() != ResourceType::FOLDER ? model.at(0) : model.at(1); + auto& res_2 = model.at(0).type() == ResourceType::FOLDER ? model.at(0) : model.at(1); + auto id_1 = res_1.internal_id(); + auto id_2 = res_2.internal_id(); + bool initial_enabled_res_2 = res_2.enabled(); + bool initial_enabled_res_1 = res_1.enabled(); + + QVERIFY(res_1.type() != ResourceType::FOLDER && res_1.type() != ResourceType::UNKNOWN); + qDebug() << "res_1 is of the correct type."; + QVERIFY(res_1.enabled()); + qDebug() << "res_1 is initially enabled."; + + QVERIFY(res_1.enable(EnableAction::TOGGLE)); + + QVERIFY(res_1.enabled() == !initial_enabled_res_1); + qDebug() << "res_1 got successfully toggled."; + + QVERIFY(res_1.enable(EnableAction::TOGGLE)); + qDebug() << "res_1 got successfully toggled again."; + + QVERIFY(res_1.enabled() == initial_enabled_res_1); + QVERIFY(res_1.internal_id() == id_1); + qDebug() << "res_1 got back to its initial state."; + + QVERIFY(!res_2.enable(initial_enabled_res_2 ? EnableAction::ENABLE : EnableAction::DISABLE)); + QVERIFY(res_2.enabled() == initial_enabled_res_2); + QVERIFY(res_2.internal_id() == id_2); + + while (model.hasPendingParseTasks()) { + QTest::qSleep(20); + QCoreApplication::processEvents(); + } + } }; QTEST_GUILESS_MAIN(ResourceFolderModelTest) diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.cpp b/launcher/ui/pages/instance/ExternalResourcesPage.cpp index 0a3687e5..f31e8325 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.cpp +++ b/launcher/ui/pages/instance/ExternalResourcesPage.cpp @@ -40,6 +40,7 @@ ExternalResourcesPage::ExternalResourcesPage(BaseInstance* instance, std::shared connect(ui->actionViewFolder, &QAction::triggered, this, &ExternalResourcesPage::viewFolder); connect(ui->treeView, &ModListView::customContextMenuRequested, this, &ExternalResourcesPage::ShowContextMenu); + connect(ui->treeView, &ModListView::activated, this, &ExternalResourcesPage::itemActivated); auto selection_model = ui->treeView->selectionModel(); connect(selection_model, &QItemSelectionModel::currentChanged, this, &ExternalResourcesPage::current); @@ -81,6 +82,15 @@ void ExternalResourcesPage::retranslate() ui->retranslateUi(this); } +void ExternalResourcesPage::itemActivated(const QModelIndex&) +{ + if (!m_controlsEnabled) + return; + + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()); + m_model->setResourceEnabled(selection.indexes(), EnableAction::TOGGLE); +} + void ExternalResourcesPage::filterTextChanged(const QString& newContents) { m_viewFilter = newContents; @@ -157,6 +167,24 @@ void ExternalResourcesPage::removeItem() m_model->deleteResources(selection.indexes()); } +void ExternalResourcesPage::enableItem() +{ + if (!m_controlsEnabled) + return; + + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()); + m_model->setResourceEnabled(selection.indexes(), EnableAction::ENABLE); +} + +void ExternalResourcesPage::disableItem() +{ + if (!m_controlsEnabled) + return; + + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()); + m_model->setResourceEnabled(selection.indexes(), EnableAction::DISABLE); +} + void ExternalResourcesPage::viewConfigs() { DesktopServices::openDirectory(m_instance->instanceConfigFolder(), true); diff --git a/launcher/ui/pages/instance/ExternalResourcesPage.h b/launcher/ui/pages/instance/ExternalResourcesPage.h index 280f1542..8e352cef 100644 --- a/launcher/ui/pages/instance/ExternalResourcesPage.h +++ b/launcher/ui/pages/instance/ExternalResourcesPage.h @@ -45,15 +45,15 @@ class ExternalResourcesPage : public QMainWindow, public BasePage { virtual bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous); protected slots: - virtual void itemActivated(const QModelIndex& index) {}; + void itemActivated(const QModelIndex& index); void filterTextChanged(const QString& newContents); virtual void runningStateChanged(bool running); virtual void addItem(); virtual void removeItem(); - virtual void enableItem() {}; - virtual void disableItem() {}; + virtual void enableItem(); + virtual void disableItem(); virtual void viewFolder(); virtual void viewConfigs(); diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 63897fb0..75b40e77 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -110,8 +110,6 @@ ModFolderPage::ModFolderPage(BaseInstance* inst, std::shared_ptr ModFolderPage::runningStateChanged(m_instance && m_instance->isRunning()); } - - connect(ui->treeView, &ModListView::activated, this, &ModFolderPage::itemActivated); } void ModFolderPage::runningStateChanged(bool running) @@ -126,33 +124,6 @@ bool ModFolderPage::shouldDisplay() const return true; } -void ModFolderPage::itemActivated(const QModelIndex&) -{ - if (!m_controlsEnabled) - return; - - auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()); - m_model->setModStatus(selection.indexes(), ModFolderModel::Toggle); -} - -void ModFolderPage::enableItem() -{ - if (!m_controlsEnabled) - return; - - auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()); - m_model->setModStatus(selection.indexes(), ModFolderModel::Enable); -} - -void ModFolderPage::disableItem() -{ - if (!m_controlsEnabled) - return; - - auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()); - m_model->setModStatus(selection.indexes(), ModFolderModel::Disable); -} - bool ModFolderPage::onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) { auto sourceCurrent = m_filterModel->mapToSource(current); diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index 5da353f0..7fc9d9a1 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -58,11 +58,6 @@ class ModFolderPage : public ExternalResourcesPage { public slots: bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override; - void itemActivated(const QModelIndex& index) override; - - void enableItem() override; - void disableItem() override; - private slots: void installMods(); void updateMods(); From 0b81b283bfdd16b409127f22eac7b51ce0142929 Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 13 Aug 2022 14:35:44 -0300 Subject: [PATCH 128/273] fix: LGTM warnings Signed-off-by: flow --- launcher/minecraft/mod/ModDetails.h | 28 +++++++++++++++++++ .../minecraft/mod/ResourceFolderModel.cpp | 4 +-- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/launcher/minecraft/mod/ModDetails.h b/launcher/minecraft/mod/ModDetails.h index 118b156f..dd84b0a3 100644 --- a/launcher/minecraft/mod/ModDetails.h +++ b/launcher/minecraft/mod/ModDetails.h @@ -91,4 +91,32 @@ struct ModDetails , authors(other.authors) , status(other.status) {} + + ModDetails& operator=(ModDetails& other) + { + this->mod_id = other.mod_id; + this->name = other.name; + this->version = other.version; + this->mcversion = other.mcversion; + this->homeurl = other.homeurl; + this->description = other.description; + this->authors = other.authors; + this->status = other.status; + + return *this; + } + + ModDetails& operator=(ModDetails&& other) + { + this->mod_id = other.mod_id; + this->name = other.name; + this->version = other.version; + this->mcversion = other.mcversion; + this->homeurl = other.homeurl; + this->description = other.description; + this->authors = other.authors; + this->status = other.status; + + return *this; + } }; diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 31d88eb6..bc18ddc2 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -380,8 +380,8 @@ bool ResourceFolderModel::validateIndex(const QModelIndex& index) const if (!index.isValid()) return false; - size_t row = index.row(); - if (row < 0 || row >= size()) + int row = index.row(); + if (row < 0 || row >= m_resources.size()) return false; return true; From 6be59b53f1a2435596838e85a74cb6543fc584f3 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 21 Aug 2022 13:04:48 -0300 Subject: [PATCH 129/273] feat: add eternal cache entries Those are entries that don't get stale over time, so we don't invalidate them if they 'expire'. Signed-off-by: flow --- launcher/net/Download.cpp | 2 +- launcher/net/Download.h | 2 +- launcher/net/HttpMetaCache.cpp | 17 +++++++++++++---- launcher/net/HttpMetaCache.h | 9 ++++++++- launcher/net/MetaCacheSink.cpp | 9 ++++++--- launcher/net/MetaCacheSink.h | 3 ++- 6 files changed, 31 insertions(+), 11 deletions(-) diff --git a/launcher/net/Download.cpp b/launcher/net/Download.cpp index e6a6adcc..3778b939 100644 --- a/launcher/net/Download.cpp +++ b/launcher/net/Download.cpp @@ -60,7 +60,7 @@ auto Download::makeCached(QUrl url, MetaEntryPtr entry, Options options) -> Down dl->m_url = url; dl->m_options = options; auto md5Node = new ChecksumValidator(QCryptographicHash::Md5); - auto cachedNode = new MetaCacheSink(entry, md5Node); + auto cachedNode = new MetaCacheSink(entry, md5Node, options.testFlag(Option::MakeEternal)); dl->m_sink.reset(cachedNode); return dl; } diff --git a/launcher/net/Download.h b/launcher/net/Download.h index 1d264381..3faa5db5 100644 --- a/launcher/net/Download.h +++ b/launcher/net/Download.h @@ -49,7 +49,7 @@ class Download : public NetAction { public: using Ptr = shared_qobject_ptr; - enum class Option { NoOptions = 0, AcceptLocalFiles = 1 }; + enum class Option { NoOptions = 0, AcceptLocalFiles = 1, MakeEternal = 2 }; Q_DECLARE_FLAGS(Options, Option) protected: diff --git a/launcher/net/HttpMetaCache.cpp b/launcher/net/HttpMetaCache.cpp index deb2780b..9606ddb6 100644 --- a/launcher/net/HttpMetaCache.cpp +++ b/launcher/net/HttpMetaCache.cpp @@ -229,8 +229,13 @@ void HttpMetaCache::Load() 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->current_age = Json::ensureDouble(element_obj, "current_age"); - foo->max_age = Json::ensureDouble(element_obj, "max_age"); + + foo->makeEternal(Json::ensureBoolean(element_obj, "eternal", false)); + if (!foo->isEternal()) { + foo->current_age = Json::ensureDouble(element_obj, "current_age"); + foo->max_age = Json::ensureDouble(element_obj, "max_age"); + } + // presumed innocent until closer examination foo->stale = false; @@ -271,8 +276,12 @@ void HttpMetaCache::SaveNow() 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)); - entryObj.insert("current_age", QJsonValue(double(entry->current_age))); - entryObj.insert("max_age", QJsonValue(double(entry->max_age))); + 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))); + } entriesArr.append(entryObj); } } diff --git a/launcher/net/HttpMetaCache.h b/launcher/net/HttpMetaCache.h index df3549e8..c0b12318 100644 --- a/launcher/net/HttpMetaCache.h +++ b/launcher/net/HttpMetaCache.h @@ -64,13 +64,17 @@ class MetaEntry { auto getMD5Sum() -> QString { return md5sum; } void setMD5Sum(QString md5sum) { this->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; } + auto getCurrentAge() -> qint64 { return current_age; } void setCurrentAge(qint64 age) { current_age = age; } auto getMaximumAge() -> qint64 { return max_age; } void setMaximumAge(qint64 age) { max_age = age; } - bool isExpired(qint64 offset) { return current_age >= max_age - offset; }; + bool isExpired(qint64 offset) { return !is_eternal && (current_age >= max_age - offset); }; protected: QString baseId; @@ -78,10 +82,13 @@ class MetaEntry { 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; }; diff --git a/launcher/net/MetaCacheSink.cpp b/launcher/net/MetaCacheSink.cpp index ab0c9fcb..5ae53c1c 100644 --- a/launcher/net/MetaCacheSink.cpp +++ b/launcher/net/MetaCacheSink.cpp @@ -46,8 +46,8 @@ namespace Net { #define MAX_TIME_TO_EXPIRE 1*7*24*60*60 -MetaCacheSink::MetaCacheSink(MetaEntryPtr entry, ChecksumValidator * md5sum) - :Net::FileSink(entry->getFullPath()), m_entry(entry), m_md5Node(md5sum) +MetaCacheSink::MetaCacheSink(MetaEntryPtr entry, ChecksumValidator * md5sum, bool is_eternal) + :Net::FileSink(entry->getFullPath()), m_entry(entry), m_md5Node(md5sum), m_is_eternal(is_eternal) { addValidator(md5sum); } @@ -95,7 +95,10 @@ Task::State MetaCacheSink::finalizeCache(QNetworkReply & reply) m_entry->setLocalChangedTimestamp(output_file_info.lastModified().toUTC().toMSecsSinceEpoch()); { // Cache lifetime - if (reply.hasRawHeader("Cache-Control")) { + if (m_is_eternal) { + qDebug() << "[MetaCache] Adding eternal cache entry:" << m_entry->getFullPath(); + m_entry->makeEternal(true); + } else if (reply.hasRawHeader("Cache-Control")) { auto cache_control_header = reply.rawHeader("Cache-Control"); // qDebug() << "[MetaCache] Parsing 'Cache-Control' header with" << cache_control_header; diff --git a/launcher/net/MetaCacheSink.h b/launcher/net/MetaCacheSink.h index c9f7edfe..f5948085 100644 --- a/launcher/net/MetaCacheSink.h +++ b/launcher/net/MetaCacheSink.h @@ -42,7 +42,7 @@ namespace Net { class MetaCacheSink : public FileSink { public: - MetaCacheSink(MetaEntryPtr entry, ChecksumValidator* md5sum); + MetaCacheSink(MetaEntryPtr entry, ChecksumValidator* md5sum, bool is_eternal = false); virtual ~MetaCacheSink() = default; auto hasLocalData() -> bool override; @@ -54,5 +54,6 @@ class MetaCacheSink : public FileSink { private: MetaEntryPtr m_entry; ChecksumValidator* m_md5Node; + bool m_is_eternal; }; } // namespace Net From 5ac4e73697696c2ea00b72b55cac5138c67a9b55 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 21 Aug 2022 13:06:18 -0300 Subject: [PATCH 130/273] fix: make libraries cache eternal This restores the behavior prior to PR #920. Signed-off-by: flow --- launcher/minecraft/Library.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/launcher/minecraft/Library.cpp b/launcher/minecraft/Library.cpp index c7982705..ba7aed4b 100644 --- a/launcher/minecraft/Library.cpp +++ b/launcher/minecraft/Library.cpp @@ -88,6 +88,9 @@ QList Library::getDownloads( options |= Net::Download::Option::AcceptLocalFiles; } + // Don't add a time limit for the libraries cache entry validity + options |= Net::Download::Option::MakeEternal; + if(sha1.size()) { auto rawSha1 = QByteArray::fromHex(sha1.toLatin1()); From ddf1e1cceea904e7a69ac62c2b281944888e4bb2 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 21 Aug 2022 13:18:04 -0300 Subject: [PATCH 131/273] fix: make FML libraries cache eternal Signed-off-by: flow --- launcher/minecraft/update/FMLLibrariesTask.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/launcher/minecraft/update/FMLLibrariesTask.cpp b/launcher/minecraft/update/FMLLibrariesTask.cpp index b6238ce9..7a0bd2f3 100644 --- a/launcher/minecraft/update/FMLLibrariesTask.cpp +++ b/launcher/minecraft/update/FMLLibrariesTask.cpp @@ -63,11 +63,12 @@ void FMLLibrariesTask::executeTask() setStatus(tr("Downloading FML libraries...")); auto dljob = new NetJob("FML libraries", APPLICATION->network()); auto metacache = APPLICATION->metacache(); + Net::Download::Options options = Net::Download::Option::MakeEternal; for (auto &lib : fmlLibsToProcess) { auto entry = metacache->resolveEntry("fmllibs", lib.filename); QString urlString = BuildConfig.FMLLIBS_BASE_URL + lib.filename; - dljob->addNetAction(Net::Download::makeCached(QUrl(urlString), entry)); + dljob->addNetAction(Net::Download::makeCached(QUrl(urlString), entry, options)); } connect(dljob, &NetJob::succeeded, this, &FMLLibrariesTask::fmllibsFinished); From 13184eb8e954656ca8fe91ef78dc22ecb4b65710 Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Wed, 24 Aug 2022 12:31:38 +0200 Subject: [PATCH 132/273] fix: fix urls on ftb legacy Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> --- .../ui/pages/modplatform/legacy_ftb/Page.ui | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.ui b/launcher/ui/pages/modplatform/legacy_ftb/Page.ui index f4231d8d..ad08dc25 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/Page.ui +++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.ui @@ -35,7 +35,11 @@ - + + + true + + @@ -45,7 +49,11 @@ - + + + true + + @@ -95,7 +103,11 @@ - + + + true + + From 2186b134a49ae6ad49e5782098239f3de0f9c5ab Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 28 Aug 2022 14:47:52 -0300 Subject: [PATCH 133/273] fix: Mod type enum -> Resource type enum Signed-off-by: flow --- launcher/modplatform/EnsureMetadataTask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/modplatform/EnsureMetadataTask.cpp b/launcher/modplatform/EnsureMetadataTask.cpp index aa77b5b9..234330a7 100644 --- a/launcher/modplatform/EnsureMetadataTask.cpp +++ b/launcher/modplatform/EnsureMetadataTask.cpp @@ -47,7 +47,7 @@ EnsureMetadataTask::EnsureMetadataTask(QList& mods, QDir dir, ModPlatform: Hashing::Hasher::Ptr EnsureMetadataTask::createNewHash(Mod* mod) { - if (!mod || !mod->valid() || mod->type() == Mod::MOD_FOLDER) + if (!mod || !mod->valid() || mod->type() == ResourceType::FOLDER) return nullptr; return Hashing::createHasher(mod->fileinfo().absoluteFilePath(), m_provider); From 3f4e55be4f0ec3bae295ed53e50e493f4bc72033 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 28 Aug 2022 15:36:01 -0300 Subject: [PATCH 134/273] fix: ensure destination file paths exist when overriding folders Signed-off-by: flow --- launcher/FileSystem.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 21edbb48..8eeb2885 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -467,6 +467,7 @@ bool overrideFolder(QString overwritten_path, QString override_path) for (auto file : listFolderPaths(root_override)) { QString destination = file; destination.replace(override_path, overwritten_path); + ensureFilePathExists(destination); qDebug() << QString("Applying override %1 in %2").arg(file, destination); From 369a8cdc7497d7bc335b660ac6c14652abe8fde7 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 22 Jul 2022 00:28:08 -0300 Subject: [PATCH 135/273] fix: only try to start tasks that are really there This fixes an annoying issue where concurrent tasks would try to start multiple tasks even when there was not that many tasks to run in the first place, causing some amount of log spam. Signed-off-by: flow --- launcher/tasks/ConcurrentTask.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp index ab7cbd03..2af296b9 100644 --- a/launcher/tasks/ConcurrentTask.cpp +++ b/launcher/tasks/ConcurrentTask.cpp @@ -37,7 +37,8 @@ void ConcurrentTask::executeTask() { m_total_size = m_queue.size(); - for (int i = 0; i < m_total_max_size; i++) { + int num_starts = std::min(m_total_max_size, m_total_size); + for (int i = 0; i < num_starts; i++) { QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection); } } From a720bcc637957ea7d5ed5594797a682818e4bfef Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 22 Jul 2022 00:29:28 -0300 Subject: [PATCH 136/273] fix: bogus progress update when the total step progress was zero Signed-off-by: flow --- launcher/tasks/ConcurrentTask.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp index 2af296b9..ce2f9b87 100644 --- a/launcher/tasks/ConcurrentTask.cpp +++ b/launcher/tasks/ConcurrentTask.cpp @@ -132,11 +132,6 @@ void ConcurrentTask::subTaskStatus(const QString& msg) void ConcurrentTask::subTaskProgress(qint64 current, qint64 total) { - if (total == 0) { - setProgress(0, 100); - return; - } - m_stepProgress = current; m_stepTotalProgress = total; } From c410bb4ecbc553fa6016a4c4e58026343deeec33 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 22 Jul 2022 01:19:56 -0300 Subject: [PATCH 137/273] fix: 'succeeded while not running' spam in ConcurrentTask Signed-off-by: flow --- launcher/tasks/ConcurrentTask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp index ce2f9b87..ecd7ae13 100644 --- a/launcher/tasks/ConcurrentTask.cpp +++ b/launcher/tasks/ConcurrentTask.cpp @@ -71,7 +71,7 @@ void ConcurrentTask::startNext() if (m_aborted || m_doing.count() > m_total_max_size) return; - if (m_queue.isEmpty() && m_doing.isEmpty()) { + if (m_queue.isEmpty() && m_doing.isEmpty() && !wasSuccessful()) { emitSucceeded(); return; } From bdf464e792f4b0c8a20f92a9073699c5fd98cd46 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 24 Jul 2022 17:16:14 -0300 Subject: [PATCH 138/273] fix: abort logic running subsequent tasks anyways some times Signed-off-by: flow --- launcher/tasks/ConcurrentTask.cpp | 20 +++++++++++++------- launcher/tasks/ConcurrentTask.h | 4 +++- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp index ecd7ae13..484ac58e 100644 --- a/launcher/tasks/ConcurrentTask.cpp +++ b/launcher/tasks/ConcurrentTask.cpp @@ -45,25 +45,31 @@ void ConcurrentTask::executeTask() bool ConcurrentTask::abort() { + m_queue.clear(); + m_aborted = true; + if (m_doing.isEmpty()) { // Don't call emitAborted() here, we want to bypass the 'is the task running' check emit aborted(); emit finished(); - m_aborted = true; return true; } - m_queue.clear(); + bool suceedeed = true; - m_aborted = true; - for (auto task : m_doing) - m_aborted &= task->abort(); + QMutableHashIterator doing_iter(m_doing); + while (doing_iter.hasNext()) { + auto task = doing_iter.next(); + suceedeed &= (task.value())->abort(); + } - if (m_aborted) + if (suceedeed) emitAborted(); + else + emitFailed(tr("Failed to abort all running tasks.")); - return m_aborted; + return suceedeed; } void ConcurrentTask::startNext() diff --git a/launcher/tasks/ConcurrentTask.h b/launcher/tasks/ConcurrentTask.h index 5898899d..f1279d32 100644 --- a/launcher/tasks/ConcurrentTask.h +++ b/launcher/tasks/ConcurrentTask.h @@ -9,7 +9,9 @@ class ConcurrentTask : public Task { Q_OBJECT public: explicit ConcurrentTask(QObject* parent = nullptr, QString task_name = "", int max_concurrent = 6); - virtual ~ConcurrentTask(); + ~ConcurrentTask() override; + + bool canAbort() const override { return true; } inline auto isMultiStep() const -> bool override { return m_queue.size() > 1; }; auto getStepProgress() const -> qint64 override; From e89969991868b05723ae87454d4e22e370137d15 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 12 Jun 2022 13:30:09 -0300 Subject: [PATCH 139/273] refactor: make SequentialTask inherit from ConcurrentTask In a way, sequential tasks are just concurrent tasks with only a single task running concurrently, so we can remove LOTS of duplicated logic :) Signed-off-by: flow --- launcher/tasks/SequentialTask.cpp | 108 +++--------------------------- launcher/tasks/SequentialTask.h | 58 +++++----------- 2 files changed, 26 insertions(+), 140 deletions(-) diff --git a/launcher/tasks/SequentialTask.cpp b/launcher/tasks/SequentialTask.cpp index f1e1a889..a34137cb 100644 --- a/launcher/tasks/SequentialTask.cpp +++ b/launcher/tasks/SequentialTask.cpp @@ -2,107 +2,21 @@ #include -SequentialTask::SequentialTask(QObject* parent, const QString& task_name) : Task(parent), m_name(task_name), m_currentIndex(-1) {} - -SequentialTask::~SequentialTask() -{ - for(auto task : m_queue){ - if(task) - task->deleteLater(); - } -} - -auto SequentialTask::getStepProgress() const -> qint64 -{ - return m_stepProgress; -} - -auto SequentialTask::getStepTotalProgress() const -> qint64 -{ - return m_stepTotalProgress; -} - -void SequentialTask::addTask(Task::Ptr task) -{ - m_queue.append(task); -} - -void SequentialTask::executeTask() -{ - m_currentIndex = -1; - startNext(); -} - -bool SequentialTask::abort() -{ - if(m_currentIndex == -1 || m_currentIndex >= m_queue.size()) { - if(m_currentIndex == -1) { - // Don't call emitAborted() here, we want to bypass the 'is the task running' check - emit aborted(); - emit finished(); - } - - m_aborted = true; - return true; - } - - bool succeeded = m_queue[m_currentIndex]->abort(); - m_aborted = succeeded; - - if (succeeded) - emitAborted(); - - return succeeded; -} +SequentialTask::SequentialTask(QObject* parent, QString task_name) : ConcurrentTask(parent, task_name, 1) {} void SequentialTask::startNext() { - if (m_aborted) - return; - - if (m_currentIndex != -1 && m_currentIndex < m_queue.size()) { - Task::Ptr previous = m_queue.at(m_currentIndex); - disconnect(previous.get(), 0, this, 0); - } - - m_currentIndex++; - if (m_queue.isEmpty() || m_currentIndex >= m_queue.size()) { - emitSucceeded(); - return; - } - Task::Ptr next = m_queue[m_currentIndex]; - - connect(next.get(), SIGNAL(failed(QString)), this, SLOT(subTaskFailed(QString))); - connect(next.get(), SIGNAL(succeeded()), this, SLOT(startNext())); - - connect(next.get(), SIGNAL(status(QString)), this, SLOT(subTaskStatus(QString))); - connect(next.get(), SIGNAL(stepStatus(QString)), this, SLOT(subTaskStatus(QString))); - - connect(next.get(), SIGNAL(progress(qint64, qint64)), this, SLOT(subTaskProgress(qint64, qint64))); - - setStatus(tr("Executing task %1 out of %2").arg(m_currentIndex + 1).arg(m_queue.size())); - setStepStatus(next->isMultiStep() ? next->getStepStatus() : next->getStatus()); - - setProgress(m_currentIndex + 1, m_queue.count()); - - next->start(); -} - -void SequentialTask::subTaskFailed(const QString& msg) -{ - emitFailed(msg); -} -void SequentialTask::subTaskStatus(const QString& msg) -{ - setStepStatus(msg); -} -void SequentialTask::subTaskProgress(qint64 current, qint64 total) -{ - if (total == 0) { - setProgress(0, 100); + if (m_failed.size() > 0) { + emitFailed(tr("One of the tasks failed!")); + qWarning() << m_failed.constBegin()->get()->failReason(); return; } - m_stepProgress = current; - m_stepTotalProgress = total; + ConcurrentTask::startNext(); +} + +void SequentialTask::updateState() +{ + setProgress(m_done.count(), m_total_size); + setStatus(tr("Executing task %1 out of %2").arg(QString::number(m_doing.count() + m_done.count()), QString::number(m_total_size))); } diff --git a/launcher/tasks/SequentialTask.h b/launcher/tasks/SequentialTask.h index f5a58b1b..5eace96e 100644 --- a/launcher/tasks/SequentialTask.h +++ b/launcher/tasks/SequentialTask.h @@ -1,49 +1,21 @@ #pragma once -#include "Task.h" -#include "QObjectPtr.h" +#include "ConcurrentTask.h" -#include - -class SequentialTask : public Task -{ +/** A concurrent task that only allows one concurrent task :) + * + * This should be used when there's a need to maintain a strict ordering of task executions, and + * the starting of a task is contingent on the success of the previous one. + * + * See MultipleOptionsTask if that's not the case. + */ +class SequentialTask : public ConcurrentTask { Q_OBJECT -public: - explicit SequentialTask(QObject *parent = nullptr, const QString& task_name = ""); - virtual ~SequentialTask(); + public: + explicit SequentialTask(QObject* parent = nullptr, QString task_name = ""); + ~SequentialTask() override = default; - inline auto isMultiStep() const -> bool override { return m_queue.size() > 1; }; - auto getStepProgress() const -> qint64 override; - auto getStepTotalProgress() const -> qint64 override; - - inline auto getStepStatus() const -> QString override { return m_step_status; } - - void addTask(Task::Ptr task); - -public slots: - bool abort() override; - -protected -slots: - void executeTask() override; - - virtual void startNext(); - virtual void subTaskFailed(const QString &msg); - virtual void subTaskStatus(const QString &msg); - virtual void subTaskProgress(qint64 current, qint64 total); - -protected: - void setStepStatus(QString status) { m_step_status = status; emit stepStatus(status); }; - -protected: - QString m_name; - QString m_step_status; - - QQueue m_queue; - int m_currentIndex; - - qint64 m_stepProgress = 0; - qint64 m_stepTotalProgress = 100; - - bool m_aborted = false; + protected: + void startNext() override; + void updateState() override; }; From 87a0482b8b299fd54691e3042ca661863ea6290a Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 21 Jul 2022 22:40:06 -0300 Subject: [PATCH 140/273] refactor: make MultipleOptionsTask inherit from ConcurrentTask too Signed-off-by: flow --- launcher/tasks/MultipleOptionsTask.cpp | 45 +++++++------------------- launcher/tasks/MultipleOptionsTask.h | 16 ++++----- 2 files changed, 19 insertions(+), 42 deletions(-) diff --git a/launcher/tasks/MultipleOptionsTask.cpp b/launcher/tasks/MultipleOptionsTask.cpp index 6e853568..7741bef0 100644 --- a/launcher/tasks/MultipleOptionsTask.cpp +++ b/launcher/tasks/MultipleOptionsTask.cpp @@ -2,47 +2,26 @@ #include -MultipleOptionsTask::MultipleOptionsTask(QObject* parent, const QString& task_name) : SequentialTask(parent, task_name) {} +MultipleOptionsTask::MultipleOptionsTask(QObject* parent, const QString& task_name) : ConcurrentTask(parent, task_name) {} void MultipleOptionsTask::startNext() { - Task* previous = nullptr; - if (m_currentIndex != -1) { - previous = m_queue[m_currentIndex].get(); - disconnect(previous, 0, this, 0); - } - - m_currentIndex++; - if ((previous && previous->wasSuccessful())) { + if (m_done.size() != m_failed.size()) { emitSucceeded(); return; } - Task::Ptr next = m_queue[m_currentIndex]; - - connect(next.get(), &Task::failed, this, &MultipleOptionsTask::subTaskFailed); - connect(next.get(), &Task::succeeded, this, &MultipleOptionsTask::startNext); - - connect(next.get(), &Task::status, this, &MultipleOptionsTask::subTaskStatus); - connect(next.get(), &Task::stepStatus, this, &MultipleOptionsTask::subTaskStatus); - - connect(next.get(), &Task::progress, this, &MultipleOptionsTask::subTaskProgress); - - qDebug() << QString("Making attemp %1 out of %2").arg(m_currentIndex + 1).arg(m_queue.size()); - setStatus(tr("Making attempt #%1 out of %2").arg(m_currentIndex + 1).arg(m_queue.size())); - setStepStatus(next->isMultiStep() ? next->getStepStatus() : next->getStatus()); - - next->start(); -} - -void MultipleOptionsTask::subTaskFailed(QString const& reason) -{ - qDebug() << QString("Failed attempt #%1 of %2. Reason: %3").arg(m_currentIndex + 1).arg(m_queue.size()).arg(reason); - if(m_currentIndex < m_queue.size() - 1) { - startNext(); + if (m_queue.isEmpty()) { + emitFailed(tr("All attempts have failed!")); + qWarning() << "All attempts have failed!"; return; } - qWarning() << QString("All attempts have failed!"); - emitFailed(); + ConcurrentTask::startNext(); +} + +void MultipleOptionsTask::updateState() +{ + setProgress(m_done.count(), m_total_size); + setStatus(tr("Attempting task %1 out of %2").arg(QString::number(m_doing.count() + m_done.count()), QString::number(m_total_size))); } diff --git a/launcher/tasks/MultipleOptionsTask.h b/launcher/tasks/MultipleOptionsTask.h index 7c508b00..c65356b0 100644 --- a/launcher/tasks/MultipleOptionsTask.h +++ b/launcher/tasks/MultipleOptionsTask.h @@ -1,19 +1,17 @@ #pragma once -#include "SequentialTask.h" +#include "ConcurrentTask.h" /* This task type will attempt to do run each of it's subtasks in sequence, * until one of them succeeds. When that happens, the remaining tasks will not run. * */ -class MultipleOptionsTask : public SequentialTask -{ +class MultipleOptionsTask : public ConcurrentTask { Q_OBJECT -public: - explicit MultipleOptionsTask(QObject *parent = nullptr, const QString& task_name = ""); - virtual ~MultipleOptionsTask() = default; + public: + explicit MultipleOptionsTask(QObject* parent = nullptr, const QString& task_name = ""); + ~MultipleOptionsTask() override = default; -private -slots: + private slots: void startNext() override; - void subTaskFailed(const QString &msg) override; + void updateState() override; }; From 7b6d26990469f26dfbed6d55af25240ef0618df4 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 22 Jul 2022 00:30:27 -0300 Subject: [PATCH 141/273] refactor: make NetJob inherit from ConcurrentTask as well! Avoids lots of code duplication Signed-off-by: flow --- launcher/net/NetAction.h | 2 + launcher/net/NetJob.cpp | 202 ++++-------------- launcher/net/NetJob.h | 48 ++--- launcher/ui/pages/modplatform/ModModel.cpp | 5 +- .../modplatform/modrinth/ModrinthModel.cpp | 5 +- 5 files changed, 64 insertions(+), 198 deletions(-) diff --git a/launcher/net/NetAction.h b/launcher/net/NetAction.h index 729d4132..d9c4fadc 100644 --- a/launcher/net/NetAction.h +++ b/launcher/net/NetAction.h @@ -54,6 +54,8 @@ class NetAction : public Task { QUrl url() { return m_url; } auto index() -> int { return m_index_within_job; } + void setNetwork(shared_qobject_ptr network) { m_network = network; } + protected slots: virtual void downloadProgress(qint64 bytesReceived, qint64 bytesTotal) = 0; virtual void downloadError(QNetworkReply::NetworkError error) = 0; diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp index bab35fa5..20d75976 100644 --- a/launcher/net/NetJob.cpp +++ b/launcher/net/NetJob.cpp @@ -35,204 +35,90 @@ */ #include "NetJob.h" -#include "Download.h" auto NetJob::addNetAction(NetAction::Ptr action) -> bool { - action->m_index_within_job = m_downloads.size(); - m_downloads.append(action); - part_info pi; - m_parts_progress.append(pi); + action->m_index_within_job = m_queue.size(); + m_queue.append(action); - partProgress(m_parts_progress.count() - 1, action->getProgress(), action->getTotalProgress()); - - if (action->isRunning()) { - connect(action.get(), &NetAction::succeeded, [this, action]{ partSucceeded(action->index()); }); - connect(action.get(), &NetAction::failed, [this, action](QString){ partFailed(action->index()); }); - connect(action.get(), &NetAction::aborted, [this, action](){ partAborted(action->index()); }); - connect(action.get(), &NetAction::progress, [this, action](qint64 done, qint64 total) { partProgress(action->index(), done, total); }); - connect(action.get(), &NetAction::status, this, &NetJob::status); - } else { - m_todo.append(m_parts_progress.size() - 1); - } + action->setNetwork(m_network); return true; } +void NetJob::startNext() +{ + if (m_queue.isEmpty() && m_doing.isEmpty()) { + // We're finished, check for failures and retry if we can (up to 3 times) + if (!m_failed.isEmpty() && m_try < 3) { + m_try += 1; + while (!m_failed.isEmpty()) + m_queue.enqueue(m_failed.take(*m_failed.keyBegin())); + } + } + + ConcurrentTask::startNext(); +} + +auto NetJob::size() const -> int +{ + return m_queue.size() + m_doing.size() + m_done.size(); +} + auto NetJob::canAbort() const -> bool { bool canFullyAbort = true; // can abort the downloads on the queue? - for (auto index : m_todo) { - auto part = m_downloads[index]; + for (auto part : m_queue) canFullyAbort &= part->canAbort(); - } + // can abort the active downloads? - for (auto index : m_doing) { - auto part = m_downloads[index]; + for (auto part : m_doing) canFullyAbort &= part->canAbort(); - } return canFullyAbort; } -void NetJob::executeTask() -{ - // hack that delays early failures so they can be caught easier - QMetaObject::invokeMethod(this, "startMoreParts", Qt::QueuedConnection); -} - -auto NetJob::getFailedFiles() -> QStringList -{ - QStringList failed; - for (auto index : m_failed) { - failed.push_back(m_downloads[index]->url().toString()); - } - failed.sort(); - return failed; -} - auto NetJob::abort() -> bool { bool fullyAborted = true; // fail all downloads on the queue -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) - QSet todoSet(m_todo.begin(), m_todo.end()); - m_failed.unite(todoSet); -#else - m_failed.unite(m_todo.toSet()); -#endif - m_todo.clear(); + for (auto task : m_queue) + m_failed.insert(task.get(), task); + m_queue.clear(); // abort active downloads auto toKill = m_doing.values(); - for (auto index : toKill) { - auto part = m_downloads[index]; + for (auto part : toKill) { fullyAborted &= part->abort(); } return fullyAborted; } -void NetJob::partSucceeded(int index) +auto NetJob::getFailedActions() -> QList { - // do progress. all slots are 1 in size at least - auto& slot = m_parts_progress[index]; - partProgress(index, slot.total_progress, slot.total_progress); - - m_doing.remove(index); - m_done.insert(index); - m_downloads[index].get()->disconnect(this); - - startMoreParts(); + QList failed; + for (auto index : m_failed) { + failed.push_back(dynamic_cast(index.get())); + } + return failed; } -void NetJob::partFailed(int index) +auto NetJob::getFailedFiles() -> QList { - m_doing.remove(index); - - auto& slot = m_parts_progress[index]; - // Can try 3 times before failing by definitive - if (slot.failures == 3) { - m_failed.insert(index); - } else { - slot.failures++; - m_todo.enqueue(index); + QList failed; + for (auto index : m_failed) { + failed.append(static_cast(index.get())->url().toString()); } - - m_downloads[index].get()->disconnect(this); - - startMoreParts(); + return failed; } -void NetJob::partAborted(int index) +void NetJob::updateState() { - m_aborted = true; - - m_doing.remove(index); - m_failed.insert(index); - m_downloads[index].get()->disconnect(this); - - startMoreParts(); -} - -void NetJob::partProgress(int index, qint64 bytesReceived, qint64 bytesTotal) -{ - auto& slot = m_parts_progress[index]; - slot.current_progress = bytesReceived; - slot.total_progress = bytesTotal; - - int done = m_done.size(); - int doing = m_doing.size(); - int all = m_parts_progress.size(); - - qint64 bytesAll = 0; - qint64 bytesTotalAll = 0; - for (auto& partIdx : m_doing) { - auto part = m_parts_progress[partIdx]; - // do not count parts with unknown/nonsensical total size - if (part.total_progress <= 0) { - continue; - } - bytesAll += part.current_progress; - bytesTotalAll += part.total_progress; - } - - qint64 inprogress = (bytesTotalAll == 0) ? 0 : (bytesAll * 1000) / bytesTotalAll; - auto current = done * 1000 + doing * inprogress; - auto current_total = all * 1000; - // HACK: make sure it never jumps backwards. - // FAIL: This breaks if the size is not known (or is it something else?) and jumps to 1000, so if it is 1000 reset it to inprogress - if (m_current_progress == 1000) { - m_current_progress = inprogress; - } - if (m_current_progress > current) { - current = m_current_progress; - } - m_current_progress = current; - setProgress(current, current_total); -} - -void NetJob::startMoreParts() -{ - if (!isRunning()) { - // this actually makes sense. You can put running m_downloads into a NetJob and then not start it until much later. - return; - } - - // OK. We are actively processing tasks, proceed. - // Check for final conditions if there's nothing in the queue. - if (!m_todo.size()) { - if (!m_doing.size()) { - if (!m_failed.size()) { - emitSucceeded(); - } else if (m_aborted) { - emitAborted(); - } else { - emitFailed(tr("Job '%1' failed to process:\n%2").arg(objectName()).arg(getFailedFiles().join("\n"))); - } - } - return; - } - - // There's work to do, try to start more parts, to a maximum of 6 concurrent ones. - while (m_doing.size() < 6) { - if (m_todo.size() == 0) - return; - int doThis = m_todo.dequeue(); - m_doing.insert(doThis); - - auto part = m_downloads[doThis]; - - // connect signals :D - connect(part.get(), &NetAction::succeeded, this, [this, part]{ partSucceeded(part->index()); }); - connect(part.get(), &NetAction::failed, this, [this, part](QString){ partFailed(part->index()); }); - connect(part.get(), &NetAction::aborted, this, [this, part]{ partAborted(part->index()); }); - connect(part.get(), &NetAction::progress, this, [this, part](qint64 done, qint64 total) { partProgress(part->index(), done, total); }); - connect(part.get(), &NetAction::status, this, &NetJob::status); - - part->startAction(m_network); - } + emit progress(m_done.count(), m_total_size); + setStatus(tr("Executing %1 task(s) (%2 out of %3 are done)") + .arg(QString::number(m_doing.count()), QString::number(m_done.count()), QString::number(m_total_size))); } diff --git a/launcher/net/NetJob.h b/launcher/net/NetJob.h index 63c1cf51..cd5d5e48 100644 --- a/launcher/net/NetJob.h +++ b/launcher/net/NetJob.h @@ -39,64 +39,40 @@ #include #include "NetAction.h" -#include "tasks/Task.h" +#include "tasks/ConcurrentTask.h" // Those are included so that they are also included by anyone using NetJob #include "net/Download.h" #include "net/HttpMetaCache.h" -class NetJob : public Task { +class NetJob : public ConcurrentTask { Q_OBJECT public: using Ptr = shared_qobject_ptr; - explicit NetJob(QString job_name, shared_qobject_ptr network) : Task(), m_network(network) - { - setObjectName(job_name); - } - virtual ~NetJob() = default; + explicit NetJob(QString job_name, shared_qobject_ptr network) : ConcurrentTask(nullptr, job_name), m_network(network) {} + ~NetJob() override = default; - void executeTask() override; + void startNext() override; + + auto size() const -> int; auto canAbort() const -> bool override; - auto addNetAction(NetAction::Ptr action) -> bool; - auto operator[](int index) -> NetAction::Ptr { return m_downloads[index]; } - auto at(int index) -> const NetAction::Ptr { return m_downloads.at(index); } - auto size() const -> int { return m_downloads.size(); } - auto first() -> NetAction::Ptr { return m_downloads.size() != 0 ? m_downloads[0] : NetAction::Ptr{}; } - - auto getFailedFiles() -> QStringList; + auto getFailedActions() -> QList; + auto getFailedFiles() -> QList; public slots: // Qt can't handle auto at the start for some reason? bool abort() override; - private slots: - void startMoreParts(); - - void partProgress(int index, qint64 bytesReceived, qint64 bytesTotal); - void partSucceeded(int index); - void partFailed(int index); - void partAborted(int index); + protected: + void updateState() override; private: shared_qobject_ptr m_network; - struct part_info { - qint64 current_progress = 0; - qint64 total_progress = 1; - int failures = 0; - }; - - QList m_downloads; - QList m_parts_progress; - QQueue m_todo; - QSet m_doing; - QSet m_done; - QSet m_failed; - qint64 m_current_progress = 0; - bool m_aborted = false; + int m_try = 1; }; diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index 94b1f099..06a6f6b8 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -230,10 +230,11 @@ void ListModel::searchRequestFinished(QJsonDocument& doc) void ListModel::searchRequestFailed(QString reason) { - if (!jobPtr->first()->m_reply) { + auto failed_action = jobPtr->getFailedActions().at(0); + if (!failed_action->m_reply) { // Network error QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load mods.")); - } else if (jobPtr->first()->m_reply && jobPtr->first()->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 409) { + } else if (failed_action->m_reply && failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 409) { // 409 Gone, notify user to update QMessageBox::critical(nullptr, tr("Error"), //: %1 refers to the launcher itself diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index 3633d575..614be434 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -301,10 +301,11 @@ void ModpackListModel::searchRequestFinished(QJsonDocument& doc_all) void ModpackListModel::searchRequestFailed(QString reason) { - if (!jobPtr->first()->m_reply) { + auto failed_action = jobPtr->getFailedActions().at(0); + if (!failed_action->m_reply) { // Network error QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load modpacks.")); - } else if (jobPtr->first()->m_reply && jobPtr->first()->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 409) { + } else if (failed_action->m_reply && failed_action->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 409) { // 409 Gone, notify user to update QMessageBox::critical(nullptr, tr("Error"), //: %1 refers to the launcher itself From 247f99ce2f981adad48974abf95ef5e10b832503 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 22 Jul 2022 01:21:57 -0300 Subject: [PATCH 142/273] feat(test): add more tests to Tasks Signed-off-by: flow --- launcher/tasks/Task_test.cpp | 125 ++++++++++++++++++++++++++++++++++- 1 file changed, 124 insertions(+), 1 deletion(-) diff --git a/launcher/tasks/Task_test.cpp b/launcher/tasks/Task_test.cpp index ef153a6a..b56ee8a6 100644 --- a/launcher/tasks/Task_test.cpp +++ b/launcher/tasks/Task_test.cpp @@ -1,5 +1,8 @@ #include +#include "ConcurrentTask.h" +#include "MultipleOptionsTask.h" +#include "SequentialTask.h" #include "Task.h" /* Does nothing. Only used for testing. */ @@ -9,7 +12,10 @@ class BasicTask : public Task { friend class TaskTest; private: - void executeTask() override {}; + void executeTask() override + { + emitSucceeded(); + }; }; /* Does nothing. Only used for testing. */ @@ -60,6 +66,123 @@ class TaskTest : public QObject { QCOMPARE(t.getProgress(), current); QCOMPARE(t.getTotalProgress(), total); } + + void test_basicRun(){ + BasicTask t; + QObject::connect(&t, &Task::finished, [&]{ QVERIFY2(t.wasSuccessful(), "Task finished but was not successful when it should have been."); }); + t.start(); + + QVERIFY2(QTest::qWaitFor([&]() { + return t.isFinished(); + }, 1000), "Task didn't finish as it should."); + } + + void test_basicConcurrentRun(){ + BasicTask t1; + BasicTask t2; + BasicTask t3; + + ConcurrentTask t; + + t.addTask(&t1); + t.addTask(&t2); + t.addTask(&t3); + + QObject::connect(&t, &Task::finished, [&]{ + QVERIFY2(t.wasSuccessful(), "Task finished but was not successful when it should have been."); + QVERIFY(t1.wasSuccessful()); + QVERIFY(t2.wasSuccessful()); + QVERIFY(t3.wasSuccessful()); + }); + + t.start(); + QVERIFY2(QTest::qWaitFor([&]() { + return t.isFinished(); + }, 1000), "Task didn't finish as it should."); + } + + // Tests if starting new tasks after the 6 initial ones is working + void test_moreConcurrentRun(){ + BasicTask t1, t2, t3, t4, t5, t6, t7, t8, t9; + + ConcurrentTask t; + + t.addTask(&t1); + t.addTask(&t2); + t.addTask(&t3); + t.addTask(&t4); + t.addTask(&t5); + t.addTask(&t6); + t.addTask(&t7); + t.addTask(&t8); + t.addTask(&t9); + + QObject::connect(&t, &Task::finished, [&]{ + QVERIFY2(t.wasSuccessful(), "Task finished but was not successful when it should have been."); + QVERIFY(t1.wasSuccessful()); + QVERIFY(t2.wasSuccessful()); + QVERIFY(t3.wasSuccessful()); + QVERIFY(t4.wasSuccessful()); + QVERIFY(t5.wasSuccessful()); + QVERIFY(t6.wasSuccessful()); + QVERIFY(t7.wasSuccessful()); + QVERIFY(t8.wasSuccessful()); + QVERIFY(t9.wasSuccessful()); + }); + + t.start(); + QVERIFY2(QTest::qWaitFor([&]() { + return t.isFinished(); + }, 1000), "Task didn't finish as it should."); + } + + void test_basicSequentialRun(){ + BasicTask t1; + BasicTask t2; + BasicTask t3; + + SequentialTask t; + + t.addTask(&t1); + t.addTask(&t2); + t.addTask(&t3); + + QObject::connect(&t, &Task::finished, [&]{ + QVERIFY2(t.wasSuccessful(), "Task finished but was not successful when it should have been."); + QVERIFY(t1.wasSuccessful()); + QVERIFY(t2.wasSuccessful()); + QVERIFY(t3.wasSuccessful()); + }); + + t.start(); + QVERIFY2(QTest::qWaitFor([&]() { + return t.isFinished(); + }, 1000), "Task didn't finish as it should."); + } + + void test_basicMultipleOptionsRun(){ + BasicTask t1; + BasicTask t2; + BasicTask t3; + + MultipleOptionsTask t; + + t.addTask(&t1); + t.addTask(&t2); + t.addTask(&t3); + + QObject::connect(&t, &Task::finished, [&]{ + QVERIFY2(t.wasSuccessful(), "Task finished but was not successful when it should have been."); + QVERIFY(t1.wasSuccessful()); + QVERIFY(!t2.wasSuccessful()); + QVERIFY(!t3.wasSuccessful()); + }); + + t.start(); + QVERIFY2(QTest::qWaitFor([&]() { + return t.isFinished(); + }, 1000), "Task didn't finish as it should."); + } }; QTEST_GUILESS_MAIN(TaskTest) From 064ae49d2b227bbc64c687e2d05fc0554ada6a31 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 28 Aug 2022 15:51:14 -0300 Subject: [PATCH 143/273] fix: make MultipleOptionsTask inherit directly from SequentialTask It's not a good idea to have multiple concurrent tasks running on a sequential thing like this one. Signed-off-by: flow --- launcher/tasks/MultipleOptionsTask.cpp | 2 +- launcher/tasks/MultipleOptionsTask.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/tasks/MultipleOptionsTask.cpp b/launcher/tasks/MultipleOptionsTask.cpp index 7741bef0..5ad6181f 100644 --- a/launcher/tasks/MultipleOptionsTask.cpp +++ b/launcher/tasks/MultipleOptionsTask.cpp @@ -2,7 +2,7 @@ #include -MultipleOptionsTask::MultipleOptionsTask(QObject* parent, const QString& task_name) : ConcurrentTask(parent, task_name) {} +MultipleOptionsTask::MultipleOptionsTask(QObject* parent, const QString& task_name) : SequentialTask(parent, task_name) {} void MultipleOptionsTask::startNext() { diff --git a/launcher/tasks/MultipleOptionsTask.h b/launcher/tasks/MultipleOptionsTask.h index c65356b0..db7d4d9a 100644 --- a/launcher/tasks/MultipleOptionsTask.h +++ b/launcher/tasks/MultipleOptionsTask.h @@ -1,11 +1,11 @@ #pragma once -#include "ConcurrentTask.h" +#include "SequentialTask.h" /* This task type will attempt to do run each of it's subtasks in sequence, * until one of them succeeds. When that happens, the remaining tasks will not run. * */ -class MultipleOptionsTask : public ConcurrentTask { +class MultipleOptionsTask : public SequentialTask { Q_OBJECT public: explicit MultipleOptionsTask(QObject* parent = nullptr, const QString& task_name = ""); From cda2bfc24061870525fa6569f390141e5703a565 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 28 Aug 2022 19:23:04 -0300 Subject: [PATCH 144/273] feat: allow specifying factory for resources in BasicFolderLoadTask This allows us to hook our own resource type, that possibly has more content than the base Resource, to it. Signed-off-by: flow --- .../minecraft/mod/tasks/BasicFolderLoadTask.h | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h b/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h index cc02a9b9..7ea512b3 100644 --- a/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h +++ b/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h @@ -10,32 +10,41 @@ #include "tasks/Task.h" -/** Very simple task that just loads a folder's contents directly. +/** Very simple task that just loads a folder's contents directly. */ -class BasicFolderLoadTask : public Task -{ +class BasicFolderLoadTask : public Task { Q_OBJECT -public: + public: struct Result { QMap resources; }; using ResultPtr = std::shared_ptr; - [[nodiscard]] ResultPtr result() const { - return m_result; - } + [[nodiscard]] ResultPtr result() const { return m_result; } -public: - BasicFolderLoadTask(QDir dir) : Task(nullptr, false), m_dir(dir), m_result(new Result) {} + public: + BasicFolderLoadTask(QDir dir) : Task(nullptr, false), m_dir(dir), m_result(new Result) + { + m_create_func = [](QFileInfo const& entry) -> Resource* { + return new Resource(entry); + }; + } + BasicFolderLoadTask(QDir dir, std::function create_function) + : Task(nullptr, false), m_dir(dir), m_result(new Result), m_create_func(std::move(create_function)) + {} [[nodiscard]] bool canAbort() const override { return true; } - bool abort() override { m_aborted = true; return true; } + bool abort() override + { + m_aborted = true; + return true; + } void executeTask() override { m_dir.refresh(); for (auto entry : m_dir.entryInfoList()) { - auto resource = new Resource(entry); + auto resource = m_create_func(entry); m_result->resources.insert(resource->internal_id(), resource); } @@ -50,4 +59,6 @@ private: ResultPtr m_result; bool m_aborted = false; + + std::function m_create_func; }; From 050768c266833d8ad1c662f93f3c8dc044e57843 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 28 Aug 2022 22:29:52 -0300 Subject: [PATCH 145/273] feat: add more resource pack info Adds pack format id and description to ResourcePack, that'll be parsed from pack.mcmeta. Signed-off-by: flow --- launcher/CMakeLists.txt | 2 ++ launcher/minecraft/mod/Resource.h | 1 + launcher/minecraft/mod/ResourcePack.cpp | 48 +++++++++++++++++++++++++ launcher/minecraft/mod/ResourcePack.h | 37 ++++++++++++++++++- 4 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 launcher/minecraft/mod/ResourcePack.cpp diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 490202cf..7c3c2f28 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -322,6 +322,8 @@ set(MINECRAFT_SOURCES minecraft/mod/Resource.cpp minecraft/mod/ResourceFolderModel.h minecraft/mod/ResourceFolderModel.cpp + minecraft/mod/ResourcePack.h + minecraft/mod/ResourcePack.cpp minecraft/mod/ResourcePackFolderModel.h minecraft/mod/ResourcePackFolderModel.cpp minecraft/mod/TexturePackFolderModel.h diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index cee1f172..96ff9f4b 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -80,6 +80,7 @@ class Resource : public QObject { [[nodiscard]] auto shouldResolve() const -> bool { return !m_is_resolving && !m_is_resolved; } [[nodiscard]] auto isResolving() const -> bool { return m_is_resolving; } + [[nodiscard]] auto isResolved() const -> bool { return m_is_resolved; } [[nodiscard]] auto resolutionTicket() const -> int { return m_resolution_ticket; } void setResolving(bool resolving, int resolutionTicket) diff --git a/launcher/minecraft/mod/ResourcePack.cpp b/launcher/minecraft/mod/ResourcePack.cpp new file mode 100644 index 00000000..68d86aed --- /dev/null +++ b/launcher/minecraft/mod/ResourcePack.cpp @@ -0,0 +1,48 @@ +#include "ResourcePack.h" + +#include +#include + +#include "Version.h" + +// Values taken from: +// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta +static const QMap> s_pack_format_versions = { + { 1, { Version("1.6.1"), Version("1.8.9") } }, { 2, { Version("1.9"), Version("1.10.2") } }, + { 3, { Version("1.11"), Version("1.12.2") } }, { 4, { Version("1.13"), Version("1.14.4") } }, + { 5, { Version("1.15"), Version("1.16.1") } }, { 6, { Version("1.16.2"), Version("1.16.5") } }, + { 7, { Version("1.17"), Version("1.17.1") } }, { 8, { Version("1.18"), Version("1.18.2") } }, + { 9, { Version("1.19"), Version("1.19.2") } }, +}; + +void ResourcePack::setPackFormat(int new_format_id) +{ + QMutexLocker locker(&m_data_lock); + + if (!s_pack_format_versions.contains(new_format_id)) { + qCritical() << "Error: Pack format '%1' is not a recognized resource pack id."; + return; + } + + m_pack_format = new_format_id; +} + +void ResourcePack::setDescription(QString new_description) +{ + QMutexLocker locker(&m_data_lock); + + m_description = new_description; +} + +std::pair ResourcePack::compatibleVersions() const +{ + if (!s_pack_format_versions.contains(m_pack_format)) { + // Not having a valid pack format is fine if we didn't yet parse the .mcmeta file, + // but if we did and we still don't have a valid pack format, that's a bit concerning. + Q_ASSERT(!isResolved()); + + return {{}, {}}; + } + + return s_pack_format_versions.constFind(m_pack_format).value(); +} diff --git a/launcher/minecraft/mod/ResourcePack.h b/launcher/minecraft/mod/ResourcePack.h index c2cc8690..ab84ad37 100644 --- a/launcher/minecraft/mod/ResourcePack.h +++ b/launcher/minecraft/mod/ResourcePack.h @@ -2,12 +2,47 @@ #include "Resource.h" +#include + +class Version; + +/* TODO: + * + * Store pack.png + * Store localized descriptions + * */ + class ResourcePack : public Resource { Q_OBJECT - public: + public: using Ptr = shared_qobject_ptr; ResourcePack(QObject* parent = nullptr) : Resource(parent) {} ResourcePack(QFileInfo file_info) : Resource(file_info) {} + /** Gets the numerical ID of the pack format. */ + [[nodiscard]] int packFormat() const { return m_pack_format; } + /** Gets, respectively, the lower and upper versions supported by the set pack format. */ + [[nodiscard]] std::pair compatibleVersions() const; + + /** Gets the description of the resource pack. */ + [[nodiscard]] QString description() const { return m_description; } + + /** Thread-safe. */ + void setPackFormat(int new_format_id); + + /** Thread-safe. */ + void setDescription(QString new_description); + + protected: + mutable QMutex m_data_lock; + + /* The 'version' of a resource pack, as defined in the pack.mcmeta file. + * See https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta + */ + int m_pack_format = 0; + + /** The resource pack's description, as defined in the pack.mcmeta file. + */ + QString m_description; }; From afa1a5e93228350ea5689b39655046b49a0133a5 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 28 Aug 2022 22:31:48 -0300 Subject: [PATCH 146/273] feat: modify InfoFrame and ResourcePackPage to show ResourcePack info Signed-off-by: flow --- launcher/ui/pages/instance/ResourcePackPage.h | 11 ++ launcher/ui/widgets/InfoFrame.cpp | 104 +++++++++++++++--- launcher/ui/widgets/InfoFrame.h | 1 + 3 files changed, 101 insertions(+), 15 deletions(-) diff --git a/launcher/ui/pages/instance/ResourcePackPage.h b/launcher/ui/pages/instance/ResourcePackPage.h index 2eefc3d3..9633e3b4 100644 --- a/launcher/ui/pages/instance/ResourcePackPage.h +++ b/launcher/ui/pages/instance/ResourcePackPage.h @@ -61,4 +61,15 @@ public: return !m_instance->traits().contains("no-texturepacks") && !m_instance->traits().contains("texturepacks"); } + + public slots: + bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override + { + auto sourceCurrent = m_filterModel->mapToSource(current); + int row = sourceCurrent.row(); + auto& rp = static_cast(m_model->at(row)); + ui->frame->updateWithResourcePack(rp); + + return true; + } }; diff --git a/launcher/ui/widgets/InfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp index 821e61a7..69d77603 100644 --- a/launcher/ui/widgets/InfoFrame.cpp +++ b/launcher/ui/widgets/InfoFrame.cpp @@ -1,17 +1,37 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// SPDX-License-Identifier: GPL-3.0-only +/* +* PolyMC - Minecraft Launcher +* Copyright (c) 2022 flowln +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, version 3. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +* +* This file incorporates work covered by the following copyright and +* permission notice: +* +* Copyright 2013-2021 MultiMC Contributors +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ #include @@ -74,6 +94,60 @@ void InfoFrame::updateWithResource(const Resource& resource) setName(resource.name()); } +// https://www.sportskeeda.com/minecraft-wiki/color-codes +static const QMap s_value_to_color = { + {'0', "#000000"}, {'1', "#0000AA"}, {'2', "#00AA00"}, {'3', "#00AAAA"}, {'4', "#AA0000"}, + {'5', "#AA00AA"}, {'6', "#FFAA00"}, {'7', "#AAAAAA"}, {'8', "#555555"}, {'9', "#5555FF"}, + {'a', "#55FF55"}, {'b', "#55FFFF"}, {'c', "#FF5555"}, {'d', "#FF55FF"}, {'e', "#FFFF55"}, + {'f', "#FFFFFF"} +}; + +void InfoFrame::updateWithResourcePack(const ResourcePack& resource_pack) +{ + setName(resource_pack.name()); + + // We have to manually set the colors for use. + // + // A color is set using §x, with x = a hex number from 0 to f. + // + // We traverse the description and, when one of those is found, we create + // a span element with that color set. + // + // TODO: Make the same logic for font formatting too. + // TODO: Wrap links inside tags + + auto description = resource_pack.description(); + + QString description_parsed(""); + bool in_div = false; + + auto desc_it = description.constBegin(); + while (desc_it != description.constEnd()) { + if (*desc_it == u'§') { + if (in_div) + description_parsed += ""; + + auto const& num = *(++desc_it); + description_parsed += QString("").arg(s_value_to_color.constFind(num).value()); + + in_div = true; + + desc_it++; + } + + description_parsed += *desc_it; + desc_it++; + } + + if (in_div) + description_parsed += ""; + description_parsed += ""; + + description_parsed.replace("\n", "
"); + + setDescription(description_parsed); +} + void InfoFrame::clear() { setName(); @@ -146,7 +220,7 @@ void InfoFrame::setDescription(QString text) } else { - ui->descriptionLabel->setTextFormat(Qt::TextFormat::PlainText); + ui->descriptionLabel->setTextFormat(Qt::TextFormat::AutoText); labeltext.append(finaltext); } ui->descriptionLabel->setText(labeltext); diff --git a/launcher/ui/widgets/InfoFrame.h b/launcher/ui/widgets/InfoFrame.h index d69dc232..4b6df023 100644 --- a/launcher/ui/widgets/InfoFrame.h +++ b/launcher/ui/widgets/InfoFrame.h @@ -39,6 +39,7 @@ class InfoFrame : public QFrame { void updateWithMod(Mod const& m); void updateWithResource(Resource const& resource); + void updateWithResourcePack(ResourcePack const& rp); public slots: void descriptionEllipsisHandler(QString link); From f21ae66265d5e3251bd996df1fc7ed7f52ec95b0 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 28 Aug 2022 22:33:44 -0300 Subject: [PATCH 147/273] feat: add basic resource pack parsing of pack.mcmeta This parses the pack format ID and the description from the local file, from both a ZIP and a folder, and hooks it into the model. Signed-off-by: flow --- launcher/CMakeLists.txt | 2 + launcher/minecraft/mod/ModFolderModel.cpp | 2 +- launcher/minecraft/mod/ModFolderModel.h | 2 +- launcher/minecraft/mod/ResourceFolderModel.h | 2 +- .../minecraft/mod/ResourcePackFolderModel.cpp | 174 ++++++++++++++---- .../minecraft/mod/ResourcePackFolderModel.h | 17 ++ .../mod/tasks/LocalResourcePackParseTask.cpp | 95 ++++++++++ .../mod/tasks/LocalResourcePackParseTask.h | 34 ++++ 8 files changed, 292 insertions(+), 36 deletions(-) create mode 100644 launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp create mode 100644 launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 7c3c2f28..ef8ec681 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -336,6 +336,8 @@ set(MINECRAFT_SOURCES minecraft/mod/tasks/LocalModParseTask.cpp minecraft/mod/tasks/LocalModUpdateTask.h minecraft/mod/tasks/LocalModUpdateTask.cpp + minecraft/mod/tasks/LocalResourcePackParseTask.h + minecraft/mod/tasks/LocalResourcePackParseTask.cpp # Assets minecraft/AssetsUtils.h diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 4e264a74..c014742c 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -156,7 +156,7 @@ Task* ModFolderModel::createUpdateTask() return task; } -Task* ModFolderModel::createParseTask(Resource const& resource) +Task* ModFolderModel::createParseTask(Resource& resource) { return new LocalModParseTask(m_next_resolution_ticket, resource.type(), resource.fileinfo()); } diff --git a/launcher/minecraft/mod/ModFolderModel.h b/launcher/minecraft/mod/ModFolderModel.h index c33195ed..93980319 100644 --- a/launcher/minecraft/mod/ModFolderModel.h +++ b/launcher/minecraft/mod/ModFolderModel.h @@ -82,7 +82,7 @@ public: int columnCount(const QModelIndex &parent) const override; [[nodiscard]] Task* createUpdateTask() override; - [[nodiscard]] Task* createParseTask(Resource const&) override; + [[nodiscard]] Task* createParseTask(Resource&) override; bool installMod(QString file_path) { return ResourceFolderModel::installResource(file_path); } bool uninstallMod(const QString& filename, bool preserve_metadata = false); diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index e27b5db6..a45d1cbd 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -145,7 +145,7 @@ class ResourceFolderModel : public QAbstractListModel { * This task should load and parse all heavy info needed by a resource, such as parsing a manifest. It gets executed * in the background, so it slowly updates the UI as tasks get done. */ - [[nodiscard]] virtual Task* createParseTask(Resource const&) { return nullptr; }; + [[nodiscard]] virtual Task* createParseTask(Resource&) { return nullptr; }; /** Standard implementation of the model update logic. * diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index e92be894..b634130f 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -1,38 +1,146 @@ // SPDX-License-Identifier: GPL-3.0-only /* -* PolyMC - Minecraft Launcher -* Copyright (C) 2022 Sefa Eyeoglu -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, version 3. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see . -* -* This file incorporates work covered by the following copyright and -* permission notice: -* -* Copyright 2013-2021 MultiMC Contributors -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ #include "ResourcePackFolderModel.h" -ResourcePackFolderModel::ResourcePackFolderModel(const QString &dir) : ResourceFolderModel(QDir(dir)) {} +#include "Version.h" + +#include "minecraft/mod/tasks/BasicFolderLoadTask.h" +#include "minecraft/mod/tasks/LocalResourcePackParseTask.h" + +ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir) : ResourceFolderModel(QDir(dir)) +{ + m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::VERSION, SortType::DATE }; +} + +QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const +{ + if (!validateIndex(index)) + return {}; + + int row = index.row(); + int column = index.column(); + + switch (role) { + case Qt::DisplayRole: + switch (column) { + case NameColumn: + return m_resources[row]->name(); + case PackFormatColumn: { + auto version_bounds = at(row)->compatibleVersions(); + if (version_bounds.first.toString().isEmpty()) + return QString::number(at(row)->packFormat()); + + return QString("%1 (%2 - %3)") + .arg(QString::number(at(row)->packFormat()), version_bounds.first.toString(), version_bounds.second.toString()); + } + case DateColumn: + return m_resources[row]->dateTimeChanged(); + + default: + return {}; + } + + case Qt::ToolTipRole: { + if (column == PackFormatColumn) { + //: The string being explained by this is in the format: ID (Lower version - Upper version) + return tr("The resource pack format ID, as well as the Minecraft versions it was designed for."); + } + return m_resources[row]->internal_id(); + } + case Qt::CheckStateRole: + switch (column) { + case ActiveColumn: + return at(row)->enabled() ? Qt::Checked : Qt::Unchecked; + default: + return {}; + } + default: + return {}; + } +} + +QVariant ResourcePackFolderModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + switch (role) { + case Qt::DisplayRole: + switch (section) { + case ActiveColumn: + return QString(); + case NameColumn: + return tr("Name"); + case PackFormatColumn: + return tr("Pack Format"); + case DateColumn: + return tr("Last changed"); + default: + return {}; + } + + case Qt::ToolTipRole: + switch (section) { + case ActiveColumn: + return tr("Is the resource pack enabled? (Only valid for ZIPs)"); + case NameColumn: + return tr("The name of the resource pack."); + case PackFormatColumn: + //: The string being explained by this is in the format: ID (Lower version - Upper version) + return tr("The resource pack format ID, as well as the Minecraft versions it was designed for."); + case DateColumn: + return tr("The date and time this resource pack was last changed (or added)."); + default: + return {}; + } + default: + return {}; + } + return {}; +} + +int ResourcePackFolderModel::columnCount(const QModelIndex& parent) const +{ + return NUM_COLUMNS; +} + +Task* ResourcePackFolderModel::createUpdateTask() +{ + return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return new ResourcePack(entry); }); +} + +Task* ResourcePackFolderModel::createParseTask(Resource& resource) +{ + return new LocalResourcePackParseTask(m_next_resolution_ticket, static_cast(resource)); +} diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.h b/launcher/minecraft/mod/ResourcePackFolderModel.h index 1fe82867..cb620ce2 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.h +++ b/launcher/minecraft/mod/ResourcePackFolderModel.h @@ -8,7 +8,24 @@ class ResourcePackFolderModel : public ResourceFolderModel { Q_OBJECT public: + enum Columns + { + ActiveColumn = 0, + NameColumn, + PackFormatColumn, + DateColumn, + NUM_COLUMNS + }; + explicit ResourcePackFolderModel(const QString &dir); + [[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + + [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; + [[nodiscard]] int columnCount(const QModelIndex &parent) const override; + + [[nodiscard]] Task* createUpdateTask() override; + [[nodiscard]] Task* createParseTask(Resource&) override; + RESOURCE_HELPERS(ResourcePack) }; diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp new file mode 100644 index 00000000..0fc7a87e --- /dev/null +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -0,0 +1,95 @@ +#include "LocalResourcePackParseTask.h" + +#include "FileSystem.h" +#include "Json.h" + +#include +#include + +LocalResourcePackParseTask::LocalResourcePackParseTask(int token, ResourcePack& rp) + : Task(nullptr, false), m_token(token), m_resource_pack(rp) +{} + +bool LocalResourcePackParseTask::abort() +{ + m_aborted = true; + return true; +} + +void LocalResourcePackParseTask::executeTask() +{ + switch (m_resource_pack.type()) { + case ResourceType::FOLDER: + processAsFolder(); + break; + case ResourceType::ZIPFILE: + processAsZip(); + break; + default: + qWarning() << "Invalid type for resource pack parse task!"; + emitFailed(tr("Invalid type.")); + } + + if (isFinished()) + return; + + if (m_aborted) + emitAborted(); + else + emitSucceeded(); +} + +// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta +void LocalResourcePackParseTask::processMCMeta(QByteArray&& raw_data) +{ + try { + auto json_doc = QJsonDocument::fromJson(raw_data); + auto pack_obj = Json::requireObject(json_doc.object(), "pack", {}); + + m_resource_pack.setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0)); + m_resource_pack.setDescription(Json::ensureString(pack_obj, "description", "")); + } catch (Json::JsonException& e) { + qWarning() << "JsonException: " << e.what() << e.cause(); + emitFailed(tr("Failed to process .mcmeta file.")); + } +} + +void LocalResourcePackParseTask::processAsFolder() +{ + QFileInfo mcmeta_file_info(FS::PathCombine(m_resource_pack.fileinfo().filePath(), "pack.mcmeta")); + if (mcmeta_file_info.isFile()) { + QFile mcmeta_file(mcmeta_file_info.filePath()); + if (!mcmeta_file.open(QIODevice::ReadOnly)) + return; + + auto data = mcmeta_file.readAll(); + if (data.isEmpty() || data.isNull()) + return; + + processMCMeta(std::move(data)); + } +} + +void LocalResourcePackParseTask::processAsZip() +{ + QuaZip zip(m_resource_pack.fileinfo().filePath()); + if (!zip.open(QuaZip::mdUnzip)) + return; + + QuaZipFile file(&zip); + + if (zip.setCurrentFile("pack.mcmeta")) { + if (!file.open(QIODevice::ReadOnly)) { + qCritical() << "Failed to open file in zip."; + zip.close(); + return; + } + + auto data = file.readAll(); + + processMCMeta(std::move(data)); + + file.close(); + zip.close(); + } +} diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h new file mode 100644 index 00000000..7660d233 --- /dev/null +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +#include "minecraft/mod/ResourcePack.h" + +#include "tasks/Task.h" + +class LocalResourcePackParseTask : public Task { + Q_OBJECT + public: + LocalResourcePackParseTask(int token, ResourcePack& rp); + + [[nodiscard]] bool canAbort() const override { return true; } + bool abort() override; + + void executeTask() override; + + [[nodiscard]] int token() const { return m_token; } + + private: + void processMCMeta(QByteArray&& raw_data); + + void processAsFolder(); + void processAsZip(); + + private: + int m_token; + + ResourcePack& m_resource_pack; + + bool m_aborted = false; +}; From 3ab17a97a8df6c7dd21ccbb1ea349cf3985fafaf Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 28 Aug 2022 22:52:29 -0300 Subject: [PATCH 148/273] fix: sorting by pack format Signed-off-by: flow --- launcher/minecraft/mod/Resource.h | 1 + launcher/minecraft/mod/ResourcePack.cpp | 25 ++++++++++++++++++- launcher/minecraft/mod/ResourcePack.h | 2 ++ .../minecraft/mod/ResourcePackFolderModel.cpp | 2 +- 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/launcher/minecraft/mod/Resource.h b/launcher/minecraft/mod/Resource.h index 96ff9f4b..f9bd811e 100644 --- a/launcher/minecraft/mod/Resource.h +++ b/launcher/minecraft/mod/Resource.h @@ -20,6 +20,7 @@ enum class SortType { DATE, VERSION, ENABLED, + PACK_FORMAT }; enum class EnableAction { diff --git a/launcher/minecraft/mod/ResourcePack.cpp b/launcher/minecraft/mod/ResourcePack.cpp index 68d86aed..cab20b50 100644 --- a/launcher/minecraft/mod/ResourcePack.cpp +++ b/launcher/minecraft/mod/ResourcePack.cpp @@ -41,8 +41,31 @@ std::pair ResourcePack::compatibleVersions() const // but if we did and we still don't have a valid pack format, that's a bit concerning. Q_ASSERT(!isResolved()); - return {{}, {}}; + return { {}, {} }; } return s_pack_format_versions.constFind(m_pack_format).value(); } + +std::pair ResourcePack::compare(const Resource& other, SortType type) const +{ + auto const& cast_other = static_cast(other); + + switch (type) { + default: { + auto res = Resource::compare(other, type); + if (res.first != 0) + return res; + } + case SortType::PACK_FORMAT: { + auto this_ver = packFormat(); + auto other_ver = cast_other.packFormat(); + + if (this_ver > other_ver) + return { 1, type == SortType::PACK_FORMAT }; + if (this_ver < other_ver) + return { -1, type == SortType::PACK_FORMAT }; + } + } + return { 0, false }; +} diff --git a/launcher/minecraft/mod/ResourcePack.h b/launcher/minecraft/mod/ResourcePack.h index ab84ad37..0bd9ebbd 100644 --- a/launcher/minecraft/mod/ResourcePack.h +++ b/launcher/minecraft/mod/ResourcePack.h @@ -34,6 +34,8 @@ class ResourcePack : public Resource { /** Thread-safe. */ void setDescription(QString new_description); + [[nodiscard]] auto compare(Resource const& other, SortType type) const -> std::pair override; + protected: mutable QMutex m_data_lock; diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index b634130f..66b6cf5e 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -43,7 +43,7 @@ ResourcePackFolderModel::ResourcePackFolderModel(const QString& dir) : ResourceFolderModel(QDir(dir)) { - m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::VERSION, SortType::DATE }; + m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::PACK_FORMAT, SortType::DATE }; } QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const From 6a93688b2e46f051ff80268783c6a8e1a2d7245f Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 28 Aug 2022 22:58:43 -0300 Subject: [PATCH 149/273] fix: filtering in regex search in resource packs Signed-off-by: flow --- launcher/minecraft/mod/ResourcePack.cpp | 17 +++++++++++++++++ launcher/minecraft/mod/ResourcePack.h | 1 + 2 files changed, 18 insertions(+) diff --git a/launcher/minecraft/mod/ResourcePack.cpp b/launcher/minecraft/mod/ResourcePack.cpp index cab20b50..5f45254d 100644 --- a/launcher/minecraft/mod/ResourcePack.cpp +++ b/launcher/minecraft/mod/ResourcePack.cpp @@ -2,6 +2,7 @@ #include #include +#include #include "Version.h" @@ -69,3 +70,19 @@ std::pair ResourcePack::compare(const Resource& other, SortType type) } return { 0, false }; } + +bool ResourcePack::applyFilter(QRegularExpression filter) const +{ + if (filter.match(description()).hasMatch()) + return true; + + if (filter.match(QString::number(packFormat())).hasMatch()) + return true; + + if (filter.match(compatibleVersions().first.toString()).hasMatch()) + return true; + if (filter.match(compatibleVersions().second.toString()).hasMatch()) + return true; + + return Resource::applyFilter(filter); +} diff --git a/launcher/minecraft/mod/ResourcePack.h b/launcher/minecraft/mod/ResourcePack.h index 0bd9ebbd..17de86a7 100644 --- a/launcher/minecraft/mod/ResourcePack.h +++ b/launcher/minecraft/mod/ResourcePack.h @@ -35,6 +35,7 @@ class ResourcePack : public Resource { void setDescription(QString new_description); [[nodiscard]] auto compare(Resource const& other, SortType type) const -> std::pair override; + [[nodiscard]] bool applyFilter(QRegularExpression filter) const override; protected: mutable QMutex m_data_lock; From dd9e30b24ab20c6e559618f435b141383e0d0fcd Mon Sep 17 00:00:00 2001 From: flow Date: Mon, 29 Aug 2022 09:14:15 -0300 Subject: [PATCH 150/273] feat: add resource icon to InfoFrame Signed-off-by: flow --- launcher/ui/widgets/InfoFrame.cpp | 15 ++++++++++++++ launcher/ui/widgets/InfoFrame.h | 1 + launcher/ui/widgets/InfoFrame.ui | 34 +++++++++++++++++++++++++------ 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/launcher/ui/widgets/InfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp index 69d77603..97d0ba37 100644 --- a/launcher/ui/widgets/InfoFrame.cpp +++ b/launcher/ui/widgets/InfoFrame.cpp @@ -87,11 +87,14 @@ void InfoFrame::updateWithMod(Mod const& m) { setDescription(m.description()); } + + setImage(); } void InfoFrame::updateWithResource(const Resource& resource) { setName(resource.name()); + setImage(); } // https://www.sportskeeda.com/minecraft-wiki/color-codes @@ -146,12 +149,14 @@ void InfoFrame::updateWithResourcePack(const ResourcePack& resource_pack) description_parsed.replace("\n", "
"); setDescription(description_parsed); + setImage(resource_pack.image({64, 64})); } void InfoFrame::clear() { setName(); setDescription(); + setImage(); } void InfoFrame::updateHiddenState() @@ -226,6 +231,16 @@ void InfoFrame::setDescription(QString text) ui->descriptionLabel->setText(labeltext); } +void InfoFrame::setImage(QPixmap img) +{ + if (img.isNull()) { + ui->iconLabel->setHidden(true); + } else { + ui->iconLabel->setHidden(false); + ui->iconLabel->setPixmap(img); + } +} + void InfoFrame::descriptionEllipsisHandler(QString link) { if(!m_current_box) diff --git a/launcher/ui/widgets/InfoFrame.h b/launcher/ui/widgets/InfoFrame.h index 4b6df023..d914aa0c 100644 --- a/launcher/ui/widgets/InfoFrame.h +++ b/launcher/ui/widgets/InfoFrame.h @@ -34,6 +34,7 @@ class InfoFrame : public QFrame { void setName(QString text = {}); void setDescription(QString text = {}); + void setImage(QPixmap img = {}); void clear(); diff --git a/launcher/ui/widgets/InfoFrame.ui b/launcher/ui/widgets/InfoFrame.ui index 0d3772d7..9e407ce9 100644 --- a/launcher/ui/widgets/InfoFrame.ui +++ b/launcher/ui/widgets/InfoFrame.ui @@ -22,10 +22,7 @@ 120
- - - 6 - + 0 @@ -38,7 +35,7 @@ 0 - + @@ -60,7 +57,7 @@ - + @@ -85,6 +82,31 @@ + + + + + 0 + 0 + + + + + 64 + 64 + + + + + + + false + + + 0 + + +
From 9b984cedaca78153bc865a3eac5def4535b4d664 Mon Sep 17 00:00:00 2001 From: flow Date: Mon, 29 Aug 2022 09:15:06 -0300 Subject: [PATCH 151/273] feat: add image from pack.png to resource packs Signed-off-by: flow --- launcher/minecraft/mod/ResourcePack.cpp | 9 ++++ launcher/minecraft/mod/ResourcePack.h | 14 ++++++ .../mod/tasks/LocalResourcePackParseTask.cpp | 44 +++++++++++++++++-- .../mod/tasks/LocalResourcePackParseTask.h | 1 + 4 files changed, 65 insertions(+), 3 deletions(-) diff --git a/launcher/minecraft/mod/ResourcePack.cpp b/launcher/minecraft/mod/ResourcePack.cpp index 5f45254d..77bd98aa 100644 --- a/launcher/minecraft/mod/ResourcePack.cpp +++ b/launcher/minecraft/mod/ResourcePack.cpp @@ -35,6 +35,15 @@ void ResourcePack::setDescription(QString new_description) m_description = new_description; } +void ResourcePack::setImage(QImage new_image) +{ + QMutexLocker locker(&m_data_lock); + + Q_ASSERT(!new_image.isNull()); + + m_pack_image = new_image; +} + std::pair ResourcePack::compatibleVersions() const { if (!s_pack_format_versions.contains(m_pack_format)) { diff --git a/launcher/minecraft/mod/ResourcePack.h b/launcher/minecraft/mod/ResourcePack.h index 17de86a7..1a51d9ef 100644 --- a/launcher/minecraft/mod/ResourcePack.h +++ b/launcher/minecraft/mod/ResourcePack.h @@ -2,7 +2,9 @@ #include "Resource.h" +#include #include +#include class Version; @@ -28,12 +30,19 @@ class ResourcePack : public Resource { /** Gets the description of the resource pack. */ [[nodiscard]] QString description() const { return m_description; } + /** Gets the image of the resource pack, converted to a QPixmap for drawing, and scaled to size. */ + [[nodiscard]] QPixmap image(QSize size) const { return QPixmap::fromImage(m_pack_image).scaled(size); } + [[nodiscard]] QSize image_size() const { return m_pack_image.size(); } + /** Thread-safe. */ void setPackFormat(int new_format_id); /** Thread-safe. */ void setDescription(QString new_description); + /** Thread-safe. */ + void setImage(QImage new_image); + [[nodiscard]] auto compare(Resource const& other, SortType type) const -> std::pair override; [[nodiscard]] bool applyFilter(QRegularExpression filter) const override; @@ -48,4 +57,9 @@ class ResourcePack : public Resource { /** The resource pack's description, as defined in the pack.mcmeta file. */ QString m_description; + + /** The resource pack's image, as per the pack.png file. + * TODO: This could probably be just a key into a static image cache. + */ + QImage m_pack_image; }; diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index 0fc7a87e..b13e5775 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -54,6 +54,16 @@ void LocalResourcePackParseTask::processMCMeta(QByteArray&& raw_data) } } +void LocalResourcePackParseTask::processPackPNG(QByteArray&& raw_data) +{ + auto img = QImage::fromData(raw_data); + if (!img.isNull()) { + m_resource_pack.setImage(img); + } else { + qWarning() << "Failed to parse pack.png."; + } +} + void LocalResourcePackParseTask::processAsFolder() { QFileInfo mcmeta_file_info(FS::PathCombine(m_resource_pack.fileinfo().filePath(), "pack.mcmeta")); @@ -63,10 +73,23 @@ void LocalResourcePackParseTask::processAsFolder() return; auto data = mcmeta_file.readAll(); - if (data.isEmpty() || data.isNull()) - return; processMCMeta(std::move(data)); + + mcmeta_file.close(); + } + + QFileInfo image_file_info(FS::PathCombine(m_resource_pack.fileinfo().filePath(), "pack.png")); + if (image_file_info.isFile()) { + QFile mcmeta_file(image_file_info.filePath()); + if (!mcmeta_file.open(QIODevice::ReadOnly)) + return; + + auto data = mcmeta_file.readAll(); + + processPackPNG(std::move(data)); + + mcmeta_file.close(); } } @@ -90,6 +113,21 @@ void LocalResourcePackParseTask::processAsZip() processMCMeta(std::move(data)); file.close(); - zip.close(); } + + if (zip.setCurrentFile("pack.png")) { + if (!file.open(QIODevice::ReadOnly)) { + qCritical() << "Failed to open file in zip."; + zip.close(); + return; + } + + auto data = file.readAll(); + + processPackPNG(std::move(data)); + + file.close(); + } + + zip.close(); } diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h index 7660d233..86ea033d 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h @@ -21,6 +21,7 @@ class LocalResourcePackParseTask : public Task { private: void processMCMeta(QByteArray&& raw_data); + void processPackPNG(QByteArray&& raw_data); void processAsFolder(); void processAsZip(); From 8a7e117f6b013972be8fc05480339e11b7325e3e Mon Sep 17 00:00:00 2001 From: flow Date: Mon, 29 Aug 2022 09:25:39 -0300 Subject: [PATCH 152/273] refactor: move resource pack file parsing utilities to separate namespace This makes it easier to use that logic in other contexts. Signed-off-by: flow --- launcher/minecraft/mod/ResourcePack.h | 1 - .../mod/tasks/LocalResourcePackParseTask.cpp | 143 +++++++++++------- .../mod/tasks/LocalResourcePackParseTask.h | 35 ++++- 3 files changed, 114 insertions(+), 65 deletions(-) diff --git a/launcher/minecraft/mod/ResourcePack.h b/launcher/minecraft/mod/ResourcePack.h index 1a51d9ef..720fa3a0 100644 --- a/launcher/minecraft/mod/ResourcePack.h +++ b/launcher/minecraft/mod/ResourcePack.h @@ -10,7 +10,6 @@ class Version; /* TODO: * - * Store pack.png * Store localized descriptions * */ diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index b13e5775..4634e08b 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -1,3 +1,21 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "LocalResourcePackParseTask.h" #include "FileSystem.h" @@ -6,67 +24,28 @@ #include #include -LocalResourcePackParseTask::LocalResourcePackParseTask(int token, ResourcePack& rp) - : Task(nullptr, false), m_token(token), m_resource_pack(rp) -{} +namespace ResourcePackUtils { -bool LocalResourcePackParseTask::abort() +bool process(ResourcePack& pack) { - m_aborted = true; - return true; -} - -void LocalResourcePackParseTask::executeTask() -{ - switch (m_resource_pack.type()) { + switch (pack.type()) { case ResourceType::FOLDER: - processAsFolder(); - break; + ResourcePackUtils::processFolder(pack); + return true; case ResourceType::ZIPFILE: - processAsZip(); - break; + ResourcePackUtils::processZIP(pack); + return true; default: qWarning() << "Invalid type for resource pack parse task!"; - emitFailed(tr("Invalid type.")); - } - - if (isFinished()) - return; - - if (m_aborted) - emitAborted(); - else - emitSucceeded(); -} - -// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta -void LocalResourcePackParseTask::processMCMeta(QByteArray&& raw_data) -{ - try { - auto json_doc = QJsonDocument::fromJson(raw_data); - auto pack_obj = Json::requireObject(json_doc.object(), "pack", {}); - - m_resource_pack.setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0)); - m_resource_pack.setDescription(Json::ensureString(pack_obj, "description", "")); - } catch (Json::JsonException& e) { - qWarning() << "JsonException: " << e.what() << e.cause(); - emitFailed(tr("Failed to process .mcmeta file.")); + return false; } } -void LocalResourcePackParseTask::processPackPNG(QByteArray&& raw_data) +void processFolder(ResourcePack& pack) { - auto img = QImage::fromData(raw_data); - if (!img.isNull()) { - m_resource_pack.setImage(img); - } else { - qWarning() << "Failed to parse pack.png."; - } -} + Q_ASSERT(pack.type() == ResourceType::FOLDER); -void LocalResourcePackParseTask::processAsFolder() -{ - QFileInfo mcmeta_file_info(FS::PathCombine(m_resource_pack.fileinfo().filePath(), "pack.mcmeta")); + QFileInfo mcmeta_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.mcmeta")); if (mcmeta_file_info.isFile()) { QFile mcmeta_file(mcmeta_file_info.filePath()); if (!mcmeta_file.open(QIODevice::ReadOnly)) @@ -74,12 +53,12 @@ void LocalResourcePackParseTask::processAsFolder() auto data = mcmeta_file.readAll(); - processMCMeta(std::move(data)); + ResourcePackUtils::processMCMeta(pack, std::move(data)); mcmeta_file.close(); } - QFileInfo image_file_info(FS::PathCombine(m_resource_pack.fileinfo().filePath(), "pack.png")); + QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png")); if (image_file_info.isFile()) { QFile mcmeta_file(image_file_info.filePath()); if (!mcmeta_file.open(QIODevice::ReadOnly)) @@ -87,15 +66,17 @@ void LocalResourcePackParseTask::processAsFolder() auto data = mcmeta_file.readAll(); - processPackPNG(std::move(data)); + ResourcePackUtils::processPackPNG(pack, std::move(data)); mcmeta_file.close(); } } -void LocalResourcePackParseTask::processAsZip() +void processZIP(ResourcePack& pack) { - QuaZip zip(m_resource_pack.fileinfo().filePath()); + Q_ASSERT(pack.type() == ResourceType::ZIPFILE); + + QuaZip zip(pack.fileinfo().filePath()); if (!zip.open(QuaZip::mdUnzip)) return; @@ -110,7 +91,7 @@ void LocalResourcePackParseTask::processAsZip() auto data = file.readAll(); - processMCMeta(std::move(data)); + ResourcePackUtils::processMCMeta(pack, std::move(data)); file.close(); } @@ -124,10 +105,58 @@ void LocalResourcePackParseTask::processAsZip() auto data = file.readAll(); - processPackPNG(std::move(data)); + ResourcePackUtils::processPackPNG(pack, std::move(data)); file.close(); } zip.close(); } + +// https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta +void processMCMeta(ResourcePack& pack, QByteArray&& raw_data) +{ + try { + auto json_doc = QJsonDocument::fromJson(raw_data); + auto pack_obj = Json::requireObject(json_doc.object(), "pack", {}); + + pack.setPackFormat(Json::ensureInteger(pack_obj, "pack_format", 0)); + pack.setDescription(Json::ensureString(pack_obj, "description", "")); + } catch (Json::JsonException& e) { + qWarning() << "JsonException: " << e.what() << e.cause(); + } +} + +void processPackPNG(ResourcePack& pack, QByteArray&& raw_data) +{ + auto img = QImage::fromData(raw_data); + if (!img.isNull()) { + pack.setImage(img); + } else { + qWarning() << "Failed to parse pack.png."; + } +} +} // namespace ResourcePackUtils + +LocalResourcePackParseTask::LocalResourcePackParseTask(int token, ResourcePack& rp) + : Task(nullptr, false), m_token(token), m_resource_pack(rp) +{} + +bool LocalResourcePackParseTask::abort() +{ + m_aborted = true; + return true; +} + +void LocalResourcePackParseTask::executeTask() +{ + Q_ASSERT(m_resource_pack.valid()); + + if (!ResourcePackUtils::process(m_resource_pack)) + return; + + if (m_aborted) + emitAborted(); + else + emitSucceeded(); +} diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h index 86ea033d..d3c25464 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.h @@ -1,3 +1,21 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #pragma once #include @@ -7,6 +25,16 @@ #include "tasks/Task.h" +namespace ResourcePackUtils { +bool process(ResourcePack& pack); + +void processZIP(ResourcePack& pack); +void processFolder(ResourcePack& pack); + +void processMCMeta(ResourcePack& pack, QByteArray&& raw_data); +void processPackPNG(ResourcePack& pack, QByteArray&& raw_data); +} // namespace ResourcePackUtils + class LocalResourcePackParseTask : public Task { Q_OBJECT public: @@ -19,13 +47,6 @@ class LocalResourcePackParseTask : public Task { [[nodiscard]] int token() const { return m_token; } - private: - void processMCMeta(QByteArray&& raw_data); - void processPackPNG(QByteArray&& raw_data); - - void processAsFolder(); - void processAsZip(); - private: int m_token; From 0331f5a1eb3e9fa21e89fc7fd56fdd4e87f29e44 Mon Sep 17 00:00:00 2001 From: flow Date: Tue, 30 Aug 2022 14:55:37 -0300 Subject: [PATCH 153/273] feat(tests): add tests for resource pack parsing Signed-off-by: flow --- launcher/CMakeLists.txt | 4 + .../mod/ResourceFolderModel_test.cpp | 3 +- .../minecraft/mod/ResourcePackParse_test.cpp | 73 ++++++++++++++++++ .../testdata/another_test_folder/pack.mcmeta | Bin 0 -> 151 bytes .../mod/testdata/test_resource_pack_idk.zip | Bin 0 -> 322 bytes 5 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 launcher/minecraft/mod/ResourcePackParse_test.cpp create mode 100644 launcher/minecraft/mod/testdata/another_test_folder/pack.mcmeta create mode 100644 launcher/minecraft/mod/testdata/test_resource_pack_idk.zip diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index ef8ec681..234ff454 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -388,6 +388,10 @@ ecm_add_test(minecraft/Library_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VER ecm_add_test(minecraft/mod/ResourceFolderModel_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME ResourceFolderModel) +ecm_add_test(minecraft/mod/ResourcePackParse_test.cpp + LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test + TEST_NAME ResourcePackParse) + ecm_add_test(minecraft/ParseUtils_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME ParseUtils) diff --git a/launcher/minecraft/mod/ResourceFolderModel_test.cpp b/launcher/minecraft/mod/ResourceFolderModel_test.cpp index fe98552e..8fe93c2a 100644 --- a/launcher/minecraft/mod/ResourceFolderModel_test.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel_test.cpp @@ -146,7 +146,8 @@ slots: for (auto mod : model.allMods()) qDebug() << mod->name(); - QCOMPARE(model.size(), 2); + // FIXME: It considers every file in the directory as a mod, but we should probably filter that out somehow. + QCOMPARE(model.size(), 4); model.stopWatching(); diff --git a/launcher/minecraft/mod/ResourcePackParse_test.cpp b/launcher/minecraft/mod/ResourcePackParse_test.cpp new file mode 100644 index 00000000..a49582d6 --- /dev/null +++ b/launcher/minecraft/mod/ResourcePackParse_test.cpp @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "FileSystem.h" + +#include "ResourcePack.h" +#include "tasks/LocalResourcePackParseTask.h" + +class ResourcePackParseTest : public QObject { + Q_OBJECT + + private slots: + void test_parseZIP() + { + QString source = QFINDTESTDATA("testdata"); + + QString zip_rp = FS::PathCombine(source, "test_resource_pack_idk.zip"); + ResourcePack pack { QFileInfo(zip_rp) }; + + ResourcePackUtils::processZIP(pack); + + QVERIFY(pack.packFormat() == 3); + QVERIFY(pack.description() == "um dois, feijão com arroz, três quatro, feijão no prato, cinco seis, café inglês, sete oito, comer biscoito, nove dez comer pastéis!!"); + } + + void test_parseFolder() + { + QString source = QFINDTESTDATA("testdata"); + + QString folder_rp = FS::PathCombine(source, "test_folder"); + ResourcePack pack { QFileInfo(folder_rp) }; + + ResourcePackUtils::processFolder(pack); + + QVERIFY(pack.packFormat() == 1); + QVERIFY(pack.description() == "Some resource pack maybe"); + } + + void test_parseFolder2() + { + QString source = QFINDTESTDATA("testdata"); + + QString folder_rp = FS::PathCombine(source, "another_test_folder"); + ResourcePack pack { QFileInfo(folder_rp) }; + + ResourcePackUtils::process(pack); + + QVERIFY(pack.packFormat() == 6); + QVERIFY(pack.description() == "o quartel pegou fogo, policia deu sinal, acode acode acode a bandeira nacional"); + } +}; + +QTEST_GUILESS_MAIN(ResourcePackParseTest) + +#include "ResourcePackParse_test.moc" diff --git a/launcher/minecraft/mod/testdata/another_test_folder/pack.mcmeta b/launcher/minecraft/mod/testdata/another_test_folder/pack.mcmeta new file mode 100644 index 0000000000000000000000000000000000000000..d33a0e5dba992dbc1066080c0e98a90ae4c79501 GIT binary patch literal 151 zcmY+7u?hk)5JdZf`-dTQY72ix*3I6r7_%{#RN|Le_(}ePdtxID1M?VW-p|*4o`6t^ zFX4!9_{Gy=?MmJ0A?_W=Nj7d;yIOG^47Wk8OGcH|1{PZ_qFPo~LXv?^3*`u6OY-kH TQ%RzYP>6kxa@bEE^W^#fD}OJ< literal 0 HcmV?d00001 diff --git a/launcher/minecraft/mod/testdata/test_resource_pack_idk.zip b/launcher/minecraft/mod/testdata/test_resource_pack_idk.zip new file mode 100644 index 0000000000000000000000000000000000000000..52b91cdcfc6e2dbb0ecb76c5cb33d07b0184487d GIT binary patch literal 322 zcmWIWW@Zs#U|`^2FfNk|z0d8=Jq^e^4aD3GG7JTY$=Q0j$+@W|iJ>8!49pvS`I14n zw1S&~k>w>b0|S_F?K9*%WWeK^uJx5K?^#Yx3|p&-E1M9zb!x`Z%o(Rliwu3bTfVga zpZ|Zl&GxhBQl77W#<2O9t_=I57hRk7#wN-iF@DxGMP!ztlfQ;oTf_8UN*_cH8eUFW zSWwG-$31+;zgCwCH$_$DCaX{QFt^(L3ai1~{9PNlycrH{bu~1Y6Yyu6ur1S<#9y+T zCI^X6S!ev~{^r~e`2cT5COKwYo|6E2k%55`h?g{iSSVg)g?JUsn*rXeY#^nKKo|(5 IZ-Y1t0BR& Date: Tue, 30 Aug 2022 18:06:13 -0300 Subject: [PATCH 154/273] feat: move resource pack images to QPixmapCache This takes care of evicting entries when the cache gets too big for us, so we can add new entries without much worries. Signed-off-by: flow --- launcher/minecraft/mod/ResourcePack.cpp | 26 ++++++++++++++++++- launcher/minecraft/mod/ResourcePack.h | 15 +++++++---- .../mod/tasks/LocalResourcePackParseTask.cpp | 2 ++ launcher/ui/widgets/InfoFrame.cpp | 2 +- launcher/ui/widgets/InfoFrame.h | 2 +- 5 files changed, 39 insertions(+), 8 deletions(-) diff --git a/launcher/minecraft/mod/ResourcePack.cpp b/launcher/minecraft/mod/ResourcePack.cpp index 77bd98aa..cc8d23ce 100644 --- a/launcher/minecraft/mod/ResourcePack.cpp +++ b/launcher/minecraft/mod/ResourcePack.cpp @@ -6,6 +6,8 @@ #include "Version.h" +#include "minecraft/mod/tasks/LocalResourcePackParseTask.h" + // Values taken from: // https://minecraft.fandom.com/wiki/Tutorials/Creating_a_resource_pack#Formatting_pack.mcmeta static const QMap> s_pack_format_versions = { @@ -41,7 +43,29 @@ void ResourcePack::setImage(QImage new_image) Q_ASSERT(!new_image.isNull()); - m_pack_image = new_image; + if (m_pack_image_cache_key.key.isValid()) + QPixmapCache::remove(m_pack_image_cache_key.key); + + m_pack_image_cache_key.key = QPixmapCache::insert(QPixmap::fromImage(new_image)); + m_pack_image_cache_key.was_ever_used = true; +} + +QPixmap ResourcePack::image(QSize size) +{ + QPixmap cached_image; + if (QPixmapCache::find(m_pack_image_cache_key.key, &cached_image)) { + if (size.isNull()) + return cached_image; + return cached_image.scaled(size); + } + + // No valid image we can get + if (!m_pack_image_cache_key.was_ever_used) + return {}; + + // Imaged got evicted from the cache. Re-process it and retry. + ResourcePackUtils::process(*this); + return image(size); } std::pair ResourcePack::compatibleVersions() const diff --git a/launcher/minecraft/mod/ResourcePack.h b/launcher/minecraft/mod/ResourcePack.h index 720fa3a0..03121908 100644 --- a/launcher/minecraft/mod/ResourcePack.h +++ b/launcher/minecraft/mod/ResourcePack.h @@ -5,6 +5,7 @@ #include #include #include +#include class Version; @@ -30,8 +31,7 @@ class ResourcePack : public Resource { [[nodiscard]] QString description() const { return m_description; } /** Gets the image of the resource pack, converted to a QPixmap for drawing, and scaled to size. */ - [[nodiscard]] QPixmap image(QSize size) const { return QPixmap::fromImage(m_pack_image).scaled(size); } - [[nodiscard]] QSize image_size() const { return m_pack_image.size(); } + [[nodiscard]] QPixmap image(QSize size); /** Thread-safe. */ void setPackFormat(int new_format_id); @@ -57,8 +57,13 @@ class ResourcePack : public Resource { */ QString m_description; - /** The resource pack's image, as per the pack.png file. - * TODO: This could probably be just a key into a static image cache. + /** The resource pack's image file cache key, for access in the QPixmapCache global instance. + * + * The 'was_ever_used' state simply identifies whether the key was never inserted on the cache (true), + * so as to tell whether a cache entry is inexistent or if it was just evicted from the cache. */ - QImage m_pack_image; + struct { + QPixmapCache::Key key; + bool was_ever_used = false; + } m_pack_image_cache_key; }; diff --git a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp index 4634e08b..4f87bc13 100644 --- a/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalResourcePackParseTask.cpp @@ -24,6 +24,8 @@ #include #include +#include + namespace ResourcePackUtils { bool process(ResourcePack& pack) diff --git a/launcher/ui/widgets/InfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp index 97d0ba37..9e0553f8 100644 --- a/launcher/ui/widgets/InfoFrame.cpp +++ b/launcher/ui/widgets/InfoFrame.cpp @@ -105,7 +105,7 @@ static const QMap s_value_to_color = { {'f', "#FFFFFF"} }; -void InfoFrame::updateWithResourcePack(const ResourcePack& resource_pack) +void InfoFrame::updateWithResourcePack(ResourcePack& resource_pack) { setName(resource_pack.name()); diff --git a/launcher/ui/widgets/InfoFrame.h b/launcher/ui/widgets/InfoFrame.h index d914aa0c..70d15b1e 100644 --- a/launcher/ui/widgets/InfoFrame.h +++ b/launcher/ui/widgets/InfoFrame.h @@ -40,7 +40,7 @@ class InfoFrame : public QFrame { void updateWithMod(Mod const& m); void updateWithResource(Resource const& resource); - void updateWithResourcePack(ResourcePack const& rp); + void updateWithResourcePack(ResourcePack& rp); public slots: void descriptionEllipsisHandler(QString link); From 42c81395b362b9a080dca3f9d62e33c547cb8259 Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 3 Sep 2022 13:25:05 -0300 Subject: [PATCH 155/273] fix: race condition on ResourceFolderModel tests This (hopefully) fixes the race contiditions that sometimes got triggered in tests. Signed-off-by: flow --- launcher/minecraft/mod/ModFolderModel.cpp | 11 +----- .../minecraft/mod/ResourceFolderModel.cpp | 36 ++++++++++--------- launcher/minecraft/mod/ResourceFolderModel.h | 4 +-- .../mod/ResourceFolderModel_test.cpp | 17 +-------- .../minecraft/mod/tasks/BasicFolderLoadTask.h | 6 ++-- .../minecraft/mod/tasks/LocalModParseTask.cpp | 4 +-- .../minecraft/mod/tasks/LocalModParseTask.h | 2 +- .../minecraft/mod/tasks/ModFolderLoadTask.cpp | 9 +++-- .../minecraft/mod/tasks/ModFolderLoadTask.h | 12 ++++++- 9 files changed, 46 insertions(+), 55 deletions(-) diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index c014742c..13fed1c9 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -151,7 +151,7 @@ int ModFolderModel::columnCount(const QModelIndex &parent) const Task* ModFolderModel::createUpdateTask() { auto index_dir = indexDir(); - auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load, this); + auto task = new ModFolderLoadTask(dir(), index_dir, m_is_indexed, m_first_folder_load); m_first_folder_load = false; return task; } @@ -259,15 +259,6 @@ void ModFolderModel::onUpdateSucceeded() #endif applyUpdates(current_set, new_set, new_mods); - - m_current_update_task.reset(); - - if (m_scheduled_update) { - m_scheduled_update = false; - update(); - } else { - emit updateFinished(); - } } void ModFolderModel::onParseSucceeded(int ticket, QString mod_id) diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index bc18ddc2..45d1db59 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -1,5 +1,6 @@ #include "ResourceFolderModel.h" +#include #include #include #include @@ -19,6 +20,12 @@ ResourceFolderModel::ResourceFolderModel(QDir dir, QObject* parent) : QAbstractL connect(&m_watcher, &QFileSystemWatcher::directoryChanged, this, &ResourceFolderModel::directoryChanged); } +ResourceFolderModel::~ResourceFolderModel() +{ + while (!QThreadPool::globalInstance()->waitForDone(100)) + QCoreApplication::processEvents(); +} + bool ResourceFolderModel::startWatching(const QStringList paths) { if (m_is_watching) @@ -229,9 +236,17 @@ bool ResourceFolderModel::update() connect(m_current_update_task.get(), &Task::succeeded, this, &ResourceFolderModel::onUpdateSucceeded, Qt::ConnectionType::QueuedConnection); connect(m_current_update_task.get(), &Task::failed, this, &ResourceFolderModel::onUpdateFailed, Qt::ConnectionType::QueuedConnection); + connect(m_current_update_task.get(), &Task::finished, this, [=] { + m_current_update_task.reset(); + if (m_scheduled_update) { + m_scheduled_update = false; + update(); + } else { + emit updateFinished(); + } + }, Qt::ConnectionType::QueuedConnection); - auto* thread_pool = QThreadPool::globalInstance(); - thread_pool->start(m_current_update_task.get()); + QThreadPool::globalInstance()->start(m_current_update_task.get()); return true; } @@ -246,10 +261,7 @@ void ResourceFolderModel::resolveResource(Resource::Ptr res) if (!task) return; - m_ticket_mutex.lock(); - int ticket = m_next_resolution_ticket; - m_next_resolution_ticket += 1; - m_ticket_mutex.unlock(); + int ticket = m_next_resolution_ticket.fetch_add(1); res->setResolving(true, ticket); m_active_parse_tasks.insert(ticket, task); @@ -261,8 +273,7 @@ void ResourceFolderModel::resolveResource(Resource::Ptr res) connect( task, &Task::finished, this, [=] { m_active_parse_tasks.remove(ticket); }, Qt::ConnectionType::QueuedConnection); - auto* thread_pool = QThreadPool::globalInstance(); - thread_pool->start(task); + QThreadPool::globalInstance()->start(task); } void ResourceFolderModel::onUpdateSucceeded() @@ -283,15 +294,6 @@ void ResourceFolderModel::onUpdateSucceeded() #endif applyUpdates(current_set, new_set, new_resources); - - m_current_update_task.reset(); - - if (m_scheduled_update) { - m_scheduled_update = false; - update(); - } else { - emit updateFinished(); - } } void ResourceFolderModel::onParseSucceeded(int ticket, QString resource_id) diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index a45d1cbd..d763bec3 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -24,6 +24,7 @@ class ResourceFolderModel : public QAbstractListModel { Q_OBJECT public: ResourceFolderModel(QDir, QObject* parent = nullptr); + ~ResourceFolderModel() override; /** Starts watching the paths for changes. * @@ -197,8 +198,7 @@ class ResourceFolderModel : public QAbstractListModel { QMap m_resources_index; QMap m_active_parse_tasks; - int m_next_resolution_ticket = 0; - QMutex m_ticket_mutex; + std::atomic m_next_resolution_ticket = 0; }; /* A macro to define useful functions to handle Resource* -> T* more easily on derived classes */ diff --git a/launcher/minecraft/mod/ResourceFolderModel_test.cpp b/launcher/minecraft/mod/ResourceFolderModel_test.cpp index 8fe93c2a..aa78e502 100644 --- a/launcher/minecraft/mod/ResourceFolderModel_test.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel_test.cpp @@ -58,7 +58,7 @@ QVERIFY2(expire_timer.isActive(), "Timer has expired. The update never finished."); \ expire_timer.stop(); \ \ - disconnect(&model, nullptr, nullptr, nullptr); + disconnect(&model, nullptr, &loop, nullptr); class ResourceFolderModelTest : public QObject { @@ -150,11 +150,6 @@ slots: QCOMPARE(model.size(), 4); model.stopWatching(); - - while (model.hasPendingParseTasks()) { - QTest::qSleep(20); - QCoreApplication::processEvents(); - } } void test_removeResource() @@ -207,11 +202,6 @@ slots: qDebug() << "Removed second mod."; model.stopWatching(); - - while (model.hasPendingParseTasks()) { - QTest::qSleep(20); - QCoreApplication::processEvents(); - } } void test_enable_disable() @@ -263,11 +253,6 @@ slots: QVERIFY(!res_2.enable(initial_enabled_res_2 ? EnableAction::ENABLE : EnableAction::DISABLE)); QVERIFY(res_2.enabled() == initial_enabled_res_2); QVERIFY(res_2.internal_id() == id_2); - - while (model.hasPendingParseTasks()) { - QTest::qSleep(20); - QCoreApplication::processEvents(); - } } }; diff --git a/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h b/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h index 7ea512b3..be0e752d 100644 --- a/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h +++ b/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h @@ -36,7 +36,7 @@ class BasicFolderLoadTask : public Task { [[nodiscard]] bool canAbort() const override { return true; } bool abort() override { - m_aborted = true; + m_aborted.store(true); return true; } @@ -49,7 +49,7 @@ class BasicFolderLoadTask : public Task { } if (m_aborted) - emitAborted(); + emit finished(); else emitSucceeded(); } @@ -58,7 +58,7 @@ private: QDir m_dir; ResultPtr m_result; - bool m_aborted = false; + std::atomic m_aborted = false; std::function m_create_func; }; diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp index c486bd46..8a6e54d8 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp @@ -499,7 +499,7 @@ void LocalModParseTask::processAsLitemod() bool LocalModParseTask::abort() { - m_aborted = true; + m_aborted.store(true); return true; } @@ -521,7 +521,7 @@ void LocalModParseTask::executeTask() } if (m_aborted) - emitAborted(); + emit finished(); else emitSucceeded(); } diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.h b/launcher/minecraft/mod/tasks/LocalModParseTask.h index 4bbf3c85..413eb2d1 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.h @@ -39,5 +39,5 @@ private: QFileInfo m_modFile; ResultPtr m_result; - bool m_aborted = false; + std::atomic m_aborted = false; }; diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp index a56ba8ab..3a857740 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp @@ -38,8 +38,8 @@ #include "minecraft/mod/MetadataHandler.h" -ModFolderLoadTask::ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan, QObject* parent) - : Task(parent, false), m_mods_dir(mods_dir), m_index_dir(index_dir), m_is_indexed(is_indexed), m_clean_orphan(clean_orphan), m_result(new Result()) +ModFolderLoadTask::ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan) + : Task(nullptr, false), m_mods_dir(mods_dir), m_index_dir(index_dir), m_is_indexed(is_indexed), m_clean_orphan(clean_orphan), m_result(new Result()) {} void ModFolderLoadTask::executeTask() @@ -96,7 +96,10 @@ void ModFolderLoadTask::executeTask() } } - emitSucceeded(); + if (m_aborted) + emit finished(); + else + emitSucceeded(); } void ModFolderLoadTask::getFromMetadata() diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h index 840e95e1..0f18b8b9 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h @@ -57,7 +57,15 @@ public: } public: - ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan = false, QObject* parent = nullptr); + ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan = false); + + [[nodiscard]] bool canAbort() const override { return true; } + bool abort() override + { + m_aborted.store(true); + return true; + } + void executeTask() override; @@ -69,4 +77,6 @@ private: bool m_is_indexed; bool m_clean_orphan; ResultPtr m_result; + + std::atomic m_aborted = false; }; From 9db27c6acc3310c9bd8cda31bcd13073eec4794e Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 3 Sep 2022 13:26:06 -0300 Subject: [PATCH 156/273] fix: crash when adding resource packs directly in the folder This fixes an issue in which, when adding a new resource pack externally to PolyMC, when the resource pack view was open, would crash poly. Signed-off-by: flow --- launcher/minecraft/mod/ResourceFolderModel.h | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index d763bec3..5652c156 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -257,8 +257,11 @@ void ResourceFolderModel::applyUpdates(QSet& current_set, QSet // If the resource is resolving, but something about it changed, we don't want to // continue the resolving. if (current_resource->isResolving()) { - auto task = (*m_active_parse_tasks.find(current_resource->resolutionTicket())).get(); - task->abort(); + auto ticket = current_resource->resolutionTicket(); + if (m_active_parse_tasks.contains(ticket)) { + auto task = (*m_active_parse_tasks.find(ticket)).get(); + task->abort(); + } } m_resources[row].reset(new_resource); @@ -285,8 +288,11 @@ void ResourceFolderModel::applyUpdates(QSet& current_set, QSet Q_ASSERT(removed_set.contains(removed_it->get()->internal_id())); if ((*removed_it)->isResolving()) { - auto task = (*m_active_parse_tasks.find((*removed_it)->resolutionTicket())).get(); - task->abort(); + auto ticket = (*removed_it)->resolutionTicket(); + if (m_active_parse_tasks.contains(ticket)) { + auto task = (*m_active_parse_tasks.find(ticket)).get(); + task->abort(); + } } beginRemoveRows(QModelIndex(), removed_index, removed_index); From 43a7af3f44ee3520f617f46be8d239f9fe695c13 Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 3 Sep 2022 13:27:27 -0300 Subject: [PATCH 157/273] fix: removing mods with their metadata as well Signed-off-by: flow --- launcher/ui/pages/instance/ModFolderPage.cpp | 10 ++++++++++ launcher/ui/pages/instance/ModFolderPage.h | 2 ++ 2 files changed, 12 insertions(+) diff --git a/launcher/ui/pages/instance/ModFolderPage.cpp b/launcher/ui/pages/instance/ModFolderPage.cpp index 75b40e77..28a874c2 100644 --- a/launcher/ui/pages/instance/ModFolderPage.cpp +++ b/launcher/ui/pages/instance/ModFolderPage.cpp @@ -135,6 +135,16 @@ bool ModFolderPage::onSelectionChanged(const QModelIndex& current, const QModelI return true; } +void ModFolderPage::removeItem() +{ + + if (!m_controlsEnabled) + return; + + auto selection = m_filterModel->mapSelectionToSource(ui->treeView->selectionModel()->selection()); + m_model->deleteMods(selection.indexes()); +} + void ModFolderPage::installMods() { if (!m_controlsEnabled) diff --git a/launcher/ui/pages/instance/ModFolderPage.h b/launcher/ui/pages/instance/ModFolderPage.h index 7fc9d9a1..c9a55bde 100644 --- a/launcher/ui/pages/instance/ModFolderPage.h +++ b/launcher/ui/pages/instance/ModFolderPage.h @@ -59,6 +59,8 @@ class ModFolderPage : public ExternalResourcesPage { bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override; private slots: + void removeItem() override; + void installMods(); void updateMods(); From bedd3c50b6d3c399ccb243c9ea1a62d9b786389f Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 3 Sep 2022 15:05:23 -0300 Subject: [PATCH 158/273] fix: improve handling of unrecognized pack formats Signed-off-by: flow --- launcher/minecraft/mod/ResourcePack.cpp | 7 +------ launcher/minecraft/mod/ResourcePackFolderModel.cpp | 11 ++++++++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/launcher/minecraft/mod/ResourcePack.cpp b/launcher/minecraft/mod/ResourcePack.cpp index cc8d23ce..3fc10a2f 100644 --- a/launcher/minecraft/mod/ResourcePack.cpp +++ b/launcher/minecraft/mod/ResourcePack.cpp @@ -23,8 +23,7 @@ void ResourcePack::setPackFormat(int new_format_id) QMutexLocker locker(&m_data_lock); if (!s_pack_format_versions.contains(new_format_id)) { - qCritical() << "Error: Pack format '%1' is not a recognized resource pack id."; - return; + qWarning() << "Pack format '%1' is not a recognized resource pack id!"; } m_pack_format = new_format_id; @@ -71,10 +70,6 @@ QPixmap ResourcePack::image(QSize size) std::pair ResourcePack::compatibleVersions() const { if (!s_pack_format_versions.contains(m_pack_format)) { - // Not having a valid pack format is fine if we didn't yet parse the .mcmeta file, - // but if we did and we still don't have a valid pack format, that's a bit concerning. - Q_ASSERT(!isResolved()); - return { {}, {} }; } diff --git a/launcher/minecraft/mod/ResourcePackFolderModel.cpp b/launcher/minecraft/mod/ResourcePackFolderModel.cpp index 66b6cf5e..f8a6c1cf 100644 --- a/launcher/minecraft/mod/ResourcePackFolderModel.cpp +++ b/launcher/minecraft/mod/ResourcePackFolderModel.cpp @@ -60,12 +60,17 @@ QVariant ResourcePackFolderModel::data(const QModelIndex& index, int role) const case NameColumn: return m_resources[row]->name(); case PackFormatColumn: { - auto version_bounds = at(row)->compatibleVersions(); + auto resource = at(row); + auto pack_format = resource->packFormat(); + if (pack_format == 0) + return tr("Unrecognized"); + + auto version_bounds = resource->compatibleVersions(); if (version_bounds.first.toString().isEmpty()) - return QString::number(at(row)->packFormat()); + return QString::number(pack_format); return QString("%1 (%2 - %3)") - .arg(QString::number(at(row)->packFormat()), version_bounds.first.toString(), version_bounds.second.toString()); + .arg(QString::number(pack_format), version_bounds.first.toString(), version_bounds.second.toString()); } case DateColumn: return m_resources[row]->dateTimeChanged(); From d5583f0f022296176fea8e5db7b34cf83c7342b7 Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Sun, 4 Sep 2022 08:55:20 +0200 Subject: [PATCH 159/273] make the about dialog smaller Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> --- launcher/ui/dialogs/AboutDialog.ui | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/ui/dialogs/AboutDialog.ui b/launcher/ui/dialogs/AboutDialog.ui index 6eaa0c4e..e0429321 100644 --- a/launcher/ui/dialogs/AboutDialog.ui +++ b/launcher/ui/dialogs/AboutDialog.ui @@ -6,8 +6,8 @@ 0 0 - 783 - 843 + 573 + 600 From 1b559c777680278bfcf78eda6ee2870a852a7833 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Mon, 5 Sep 2022 17:21:58 +0200 Subject: [PATCH 160/273] Revert "Move classpath definition into NewLaunch itself" Signed-off-by: Sefa Eyeoglu --- launcher/minecraft/MinecraftInstance.cpp | 5 -- .../minecraft/launch/LauncherPartLaunch.cpp | 25 +++++- .../org/polymc/impl/OneSixLauncher.java | 86 ++++--------------- 3 files changed, 40 insertions(+), 76 deletions(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index cf127525..94b4776e 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -570,11 +570,6 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS if(!profile) return QString(); - for (auto cp : getClassPath()) - { - launchScript += "classPath " + cp + "\n"; - } - auto mainClass = getMainClass(); if (!mainClass.isEmpty()) { diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp index 3b905bf5..63e4d90f 100644 --- a/launcher/minecraft/launch/LauncherPartLaunch.cpp +++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp @@ -95,8 +95,8 @@ bool fitsInLocal8bit(const QString & string) void LauncherPartLaunch::executeTask() { - QString newLaunchJar = APPLICATION->getJarPath("NewLaunch.jar"); - if (newLaunchJar.isEmpty()) + QString jarPath = APPLICATION->getJarPath("NewLaunch.jar"); + if (jarPath.isEmpty()) { const char *reason = QT_TR_NOOP("Launcher library could not be found. Please check your installation."); emit logLine(tr(reason), MessageLevel::Fatal); @@ -119,6 +119,9 @@ void LauncherPartLaunch::executeTask() // make detachable - this will keep the process running even if the object is destroyed m_process.setDetachable(true); + auto classPath = minecraftInstance->getClassPath(); + classPath.prepend(jarPath); + auto natPath = minecraftInstance->getNativePath(); #ifdef Q_OS_WIN if (!fitsInLocal8bit(natPath)) @@ -134,7 +137,23 @@ void LauncherPartLaunch::executeTask() #endif args << "-cp"; - args << newLaunchJar; +#ifdef Q_OS_WIN + QStringList processed; + for(auto & item: classPath) + { + if (!fitsInLocal8bit(item)) + { + processed << shortPathName(item); + } + else + { + processed << item; + } + } + args << processed.join(';'); +#else + args << classPath.join(':'); +#endif args << "org.polymc.EntryPoint"; qDebug() << args.join(' '); diff --git a/libraries/launcher/org/polymc/impl/OneSixLauncher.java b/libraries/launcher/org/polymc/impl/OneSixLauncher.java index 250fe0f2..362ff8d6 100644 --- a/libraries/launcher/org/polymc/impl/OneSixLauncher.java +++ b/libraries/launcher/org/polymc/impl/OneSixLauncher.java @@ -1,53 +1,16 @@ -// SPDX-License-Identifier: GPL-3.0-only -/* - * PolyMC - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu +/* Copyright 2012-2021 MultiMC Contributors * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, version 3. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * http://www.apache.org/licenses/LICENSE-2.0 * - * Linking this library statically or dynamically with other modules is - * making a combined work based on this library. Thus, the terms and - * conditions of the GNU General Public License cover the whole - * combination. - * - * As a special exception, the copyright holders of this library give - * you permission to link this library with independent modules to - * produce an executable, regardless of the license terms of these - * independent modules, and to copy and distribute the resulting - * executable under terms of your choice, provided that you also meet, - * for each linked independent module, the terms and conditions of the - * license of that module. An independent module is a module which is - * not derived from or based on this library. If you modify this - * library, you may extend this exception to your version of the - * library, but you are not obliged to do so. If you do not wish to do - * so, delete this exception statement from your version. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - * - * This file incorporates work covered by the following copyright and - * permission notice: - * - * Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package org.polymc.impl; @@ -61,9 +24,6 @@ import java.applet.Applet; import java.io.File; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.net.URL; -import java.net.URLClassLoader; -import java.nio.file.Paths; import java.util.Collections; import java.util.List; import java.util.logging.Level; @@ -77,7 +37,6 @@ public final class OneSixLauncher implements Launcher { private static final Logger LOGGER = Logger.getLogger("OneSixLauncher"); // parameters, separated from ParamBucket - private final List classPath; private final List mcParams; private final List traits; private final String appletClass; @@ -94,8 +53,11 @@ public final class OneSixLauncher implements Launcher { private final String serverAddress; private final String serverPort; + private final ClassLoader classLoader; + public OneSixLauncher(Parameters params) { - classPath = params.allSafe("classPath", Collections.emptyList()); + classLoader = ClassLoader.getSystemClassLoader(); + mcParams = params.allSafe("param", Collections.emptyList()); mainClass = params.firstSafe("mainClass", "net.minecraft.client.Minecraft"); appletClass = params.firstSafe("appletClass", "net.minecraft.client.MinecraftApplet"); @@ -142,7 +104,7 @@ public final class OneSixLauncher implements Launcher { method.invoke(null, (Object) mcParams.toArray(new String[0])); } - private void legacyLaunch(ClassLoader classLoader) throws Exception { + private void legacyLaunch() throws Exception { // Get the Minecraft Class and set the base folder Class minecraftClass = classLoader.loadClass(mainClass); @@ -189,7 +151,7 @@ public final class OneSixLauncher implements Launcher { invokeMain(minecraftClass); } - private void launchWithMainClass(ClassLoader classLoader) throws Exception { + private void launchWithMainClass() throws Exception { // window size, title and state, onesix // FIXME: there is no good way to maximize the minecraft window in onesix. @@ -215,24 +177,12 @@ public final class OneSixLauncher implements Launcher { @Override public void launch() throws Exception { - URL[] classPathURLs = new URL[classPath.size()]; - for (int i = 0; i < classPath.size(); i++) { - File f = new File(classPath.get(i)); - classPathURLs[i] = f.toURI().toURL(); - } - // Some mod loaders (Fabric) read this property to determine the classpath. - String systemClassPath = System.getProperty("java.class.path"); - systemClassPath += File.pathSeparator + String.join(File.pathSeparator, classPath); - System.setProperty("java.class.path", systemClassPath); - - ClassLoader classLoader = new URLClassLoader(classPathURLs, getClass().getClassLoader()); - if (traits.contains("legacyLaunch") || traits.contains("alphaLaunch")) { // legacy launch uses the applet wrapper - legacyLaunch(classLoader); + legacyLaunch(); } else { // normal launch just calls main() - launchWithMainClass(classLoader); + launchWithMainClass(); } } From 4817f0312d6180ac15ddffaa698f77c5dd45dc00 Mon Sep 17 00:00:00 2001 From: timoreo Date: Tue, 6 Sep 2022 14:32:19 +0200 Subject: [PATCH 161/273] Fixed up a warning Signed-off-by: timoreo --- launcher/modplatform/atlauncher/ATLPackInstallTask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 5ed13470..70a35395 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -136,7 +136,7 @@ void PackInstallTask::onDownloadSucceeded() default: emitFailed(tr("Unsupported installation mode")); - break; + return; } // Display message if one exists From 03c148ce5081f71039d839a1e65a1866d321e597 Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Tue, 6 Sep 2022 15:20:46 +0200 Subject: [PATCH 162/273] chore: update install-qt-action to v3 Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> --- .github/workflows/build.yml | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 99d9cd07..26820d47 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -27,7 +27,6 @@ jobs: qt_host: linux qt_version: '6.2.4' qt_modules: 'qt5compat qtimageformats' - qt_path: /home/runner/work/PolyMC/Qt - os: windows-2022 name: "Windows-Legacy" @@ -45,7 +44,6 @@ jobs: qt_host: mac qt_version: '6.3.0' qt_modules: 'qt5compat qtimageformats' - qt_path: /Users/runner/work/PolyMC/Qt runs-on: ${{ matrix.os }} @@ -141,24 +139,16 @@ jobs: run: | sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 - - name: Cache Qt (macOS and AppImage) - id: cache-qt - if: matrix.qt_ver == 6 && runner.os != 'Windows' - uses: actions/cache@v3 - with: - path: '${{ matrix.qt_path }}/${{ matrix.qt_version }}' - key: ${{ matrix.qt_host }}-${{ matrix.qt_version }}-"${{ matrix.qt_modules }}"-qt_cache - - name: Install Qt (macOS and AppImage) if: matrix.qt_ver == 6 && runner.os != 'Windows' - uses: jurplel/install-qt-action@v2 + uses: jurplel/install-qt-action@v3 with: version: ${{ matrix.qt_version }} host: ${{ matrix.qt_host }} target: 'desktop' modules: ${{ matrix.qt_modules }} - cached: ${{ steps.cache-qt.outputs.cache-hit }} - aqtversion: ==2.1.* + cache: true + cache-key-prefix: ${{ matrix.qt_host }}-${{ matrix.qt_version }}-"${{ matrix.qt_modules }}"-qt_cache - name: Prepare AppImage (Linux) if: runner.os == 'Linux' && matrix.qt_ver != 5 From 8a65726e9dbe990a898a49d4ce05d61ad5b06852 Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 30 Jul 2022 17:38:02 -0300 Subject: [PATCH 163/273] fix: use more robust method of finding matches for major version This uses the proper version list to find all MC versions matching the major number (_don't say anything about SemVer_ :gun:). Signed-off-by: flow --- launcher/meta/VersionList.cpp | 7 ++++++ launcher/meta/VersionList.h | 1 + launcher/ui/widgets/ModFilterWidget.cpp | 33 ++++++++++++++++++++++--- launcher/ui/widgets/ModFilterWidget.h | 8 ++++++ 4 files changed, 45 insertions(+), 4 deletions(-) diff --git a/launcher/meta/VersionList.cpp b/launcher/meta/VersionList.cpp index 6d23ce9a..f609e94c 100644 --- a/launcher/meta/VersionList.cpp +++ b/launcher/meta/VersionList.cpp @@ -140,6 +140,13 @@ VersionPtr VersionList::getVersion(const QString &version) return out; } +bool VersionList::hasVersion(QString version) const +{ + auto ver = std::find_if(m_versions.constBegin(), m_versions.constEnd(), + [&](Meta::VersionPtr const& a){ return a->version() == version; }); + return (ver != m_versions.constEnd()); +} + void VersionList::setName(const QString &name) { m_name = name; diff --git a/launcher/meta/VersionList.h b/launcher/meta/VersionList.h index 378255df..a6db2fd7 100644 --- a/launcher/meta/VersionList.h +++ b/launcher/meta/VersionList.h @@ -66,6 +66,7 @@ public: QString humanReadable() const; VersionPtr getVersion(const QString &version); + bool hasVersion(QString version) const; QVector versions() const { diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index 4ab34375..a4010aee 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -1,6 +1,8 @@ #include "ModFilterWidget.h" #include "ui_ModFilterWidget.h" +#include "Application.h" + ModFilterWidget::ModFilterWidget(Version def, QWidget* parent) : QTabWidget(parent), m_filter(new Filter()), ui(new Ui::ModFilterWidget) { @@ -16,6 +18,24 @@ ModFilterWidget::ModFilterWidget(Version def, QWidget* parent) m_filter->versions.push_front(def); + m_version_list = APPLICATION->metadataIndex()->get("net.minecraft"); + if (!m_version_list->isLoaded()) { + QEventLoop load_version_list_loop; + + auto task = m_version_list->getLoadTask(); + + connect(task.get(), &Task::failed, [this]{ + ui->majorVersionButton->setText(tr("Major version match (failed to get version index)")); + disableVersionButton(VersionButtonID::Major); + }); + connect(task.get(), &Task::finished, &load_version_list_loop, &QEventLoop::quit); + + if (!task->isRunning()) + task->start(); + + load_version_list_loop.exec(); + } + setHidden(true); } @@ -76,7 +96,7 @@ void ModFilterWidget::onVersionFilterChanged(int id) //ui->lowerVersionComboBox->setEnabled(id == VersionButtonID::Between); //ui->upperVersionComboBox->setEnabled(id == VersionButtonID::Between); - int index = 0; + int index = 1; auto cast_id = (VersionButtonID) id; if (cast_id != m_version_id) { @@ -93,10 +113,15 @@ void ModFilterWidget::onVersionFilterChanged(int id) break; case(VersionButtonID::Major): { auto versionSplit = mcVersionStr().split("."); - for(auto i = Version(QString("%1.%2").arg(versionSplit[0], versionSplit[1])); i <= mcVersion(); index++){ - m_filter->versions.push_front(i); - i = Version(QString("%1.%2.%3").arg(versionSplit[0], versionSplit[1], QString("%1").arg(index))); + + auto major_version = QString("%1.%2").arg(versionSplit[0], versionSplit[1]); + QString version_str = major_version; + + while (m_version_list->hasVersion(version_str)) { + m_filter->versions.emplace_back(version_str); + version_str = QString("%1.%2").arg(major_version, QString::number(index++)); } + break; } case(VersionButtonID::All): diff --git a/launcher/ui/widgets/ModFilterWidget.h b/launcher/ui/widgets/ModFilterWidget.h index 334fc672..cf429174 100644 --- a/launcher/ui/widgets/ModFilterWidget.h +++ b/launcher/ui/widgets/ModFilterWidget.h @@ -4,6 +4,10 @@ #include #include "Version.h" + +#include "meta/Index.h" +#include "meta/VersionList.h" + #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" @@ -61,8 +65,12 @@ private: MinecraftInstance* m_instance = nullptr; + +/* Version stuff */ QButtonGroup m_mcVersion_buttons; + Meta::VersionListPtr m_version_list; + /* Used to tell if the filter was changed since the last getFilter() call */ VersionButtonID m_last_version_id = VersionButtonID::Strict; VersionButtonID m_version_id = VersionButtonID::Strict; From 42eb265624cb4a6461dc203a5ebfcb35ddd5d730 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 21 Aug 2022 10:50:10 -0300 Subject: [PATCH 164/273] refactor: create mod pages and filter widget by factory methods This takes most expensive operations out of the constructors. Signed-off-by: flow --- launcher/ui/dialogs/ModDownloadDialog.cpp | 4 +- launcher/ui/pages/modplatform/ModPage.cpp | 39 ++++++----- launcher/ui/pages/modplatform/ModPage.h | 17 ++++- .../ui/pages/modplatform/flame/FlameModPage.h | 7 +- .../modplatform/modrinth/ModrinthModPage.h | 7 +- launcher/ui/widgets/ModFilterWidget.cpp | 66 ++++++++++++------- launcher/ui/widgets/ModFilterWidget.h | 8 ++- 7 files changed, 102 insertions(+), 46 deletions(-) diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp index 7382d1cf..d740c8cb 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.cpp +++ b/launcher/ui/dialogs/ModDownloadDialog.cpp @@ -127,9 +127,9 @@ QList ModDownloadDialog::getPages() { QList pages; - pages.append(new ModrinthModPage(this, m_instance)); + pages.append(ModrinthModPage::create(this, m_instance)); if (APPLICATION->capabilities() & Application::SupportsFlame) - pages.append(new FlameModPage(this, m_instance)); + pages.append(FlameModPage::create(this, m_instance)); return pages; } diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index a34a74db..986caa77 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -47,12 +47,12 @@ #include "ui/dialogs/ModDownloadDialog.h" #include "ui/widgets/ProjectItem.h" + ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api) : QWidget(dialog) , m_instance(instance) , ui(new Ui::ModPage) , dialog(dialog) - , filter_widget(static_cast(instance)->getPackProfile()->getComponentVersion("net.minecraft"), this) , m_fetch_progress(this, false) , api(api) { @@ -76,17 +76,6 @@ ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api) m_fetch_progress.progressFormat(""); ui->gridLayout_3->addWidget(&m_fetch_progress, 0, 0, 1, ui->gridLayout_3->columnCount()); - ui->gridLayout_3->addWidget(&filter_widget, 1, 0, 1, ui->gridLayout_3->columnCount()); - - filter_widget.setInstance(static_cast(m_instance)); - m_filter = filter_widget.getFilter(); - - connect(&filter_widget, &ModFilterWidget::filterChanged, this, [&]{ - ui->searchButton->setStyleSheet("text-decoration: underline"); - }); - connect(&filter_widget, &ModFilterWidget::filterUnchanged, this, [&]{ - ui->searchButton->setStyleSheet("text-decoration: none"); - }); ui->packView->setItemDelegate(new ProjectItemDelegate(this)); ui->packView->installEventFilter(this); @@ -97,6 +86,26 @@ ModPage::~ModPage() delete ui; } +void ModPage::setFilterWidget(unique_qobject_ptr& widget) +{ + if (m_filter_widget) + disconnect(m_filter_widget.get(), nullptr, nullptr, nullptr); + + m_filter_widget.swap(widget); + + ui->gridLayout_3->addWidget(m_filter_widget.get(), 0, 0, 1, ui->gridLayout_3->columnCount()); + + m_filter_widget->setInstance(static_cast(m_instance)); + m_filter = m_filter_widget->getFilter(); + + connect(m_filter_widget.get(), &ModFilterWidget::filterChanged, this, [&]{ + ui->searchButton->setStyleSheet("text-decoration: underline"); + }); + connect(m_filter_widget.get(), &ModFilterWidget::filterUnchanged, this, [&]{ + ui->searchButton->setStyleSheet("text-decoration: none"); + }); +} + /******** Qt things ********/ @@ -141,13 +150,13 @@ auto ModPage::eventFilter(QObject* watched, QEvent* event) -> bool void ModPage::filterMods() { - filter_widget.setHidden(!filter_widget.isHidden()); + m_filter_widget->setHidden(!m_filter_widget->isHidden()); } void ModPage::triggerSearch() { - auto changed = filter_widget.changed(); - m_filter = filter_widget.getFilter(); + auto changed = m_filter_widget->changed(); + m_filter = m_filter_widget->getFilter(); if(changed){ ui->packView->clearSelection(); diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index 09c38d8b..3f31651c 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -21,7 +21,17 @@ class ModPage : public QWidget, public BasePage { Q_OBJECT public: - explicit ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api); + template + static T* create(ModDownloadDialog* dialog, BaseInstance* instance) + { + auto page = new T(dialog, instance); + + auto filter_widget = ModFilterWidget::create(static_cast(instance)->getPackProfile()->getComponentVersion("net.minecraft"), page); + page->setFilterWidget(filter_widget); + + return page; + } + ~ModPage() override; /* Affects what the user sees */ @@ -51,6 +61,8 @@ class ModPage : public QWidget, public BasePage { /** Programatically set the term in the search bar. */ void setSearchTerm(QString); + void setFilterWidget(unique_qobject_ptr&); + auto getCurrent() -> ModPlatform::IndexedPack& { return current; } void updateModVersions(int prev_count = -1); @@ -60,6 +72,7 @@ class ModPage : public QWidget, public BasePage { BaseInstance* m_instance; protected: + ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api); void updateSelectionButton(); protected slots: @@ -73,7 +86,7 @@ class ModPage : public QWidget, public BasePage { Ui::ModPage* ui = nullptr; ModDownloadDialog* dialog = nullptr; - ModFilterWidget filter_widget; + unique_qobject_ptr m_filter_widget; std::shared_ptr m_filter; ProgressWidget m_fetch_progress; diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.h b/launcher/ui/pages/modplatform/flame/FlameModPage.h index 445d0368..2cd484cb 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModPage.h +++ b/launcher/ui/pages/modplatform/flame/FlameModPage.h @@ -44,7 +44,12 @@ class FlameModPage : public ModPage { Q_OBJECT public: - explicit FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance); + static FlameModPage* create(ModDownloadDialog* dialog, BaseInstance* instance) + { + return ModPage::create(dialog, instance); + } + + FlameModPage(ModDownloadDialog* dialog, BaseInstance* instance); ~FlameModPage() override = default; inline auto displayName() const -> QString override { return "CurseForge"; } diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h index 94985f63..40d82e6f 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h @@ -44,7 +44,12 @@ class ModrinthModPage : public ModPage { Q_OBJECT public: - explicit ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* instance); + static ModrinthModPage* create(ModDownloadDialog* dialog, BaseInstance* instance) + { + return ModPage::create(dialog, instance); + } + + ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* instance); ~ModrinthModPage() override = default; inline auto displayName() const -> QString override { return "Modrinth"; } diff --git a/launcher/ui/widgets/ModFilterWidget.cpp b/launcher/ui/widgets/ModFilterWidget.cpp index a4010aee..ea052c41 100644 --- a/launcher/ui/widgets/ModFilterWidget.cpp +++ b/launcher/ui/widgets/ModFilterWidget.cpp @@ -3,6 +3,37 @@ #include "Application.h" +unique_qobject_ptr ModFilterWidget::create(Version default_version, QWidget* parent) +{ + auto filter_widget = new ModFilterWidget(default_version, parent); + + if (!filter_widget->versionList()->isLoaded()) { + QEventLoop load_version_list_loop; + + QTimer time_limit_for_list_load; + time_limit_for_list_load.setTimerType(Qt::TimerType::CoarseTimer); + time_limit_for_list_load.setSingleShot(true); + time_limit_for_list_load.callOnTimeout(&load_version_list_loop, &QEventLoop::quit); + time_limit_for_list_load.start(4000); + + auto task = filter_widget->versionList()->getLoadTask(); + + connect(task.get(), &Task::failed, [filter_widget]{ + filter_widget->disableVersionButton(VersionButtonID::Major, tr("failed to get version index")); + }); + connect(task.get(), &Task::finished, &load_version_list_loop, &QEventLoop::quit); + + if (!task->isRunning()) + task->start(); + + load_version_list_loop.exec(); + if (time_limit_for_list_load.isActive()) + time_limit_for_list_load.stop(); + } + + return unique_qobject_ptr(filter_widget); +} + ModFilterWidget::ModFilterWidget(Version def, QWidget* parent) : QTabWidget(parent), m_filter(new Filter()), ui(new Ui::ModFilterWidget) { @@ -19,23 +50,6 @@ ModFilterWidget::ModFilterWidget(Version def, QWidget* parent) m_filter->versions.push_front(def); m_version_list = APPLICATION->metadataIndex()->get("net.minecraft"); - if (!m_version_list->isLoaded()) { - QEventLoop load_version_list_loop; - - auto task = m_version_list->getLoadTask(); - - connect(task.get(), &Task::failed, [this]{ - ui->majorVersionButton->setText(tr("Major version match (failed to get version index)")); - disableVersionButton(VersionButtonID::Major); - }); - connect(task.get(), &Task::finished, &load_version_list_loop, &QEventLoop::quit); - - if (!task->isRunning()) - task->start(); - - load_version_list_loop.exec(); - } - setHidden(true); } @@ -71,24 +85,30 @@ auto ModFilterWidget::getFilter() -> std::shared_ptr return m_filter; } -void ModFilterWidget::disableVersionButton(VersionButtonID id) +void ModFilterWidget::disableVersionButton(VersionButtonID id, QString reason) { + QAbstractButton* btn = nullptr; + switch(id){ case(VersionButtonID::Strict): - ui->strictVersionButton->setEnabled(false); + btn = ui->strictVersionButton; break; case(VersionButtonID::Major): - ui->majorVersionButton->setEnabled(false); + btn = ui->majorVersionButton; break; case(VersionButtonID::All): - ui->allVersionsButton->setEnabled(false); + btn = ui->allVersionsButton; break; case(VersionButtonID::Between): - // ui->betweenVersionsButton->setEnabled(false); - break; default: break; } + + if (btn) { + btn->setEnabled(false); + if (!reason.isEmpty()) + btn->setText(btn->text() + QString(" (%1)").arg(reason)); + } } void ModFilterWidget::onVersionFilterChanged(int id) diff --git a/launcher/ui/widgets/ModFilterWidget.h b/launcher/ui/widgets/ModFilterWidget.h index cf429174..958a1e2b 100644 --- a/launcher/ui/widgets/ModFilterWidget.h +++ b/launcher/ui/widgets/ModFilterWidget.h @@ -38,18 +38,22 @@ public: std::shared_ptr m_filter; public: - explicit ModFilterWidget(Version def, QWidget* parent = nullptr); + static unique_qobject_ptr create(Version default_version, QWidget* parent = nullptr); ~ModFilterWidget(); void setInstance(MinecraftInstance* instance); /// By default all buttons are enabled - void disableVersionButton(VersionButtonID); + void disableVersionButton(VersionButtonID, QString reason = {}); auto getFilter() -> std::shared_ptr; auto changed() const -> bool { return m_last_version_id != m_version_id; } + Meta::VersionListPtr versionList() { return m_version_list; } + private: + ModFilterWidget(Version def, QWidget* parent = nullptr); + inline auto mcVersionStr() const -> QString { return m_instance ? m_instance->getPackProfile()->getComponentVersion("net.minecraft") : ""; } inline auto mcVersion() const -> Version { return { mcVersionStr() }; } From 7cf2c3be0fe7495898675082e2e3f74c496683d4 Mon Sep 17 00:00:00 2001 From: flow Date: Wed, 7 Sep 2022 12:11:42 -0300 Subject: [PATCH 165/273] fix: start at least one task in ConcurrentTask This allows us to emit all the necessary stuff when we're finished in the case of starting a task with no subtasks. In particular, this caused the mod updater to not work properly :) Signed-off-by: flow --- launcher/tasks/ConcurrentTask.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/launcher/tasks/ConcurrentTask.cpp b/launcher/tasks/ConcurrentTask.cpp index 484ac58e..ce08a6a2 100644 --- a/launcher/tasks/ConcurrentTask.cpp +++ b/launcher/tasks/ConcurrentTask.cpp @@ -37,7 +37,8 @@ void ConcurrentTask::executeTask() { m_total_size = m_queue.size(); - int num_starts = std::min(m_total_max_size, m_total_size); + // Start the least amount of tasks needed, but at least one + int num_starts = std::max(1, std::min(m_total_max_size, m_total_size)); for (int i = 0; i < num_starts; i++) { QMetaObject::invokeMethod(this, &ConcurrentTask::startNext, Qt::QueuedConnection); } From a091245793d1e8c9cc4820c76dfdfc6e2fbbb917 Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 10 Sep 2022 09:10:16 -0300 Subject: [PATCH 166/273] fix: emit signals when aborting NetJob Signed-off-by: flow --- launcher/net/NetJob.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/launcher/net/NetJob.cpp b/launcher/net/NetJob.cpp index 20d75976..8ced1b7e 100644 --- a/launcher/net/NetJob.cpp +++ b/launcher/net/NetJob.cpp @@ -95,6 +95,11 @@ auto NetJob::abort() -> bool fullyAborted &= part->abort(); } + if (fullyAborted) + emitAborted(); + else + emitFailed(tr("Failed to abort all tasks in the NetJob!")); + return fullyAborted; } From 4261dcff39b827f186b12edd7641d7290c91492d Mon Sep 17 00:00:00 2001 From: dada513 Date: Sun, 11 Sep 2022 10:16:18 +0200 Subject: [PATCH 167/273] fix meta Signed-off-by: dada513 --- program_info/org.polymc.PolyMC.metainfo.xml.in | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/program_info/org.polymc.PolyMC.metainfo.xml.in b/program_info/org.polymc.PolyMC.metainfo.xml.in index db0ab882..ef5a7d93 100644 --- a/program_info/org.polymc.PolyMC.metainfo.xml.in +++ b/program_info/org.polymc.PolyMC.metainfo.xml.in @@ -28,27 +28,27 @@ The main PolyMC window - https://polymc.org/img/screenshots/LauncherDark.png + https://polymc.org/img/screenshots/LauncherDark.png Modpack installation - https://polymc.org/img/screenshots/ModpackInstallDark.png + https://polymc.org/img/screenshots/ModpackInstallDark.png Mod installation - https://polymc.org/img/screenshots/ModInstallDark.png + https://polymc.org/img/screenshots/ModInstallDark.png Mod updating - https://polymc.org/img/screenshots/ModUpdateDark.png + https://polymc.org/img/screenshots/ModUpdateDark.png Instance management - https://polymc.org/img/screenshots/PropertiesDark.png + https://polymc.org/img/screenshots/PropertiesDark.png Cat :) - https://polymc.org/img/screenshots/LauncherCatDark.png + https://polymc.org/img/screenshots/LauncherCatDark.png From 4c7d3a103ca9cfd3af0b3acf2877561150c5ac60 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 11 Sep 2022 22:25:18 +0200 Subject: [PATCH 168/273] refactor: restructure tests Signed-off-by: Sefa Eyeoglu --- CMakeLists.txt | 5 +- launcher/CMakeLists.txt | 62 +----------------- tests/CMakeLists.txt | 43 ++++++++++++ {launcher => tests}/FileSystem_test.cpp | 4 +- {launcher => tests}/GZip_test.cpp | 2 +- .../GradleSpecifier_test.cpp | 2 +- {launcher/settings => tests}/INIFile_test.cpp | 2 +- {launcher/meta => tests}/Index_test.cpp | 4 +- {launcher/java => tests}/JavaVersion_test.cpp | 2 +- .../minecraft => tests}/Library_test.cpp | 48 +++++++------- .../MojangVersionFormat_test.cpp | 6 +- .../mojang => tests}/PackageManifest_test.cpp | 8 +-- .../packwiz => tests}/Packwiz_test.cpp | 6 +- .../minecraft => tests}/ParseUtils_test.cpp | 2 +- .../ResourceFolderModel_test.cpp | 18 ++--- .../mod => tests}/ResourcePackParse_test.cpp | 12 ++-- {launcher/tasks => tests}/Task_test.cpp | 8 +-- {launcher => tests}/Version_test.cpp | 2 +- .../FileSystem-test_createShortcut-unix | Bin .../assets/minecraft/textures/blah.txt | Bin .../FileSystem}/test_folder/pack.mcmeta | Bin .../testdata/FileSystem}/test_folder/pack.nfo | Bin tests/testdata/Library | 1 + .../MojangVersionFormat}/1.9-simple.json | Bin .../testdata/MojangVersionFormat}/1.9.json | Bin .../codecwav-20101023.jar | Bin .../MojangVersionFormat}/lib-native-arch.json | Bin .../MojangVersionFormat}/lib-native.json | Bin .../MojangVersionFormat}/lib-simple.json | Bin .../testname-testversion-linux-32.jar | Bin .../PackageManifest}/1.8.0_202-x64.json | Bin .../testdata/PackageManifest}/inspect/a/b.txt | Bin .../PackageManifest}/inspect/a/b/b.txt | 0 .../PackageManifest}/inspect_win/a/b.txt | Bin .../PackageManifest}/inspect_win/a/b/b.txt | Bin .../Packwiz}/borderless-mining.pw.toml | Bin .../screenshot-to-clipboard-fabric.pw.toml | Bin tests/testdata/ResourceFolderModel | 1 + .../another_test_folder/pack.mcmeta | Bin .../ResourcePackParse}/supercoolmod.jar | Bin .../assets/minecraft/textures/blah.txt | Bin .../test_folder/pack.mcmeta | Bin .../ResourcePackParse}/test_folder/pack.nfo | Bin .../test_resource_pack_idk.zip | Bin 44 files changed, 113 insertions(+), 125 deletions(-) create mode 100644 tests/CMakeLists.txt rename {launcher => tests}/FileSystem_test.cpp (97%) rename {launcher => tests}/GZip_test.cpp (98%) rename {launcher/minecraft => tests}/GradleSpecifier_test.cpp (98%) rename {launcher/settings => tests}/INIFile_test.cpp (97%) rename {launcher/meta => tests}/Index_test.cpp (96%) rename {launcher/java => tests}/JavaVersion_test.cpp (99%) rename {launcher/minecraft => tests}/Library_test.cpp (88%) rename {launcher/minecraft => tests}/MojangVersionFormat_test.cpp (85%) rename {launcher/mojang => tests}/PackageManifest_test.cpp (97%) rename {launcher/modplatform/packwiz => tests}/Packwiz_test.cpp (94%) rename {launcher/minecraft => tests}/ParseUtils_test.cpp (96%) rename {launcher/minecraft/mod => tests}/ResourceFolderModel_test.cpp (92%) rename {launcher/minecraft/mod => tests}/ResourcePackParse_test.cpp (86%) rename {launcher/tasks => tests}/Task_test.cpp (97%) rename {launcher => tests}/Version_test.cpp (99%) rename {launcher/testdata => tests/testdata/FileSystem}/FileSystem-test_createShortcut-unix (100%) rename {launcher/minecraft/mod/testdata => tests/testdata/FileSystem}/test_folder/assets/minecraft/textures/blah.txt (100%) rename {launcher/minecraft/mod/testdata => tests/testdata/FileSystem}/test_folder/pack.mcmeta (100%) rename {launcher/minecraft/mod/testdata => tests/testdata/FileSystem}/test_folder/pack.nfo (100%) create mode 120000 tests/testdata/Library rename {launcher/minecraft/testdata => tests/testdata/MojangVersionFormat}/1.9-simple.json (100%) rename {launcher/minecraft/testdata => tests/testdata/MojangVersionFormat}/1.9.json (100%) rename {launcher/minecraft/testdata => tests/testdata/MojangVersionFormat}/codecwav-20101023.jar (100%) rename {launcher/minecraft/testdata => tests/testdata/MojangVersionFormat}/lib-native-arch.json (100%) rename {launcher/minecraft/testdata => tests/testdata/MojangVersionFormat}/lib-native.json (100%) rename {launcher/minecraft/testdata => tests/testdata/MojangVersionFormat}/lib-simple.json (100%) rename {launcher/minecraft/testdata => tests/testdata/MojangVersionFormat}/testname-testversion-linux-32.jar (100%) rename {launcher/mojang/testdata => tests/testdata/PackageManifest}/1.8.0_202-x64.json (100%) rename {launcher/mojang/testdata => tests/testdata/PackageManifest}/inspect/a/b.txt (100%) rename {launcher/mojang/testdata => tests/testdata/PackageManifest}/inspect/a/b/b.txt (100%) rename {launcher/mojang/testdata => tests/testdata/PackageManifest}/inspect_win/a/b.txt (100%) rename {launcher/mojang/testdata => tests/testdata/PackageManifest}/inspect_win/a/b/b.txt (100%) rename {launcher/modplatform/packwiz/testdata => tests/testdata/Packwiz}/borderless-mining.pw.toml (100%) rename {launcher/modplatform/packwiz/testdata => tests/testdata/Packwiz}/screenshot-to-clipboard-fabric.pw.toml (100%) create mode 120000 tests/testdata/ResourceFolderModel rename {launcher/minecraft/mod/testdata => tests/testdata/ResourcePackParse}/another_test_folder/pack.mcmeta (100%) rename {launcher/minecraft/mod/testdata => tests/testdata/ResourcePackParse}/supercoolmod.jar (100%) rename {launcher/testdata => tests/testdata/ResourcePackParse}/test_folder/assets/minecraft/textures/blah.txt (100%) rename {launcher/testdata => tests/testdata/ResourcePackParse}/test_folder/pack.mcmeta (100%) rename {launcher/testdata => tests/testdata/ResourcePackParse}/test_folder/pack.nfo (100%) rename {launcher/minecraft/mod/testdata => tests/testdata/ResourcePackParse}/test_resource_pack_idk.zip (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6cb806a3..7100ab1b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,7 +67,7 @@ find_package(ECM REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH "${ECM_MODULE_PATH};${CMAKE_MODULE_PATH}") include(CTest) include(ECMAddTests) -if (BUILD_TESTING) +if(BUILD_TESTING) enable_testing() endif() @@ -321,5 +321,8 @@ add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API add_subdirectory(buildconfig) +if(BUILD_TESTING) + add_subdirectory(tests) +endif() # NOTE: this must always be last to appease the CMake deity of quirky install command evaluation order. add_subdirectory(launcher) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index a5303e94..6e93b530 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -88,12 +88,6 @@ set(CORE_SOURCES MMCTime.cpp ) -ecm_add_test(FileSystem_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test - TEST_NAME FileSystem) # TODO: needs testdata - -ecm_add_test(GZip_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test - TEST_NAME GZip) - set(PATHMATCHER_SOURCES # Path matchers pathmatcher/FSTreeMatcher.h @@ -355,46 +349,6 @@ set(MINECRAFT_SOURCES mojang/PackageManifest.cpp minecraft/Agent.h) -ecm_add_test(minecraft/GradleSpecifier_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test - TEST_NAME GradleSpecifier) - -if(BUILD_TESTING) - add_executable(PackageManifest - mojang/PackageManifest_test.cpp - ) - target_link_libraries(PackageManifest - Launcher_logic - Qt${QT_VERSION_MAJOR}::Test - ) - target_include_directories(PackageManifest - PRIVATE ../cmake/UnitTest/ - ) - add_test( - NAME PackageManifest - COMMAND PackageManifest - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - ) -endif() - -# TODO: needs minecraft/testdata -ecm_add_test(minecraft/MojangVersionFormat_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test - TEST_NAME MojangVersionFormat) - -ecm_add_test(minecraft/Library_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test - TEST_NAME Library) - -# FIXME: shares data with FileSystem test -# TODO: needs testdata -ecm_add_test(minecraft/mod/ResourceFolderModel_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test - TEST_NAME ResourceFolderModel) - -ecm_add_test(minecraft/mod/ResourcePackParse_test.cpp - LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test - TEST_NAME ResourcePackParse) - -ecm_add_test(minecraft/ParseUtils_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test - TEST_NAME ParseUtils) - # the screenshots feature set(SCREENSHOTS_SOURCES screenshots/Screenshot.h @@ -416,9 +370,6 @@ set(TASKS_SOURCES tasks/MultipleOptionsTask.cpp ) -ecm_add_test(tasks/Task_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test - TEST_NAME Task) - set(SETTINGS_SOURCES # Settings settings/INIFile.cpp @@ -435,9 +386,6 @@ set(SETTINGS_SOURCES settings/SettingsObject.h ) -ecm_add_test(settings/INIFile_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test - TEST_NAME INIFile) - set(JAVA_SOURCES java/JavaChecker.h java/JavaChecker.cpp @@ -453,9 +401,6 @@ set(JAVA_SOURCES java/JavaVersion.cpp ) -ecm_add_test(java/JavaVersion_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test - TEST_NAME JavaVersion) - set(TRANSLATIONS_SOURCES translations/TranslationsModel.h translations/TranslationsModel.cpp @@ -558,9 +503,6 @@ set(PACKWIZ_SOURCES modplatform/packwiz/Packwiz.cpp ) -# TODO: needs modplatform/packwiz/testdata -ecm_add_test(modplatform/packwiz/Packwiz_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test - TEST_NAME Packwiz) set(TECHNIC_SOURCES modplatform/technic/SingleZipPackInstallTask.h @@ -584,9 +526,6 @@ set(ATLAUNCHER_SOURCES modplatform/atlauncher/ATLShareCode.h ) -ecm_add_test(meta/Index_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test - TEST_NAME Index) - ################################ COMPILE ################################ # we need zlib @@ -1005,6 +944,7 @@ endif() # Add executable add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHER_UI} ${LAUNCHER_RESOURCES}) +target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(Launcher_logic systeminfo Launcher_classparser diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 00000000..1265d7a5 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,43 @@ +project(tests) + +ecm_add_test(FileSystem_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test + TEST_NAME FileSystem) + +ecm_add_test(GZip_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test + TEST_NAME GZip) + +ecm_add_test(GradleSpecifier_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test + TEST_NAME GradleSpecifier) + +ecm_add_test(PackageManifest_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test + TEST_NAME PackageManifest) + +ecm_add_test(MojangVersionFormat_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test + TEST_NAME MojangVersionFormat) + +ecm_add_test(Library_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test + TEST_NAME Library) + +ecm_add_test(ResourceFolderModel_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test + TEST_NAME ResourceFolderModel) + +ecm_add_test(ResourcePackParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test + TEST_NAME ResourcePackParse) + +ecm_add_test(ParseUtils_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test + TEST_NAME ParseUtils) + +ecm_add_test(Task_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test + TEST_NAME Task) + +ecm_add_test(INIFile_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test + TEST_NAME INIFile) + +ecm_add_test(JavaVersion_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test + TEST_NAME JavaVersion) + +ecm_add_test(Packwiz_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test + TEST_NAME Packwiz) + +ecm_add_test(Index_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test + TEST_NAME Index) diff --git a/launcher/FileSystem_test.cpp b/tests/FileSystem_test.cpp similarity index 97% rename from launcher/FileSystem_test.cpp rename to tests/FileSystem_test.cpp index 99ae9269..6df13e80 100644 --- a/launcher/FileSystem_test.cpp +++ b/tests/FileSystem_test.cpp @@ -2,7 +2,7 @@ #include #include -#include "FileSystem.h" +#include class FileSystemTest : public QObject { @@ -80,7 +80,7 @@ slots: void test_copy() { - QString folder = QFINDTESTDATA("testdata/test_folder"); + QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder"); auto f = [&folder]() { QTemporaryDir tempDir; diff --git a/launcher/GZip_test.cpp b/tests/GZip_test.cpp similarity index 98% rename from launcher/GZip_test.cpp rename to tests/GZip_test.cpp index 73859fbc..1e762b2e 100644 --- a/launcher/GZip_test.cpp +++ b/tests/GZip_test.cpp @@ -1,6 +1,6 @@ #include -#include "GZip.h" +#include #include void fib(int &prev, int &cur) diff --git a/launcher/minecraft/GradleSpecifier_test.cpp b/tests/GradleSpecifier_test.cpp similarity index 98% rename from launcher/minecraft/GradleSpecifier_test.cpp rename to tests/GradleSpecifier_test.cpp index a062dfac..850f8388 100644 --- a/launcher/minecraft/GradleSpecifier_test.cpp +++ b/tests/GradleSpecifier_test.cpp @@ -1,6 +1,6 @@ #include -#include "minecraft/GradleSpecifier.h" +#include class GradleSpecifierTest : public QObject { diff --git a/launcher/settings/INIFile_test.cpp b/tests/INIFile_test.cpp similarity index 97% rename from launcher/settings/INIFile_test.cpp rename to tests/INIFile_test.cpp index d23f9fdf..b64b031b 100644 --- a/launcher/settings/INIFile_test.cpp +++ b/tests/INIFile_test.cpp @@ -1,6 +1,6 @@ #include -#include "settings/INIFile.h" +#include class IniFileTest : public QObject { diff --git a/launcher/meta/Index_test.cpp b/tests/Index_test.cpp similarity index 96% rename from launcher/meta/Index_test.cpp rename to tests/Index_test.cpp index 261858c4..436b753e 100644 --- a/launcher/meta/Index_test.cpp +++ b/tests/Index_test.cpp @@ -1,7 +1,7 @@ #include -#include "meta/Index.h" -#include "meta/VersionList.h" +#include +#include class IndexTest : public QObject { diff --git a/launcher/java/JavaVersion_test.cpp b/tests/JavaVersion_test.cpp similarity index 99% rename from launcher/java/JavaVersion_test.cpp rename to tests/JavaVersion_test.cpp index 545947ef..76d9af2f 100644 --- a/launcher/java/JavaVersion_test.cpp +++ b/tests/JavaVersion_test.cpp @@ -1,6 +1,6 @@ #include -#include "java/JavaVersion.h" +#include class JavaVersionTest : public QObject { diff --git a/launcher/minecraft/Library_test.cpp b/tests/Library_test.cpp similarity index 88% rename from launcher/minecraft/Library_test.cpp rename to tests/Library_test.cpp index 834dd558..869c7673 100644 --- a/launcher/minecraft/Library_test.cpp +++ b/tests/Library_test.cpp @@ -1,10 +1,10 @@ #include -#include "minecraft/MojangVersionFormat.h" -#include "minecraft/OneSixVersionFormat.h" -#include "minecraft/Library.h" -#include "net/HttpMetaCache.h" -#include "FileSystem.h" +#include +#include +#include +#include +#include class LibraryTest : public QObject { @@ -30,7 +30,7 @@ slots: { cache.reset(new HttpMetaCache()); cache->addBase("libraries", QDir("libraries").absolutePath()); - dataDir = QDir(QFINDTESTDATA("testdata")).absolutePath(); + dataDir = QDir(QFINDTESTDATA("testdata/Library")).absolutePath(); } void test_legacy() { @@ -72,14 +72,14 @@ slots: QCOMPARE(test.isNative(), false); QStringList failedFiles; test.setHint("local"); - auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QFINDTESTDATA("testdata")); + auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QFINDTESTDATA("testdata/Library")); QCOMPARE(downloads.size(), 0); qDebug() << failedFiles; QCOMPARE(failedFiles.size(), 0); QStringList jar, native, native32, native64; - test.getApplicableFiles(currentSystem, jar, native, native32, native64, QFINDTESTDATA("testdata")); - QCOMPARE(jar, {QFileInfo(QFINDTESTDATA("testdata/codecwav-20101023.jar")).absoluteFilePath()}); + test.getApplicableFiles(currentSystem, jar, native, native32, native64, QFINDTESTDATA("testdata/Library")); + QCOMPARE(jar, {QFileInfo(QFINDTESTDATA("testdata/Library/codecwav-20101023.jar")).absoluteFilePath()}); QCOMPARE(native, {}); QCOMPARE(native32, {}); QCOMPARE(native64, {}); @@ -165,20 +165,20 @@ slots: test.setRepositoryURL("file://foo/bar"); { QStringList jar, native, native32, native64; - test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QFINDTESTDATA("testdata")); + test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QFINDTESTDATA("testdata/Library")); QCOMPARE(jar, {}); QCOMPARE(native, {}); - QCOMPARE(native32, {QFileInfo(QFINDTESTDATA("testdata/testname-testversion-linux-32.jar")).absoluteFilePath()}); - QCOMPARE(native64, {QFileInfo(QFINDTESTDATA("testdata") + "/testname-testversion-linux-64.jar").absoluteFilePath()}); + QCOMPARE(native32, {QFileInfo(QFINDTESTDATA("testdata/Library/testname-testversion-linux-32.jar")).absoluteFilePath()}); + QCOMPARE(native64, {QFileInfo(QFINDTESTDATA("testdata/Library") + "/testname-testversion-linux-64.jar").absoluteFilePath()}); QStringList failedFiles; - auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QFINDTESTDATA("testdata")); + auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QFINDTESTDATA("testdata/Library")); QCOMPARE(dls.size(), 0); - QCOMPARE(failedFiles, {QFileInfo(QFINDTESTDATA("testdata") + "/testname-testversion-linux-64.jar").absoluteFilePath()}); + QCOMPARE(failedFiles, {QFileInfo(QFINDTESTDATA("testdata/Library") + "/testname-testversion-linux-64.jar").absoluteFilePath()}); } } void test_onenine() { - auto test = readMojangJson(QFINDTESTDATA("testdata/lib-simple.json")); + auto test = readMojangJson(QFINDTESTDATA("testdata/Library/lib-simple.json")); { QStringList jar, native, native32, native64; test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString()); @@ -197,41 +197,41 @@ slots: test->setHint("local"); { QStringList jar, native, native32, native64; - test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QFINDTESTDATA("testdata")); - QCOMPARE(jar, {QFileInfo(QFINDTESTDATA("testdata/codecwav-20101023.jar")).absoluteFilePath()}); + test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QFINDTESTDATA("testdata/Library")); + QCOMPARE(jar, {QFileInfo(QFINDTESTDATA("testdata/Library/codecwav-20101023.jar")).absoluteFilePath()}); QCOMPARE(native, {}); QCOMPARE(native32, {}); QCOMPARE(native64, {}); } { QStringList failedFiles; - auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QFINDTESTDATA("testdata")); + auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QFINDTESTDATA("testdata/Library")); QCOMPARE(dls.size(), 0); QCOMPARE(failedFiles, {}); } } void test_onenine_local_override() { - auto test = readMojangJson(QFINDTESTDATA("testdata/lib-simple.json")); + auto test = readMojangJson(QFINDTESTDATA("testdata/Library/lib-simple.json")); test->setHint("local"); { QStringList jar, native, native32, native64; - test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QFINDTESTDATA("testdata")); - QCOMPARE(jar, {QFileInfo(QFINDTESTDATA("testdata/codecwav-20101023.jar")).absoluteFilePath()}); + test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QFINDTESTDATA("testdata/Library")); + QCOMPARE(jar, {QFileInfo(QFINDTESTDATA("testdata/Library/codecwav-20101023.jar")).absoluteFilePath()}); QCOMPARE(native, {}); QCOMPARE(native32, {}); QCOMPARE(native64, {}); } { QStringList failedFiles; - auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QFINDTESTDATA("testdata")); + auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QFINDTESTDATA("testdata/Library")); QCOMPARE(dls.size(), 0); QCOMPARE(failedFiles, {}); } } void test_onenine_native() { - auto test = readMojangJson(QFINDTESTDATA("testdata/lib-native.json")); + auto test = readMojangJson(QFINDTESTDATA("testdata/Library/lib-native.json")); QStringList jar, native, native32, native64; test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString()); QCOMPARE(jar, QStringList()); @@ -246,7 +246,7 @@ slots: } void test_onenine_native_arch() { - auto test = readMojangJson(QFINDTESTDATA("testdata/lib-native-arch.json")); + auto test = readMojangJson(QFINDTESTDATA("testdata/Library/lib-native-arch.json")); QStringList jar, native, native32, native64; test->getApplicableFiles(Os_Windows, jar, native, native32, native64, QString()); QCOMPARE(jar, {}); diff --git a/launcher/minecraft/MojangVersionFormat_test.cpp b/tests/MojangVersionFormat_test.cpp similarity index 85% rename from launcher/minecraft/MojangVersionFormat_test.cpp rename to tests/MojangVersionFormat_test.cpp index 71df784b..219fbfa2 100644 --- a/launcher/minecraft/MojangVersionFormat_test.cpp +++ b/tests/MojangVersionFormat_test.cpp @@ -1,7 +1,7 @@ #include #include -#include "minecraft/MojangVersionFormat.h" +#include class MojangVersionFormatTest : public QObject { @@ -29,7 +29,7 @@ private slots: void test_Through_Simple() { - QJsonDocument doc = readJson(QFINDTESTDATA("testdata/1.9-simple.json")); + QJsonDocument doc = readJson(QFINDTESTDATA("testdata/MojangVersionFormat/1.9-simple.json")); auto vfile = MojangVersionFormat::versionFileFromJson(doc, "1.9-simple.json"); auto doc2 = MojangVersionFormat::versionFileToJson(vfile); writeJson("1.9-simple-passthorugh.json", doc2); @@ -39,7 +39,7 @@ slots: void test_Through() { - QJsonDocument doc = readJson(QFINDTESTDATA("testdata/1.9.json")); + QJsonDocument doc = readJson(QFINDTESTDATA("testdata/MojangVersionFormat/1.9.json")); auto vfile = MojangVersionFormat::versionFileFromJson(doc, "1.9.json"); auto doc2 = MojangVersionFormat::versionFileToJson(vfile); writeJson("1.9-passthorugh.json", doc2); diff --git a/launcher/mojang/PackageManifest_test.cpp b/tests/PackageManifest_test.cpp similarity index 97% rename from launcher/mojang/PackageManifest_test.cpp rename to tests/PackageManifest_test.cpp index e8da4266..e38abf80 100644 --- a/launcher/mojang/PackageManifest_test.cpp +++ b/tests/PackageManifest_test.cpp @@ -1,7 +1,7 @@ #include #include -#include "mojang/PackageManifest.h" +#include using namespace mojang_files; @@ -82,14 +82,14 @@ void PackageManifestTest::test_parse() } void PackageManifestTest::test_parse_file() { - auto path = QFINDTESTDATA("testdata/1.8.0_202-x64.json"); + auto path = QFINDTESTDATA("testdata/PackageManifest/1.8.0_202-x64.json"); auto manifest = Package::fromManifestFile(path); QVERIFY(manifest.valid == true); } void PackageManifestTest::test_inspect() { - auto path = QFINDTESTDATA("testdata/inspect_win/"); + auto path = QFINDTESTDATA("testdata/PackageManifest/inspect_win/"); auto manifest = Package::fromInspectedFolder(path); QVERIFY(manifest.valid == true); QVERIFY(manifest.files.size() == 2); @@ -112,7 +112,7 @@ void PackageManifestTest::test_inspect() { #ifndef Q_OS_WIN32 void PackageManifestTest::test_inspect_symlinks() { - auto path = QFINDTESTDATA("testdata/inspect/"); + auto path = QFINDTESTDATA("testdata/PackageManifest/inspect/"); auto manifest = Package::fromInspectedFolder(path); QVERIFY(manifest.valid == true); QVERIFY(manifest.files.size() == 1); diff --git a/launcher/modplatform/packwiz/Packwiz_test.cpp b/tests/Packwiz_test.cpp similarity index 94% rename from launcher/modplatform/packwiz/Packwiz_test.cpp rename to tests/Packwiz_test.cpp index aa0c35df..098e8f89 100644 --- a/launcher/modplatform/packwiz/Packwiz_test.cpp +++ b/tests/Packwiz_test.cpp @@ -20,7 +20,7 @@ #include #include -#include "Packwiz.h" +#include class PackwizTest : public QObject { Q_OBJECT @@ -29,7 +29,7 @@ class PackwizTest : public QObject { // Files taken from https://github.com/packwiz/packwiz-example-pack void loadFromFile_Modrinth() { - QString source = QFINDTESTDATA("testdata"); + QString source = QFINDTESTDATA("testdata/Packwiz"); QDir index_dir(source); QString slug_mod("borderless-mining"); @@ -55,7 +55,7 @@ class PackwizTest : public QObject { void loadFromFile_Curseforge() { - QString source = QFINDTESTDATA("testdata"); + QString source = QFINDTESTDATA("testdata/Packwiz"); QDir index_dir(source); QString name_mod("screenshot-to-clipboard-fabric.pw.toml"); diff --git a/launcher/minecraft/ParseUtils_test.cpp b/tests/ParseUtils_test.cpp similarity index 96% rename from launcher/minecraft/ParseUtils_test.cpp rename to tests/ParseUtils_test.cpp index 7721a46d..02208fdf 100644 --- a/launcher/minecraft/ParseUtils_test.cpp +++ b/tests/ParseUtils_test.cpp @@ -1,6 +1,6 @@ #include -#include "minecraft/ParseUtils.h" +#include class ParseUtilsTest : public QObject { diff --git a/launcher/minecraft/mod/ResourceFolderModel_test.cpp b/tests/ResourceFolderModel_test.cpp similarity index 92% rename from launcher/minecraft/mod/ResourceFolderModel_test.cpp rename to tests/ResourceFolderModel_test.cpp index aa78e502..3f0f3ba1 100644 --- a/launcher/minecraft/mod/ResourceFolderModel_test.cpp +++ b/tests/ResourceFolderModel_test.cpp @@ -37,10 +37,10 @@ #include #include -#include "FileSystem.h" +#include -#include "minecraft/mod/ModFolderModel.h" -#include "minecraft/mod/ResourceFolderModel.h" +#include +#include #define EXEC_UPDATE_TASK(EXEC, VERIFY) \ QEventLoop loop; \ @@ -70,7 +70,7 @@ slots: void test_1178() { // source - QString source = QFINDTESTDATA("testdata/test_folder"); + QString source = QFINDTESTDATA("testdata/ResourceFolderModel/test_folder"); // sanity check QVERIFY(!source.endsWith('/')); @@ -135,7 +135,7 @@ slots: void test_addFromWatch() { - QString source = QFINDTESTDATA("testdata"); + QString source = QFINDTESTDATA("testdata/ResourceFolderModel"); ModFolderModel model(source); @@ -154,8 +154,8 @@ slots: void test_removeResource() { - QString folder_resource = QFINDTESTDATA("testdata/test_folder"); - QString file_mod = QFINDTESTDATA("testdata/supercoolmod.jar"); + QString folder_resource = QFINDTESTDATA("testdata/ResourceFolderModel/test_folder"); + QString file_mod = QFINDTESTDATA("testdata/ResourceFolderModel/supercoolmod.jar"); QTemporaryDir tmp; @@ -206,8 +206,8 @@ slots: void test_enable_disable() { - QString folder_resource = QFINDTESTDATA("testdata/test_folder"); - QString file_mod = QFINDTESTDATA("testdata/supercoolmod.jar"); + QString folder_resource = QFINDTESTDATA("testdata/ResourceFolderModel/test_folder"); + QString file_mod = QFINDTESTDATA("testdata/ResourceFolderModel/supercoolmod.jar"); QTemporaryDir tmp; ResourceFolderModel model(tmp.path()); diff --git a/launcher/minecraft/mod/ResourcePackParse_test.cpp b/tests/ResourcePackParse_test.cpp similarity index 86% rename from launcher/minecraft/mod/ResourcePackParse_test.cpp rename to tests/ResourcePackParse_test.cpp index a49582d6..568c3b63 100644 --- a/launcher/minecraft/mod/ResourcePackParse_test.cpp +++ b/tests/ResourcePackParse_test.cpp @@ -19,10 +19,10 @@ #include #include -#include "FileSystem.h" +#include -#include "ResourcePack.h" -#include "tasks/LocalResourcePackParseTask.h" +#include +#include class ResourcePackParseTest : public QObject { Q_OBJECT @@ -30,7 +30,7 @@ class ResourcePackParseTest : public QObject { private slots: void test_parseZIP() { - QString source = QFINDTESTDATA("testdata"); + QString source = QFINDTESTDATA("testdata/ResourcePackParse"); QString zip_rp = FS::PathCombine(source, "test_resource_pack_idk.zip"); ResourcePack pack { QFileInfo(zip_rp) }; @@ -43,7 +43,7 @@ class ResourcePackParseTest : public QObject { void test_parseFolder() { - QString source = QFINDTESTDATA("testdata"); + QString source = QFINDTESTDATA("testdata/ResourcePackParse"); QString folder_rp = FS::PathCombine(source, "test_folder"); ResourcePack pack { QFileInfo(folder_rp) }; @@ -56,7 +56,7 @@ class ResourcePackParseTest : public QObject { void test_parseFolder2() { - QString source = QFINDTESTDATA("testdata"); + QString source = QFINDTESTDATA("testdata/ResourcePackParse"); QString folder_rp = FS::PathCombine(source, "another_test_folder"); ResourcePack pack { QFileInfo(folder_rp) }; diff --git a/launcher/tasks/Task_test.cpp b/tests/Task_test.cpp similarity index 97% rename from launcher/tasks/Task_test.cpp rename to tests/Task_test.cpp index b56ee8a6..80bba02f 100644 --- a/launcher/tasks/Task_test.cpp +++ b/tests/Task_test.cpp @@ -1,9 +1,9 @@ #include -#include "ConcurrentTask.h" -#include "MultipleOptionsTask.h" -#include "SequentialTask.h" -#include "Task.h" +#include +#include +#include +#include /* Does nothing. Only used for testing. */ class BasicTask : public Task { diff --git a/launcher/Version_test.cpp b/tests/Version_test.cpp similarity index 99% rename from launcher/Version_test.cpp rename to tests/Version_test.cpp index b2d657a6..734528b7 100644 --- a/launcher/Version_test.cpp +++ b/tests/Version_test.cpp @@ -15,7 +15,7 @@ #include -#include "TestUtil.h" +#include #include class ModUtilsTest : public QObject diff --git a/launcher/testdata/FileSystem-test_createShortcut-unix b/tests/testdata/FileSystem/FileSystem-test_createShortcut-unix similarity index 100% rename from launcher/testdata/FileSystem-test_createShortcut-unix rename to tests/testdata/FileSystem/FileSystem-test_createShortcut-unix diff --git a/launcher/minecraft/mod/testdata/test_folder/assets/minecraft/textures/blah.txt b/tests/testdata/FileSystem/test_folder/assets/minecraft/textures/blah.txt similarity index 100% rename from launcher/minecraft/mod/testdata/test_folder/assets/minecraft/textures/blah.txt rename to tests/testdata/FileSystem/test_folder/assets/minecraft/textures/blah.txt diff --git a/launcher/minecraft/mod/testdata/test_folder/pack.mcmeta b/tests/testdata/FileSystem/test_folder/pack.mcmeta similarity index 100% rename from launcher/minecraft/mod/testdata/test_folder/pack.mcmeta rename to tests/testdata/FileSystem/test_folder/pack.mcmeta diff --git a/launcher/minecraft/mod/testdata/test_folder/pack.nfo b/tests/testdata/FileSystem/test_folder/pack.nfo similarity index 100% rename from launcher/minecraft/mod/testdata/test_folder/pack.nfo rename to tests/testdata/FileSystem/test_folder/pack.nfo diff --git a/tests/testdata/Library b/tests/testdata/Library new file mode 120000 index 00000000..0e7a2286 --- /dev/null +++ b/tests/testdata/Library @@ -0,0 +1 @@ +MojangVersionFormat/ \ No newline at end of file diff --git a/launcher/minecraft/testdata/1.9-simple.json b/tests/testdata/MojangVersionFormat/1.9-simple.json similarity index 100% rename from launcher/minecraft/testdata/1.9-simple.json rename to tests/testdata/MojangVersionFormat/1.9-simple.json diff --git a/launcher/minecraft/testdata/1.9.json b/tests/testdata/MojangVersionFormat/1.9.json similarity index 100% rename from launcher/minecraft/testdata/1.9.json rename to tests/testdata/MojangVersionFormat/1.9.json diff --git a/launcher/minecraft/testdata/codecwav-20101023.jar b/tests/testdata/MojangVersionFormat/codecwav-20101023.jar similarity index 100% rename from launcher/minecraft/testdata/codecwav-20101023.jar rename to tests/testdata/MojangVersionFormat/codecwav-20101023.jar diff --git a/launcher/minecraft/testdata/lib-native-arch.json b/tests/testdata/MojangVersionFormat/lib-native-arch.json similarity index 100% rename from launcher/minecraft/testdata/lib-native-arch.json rename to tests/testdata/MojangVersionFormat/lib-native-arch.json diff --git a/launcher/minecraft/testdata/lib-native.json b/tests/testdata/MojangVersionFormat/lib-native.json similarity index 100% rename from launcher/minecraft/testdata/lib-native.json rename to tests/testdata/MojangVersionFormat/lib-native.json diff --git a/launcher/minecraft/testdata/lib-simple.json b/tests/testdata/MojangVersionFormat/lib-simple.json similarity index 100% rename from launcher/minecraft/testdata/lib-simple.json rename to tests/testdata/MojangVersionFormat/lib-simple.json diff --git a/launcher/minecraft/testdata/testname-testversion-linux-32.jar b/tests/testdata/MojangVersionFormat/testname-testversion-linux-32.jar similarity index 100% rename from launcher/minecraft/testdata/testname-testversion-linux-32.jar rename to tests/testdata/MojangVersionFormat/testname-testversion-linux-32.jar diff --git a/launcher/mojang/testdata/1.8.0_202-x64.json b/tests/testdata/PackageManifest/1.8.0_202-x64.json similarity index 100% rename from launcher/mojang/testdata/1.8.0_202-x64.json rename to tests/testdata/PackageManifest/1.8.0_202-x64.json diff --git a/launcher/mojang/testdata/inspect/a/b.txt b/tests/testdata/PackageManifest/inspect/a/b.txt similarity index 100% rename from launcher/mojang/testdata/inspect/a/b.txt rename to tests/testdata/PackageManifest/inspect/a/b.txt diff --git a/launcher/mojang/testdata/inspect/a/b/b.txt b/tests/testdata/PackageManifest/inspect/a/b/b.txt similarity index 100% rename from launcher/mojang/testdata/inspect/a/b/b.txt rename to tests/testdata/PackageManifest/inspect/a/b/b.txt diff --git a/launcher/mojang/testdata/inspect_win/a/b.txt b/tests/testdata/PackageManifest/inspect_win/a/b.txt similarity index 100% rename from launcher/mojang/testdata/inspect_win/a/b.txt rename to tests/testdata/PackageManifest/inspect_win/a/b.txt diff --git a/launcher/mojang/testdata/inspect_win/a/b/b.txt b/tests/testdata/PackageManifest/inspect_win/a/b/b.txt similarity index 100% rename from launcher/mojang/testdata/inspect_win/a/b/b.txt rename to tests/testdata/PackageManifest/inspect_win/a/b/b.txt diff --git a/launcher/modplatform/packwiz/testdata/borderless-mining.pw.toml b/tests/testdata/Packwiz/borderless-mining.pw.toml similarity index 100% rename from launcher/modplatform/packwiz/testdata/borderless-mining.pw.toml rename to tests/testdata/Packwiz/borderless-mining.pw.toml diff --git a/launcher/modplatform/packwiz/testdata/screenshot-to-clipboard-fabric.pw.toml b/tests/testdata/Packwiz/screenshot-to-clipboard-fabric.pw.toml similarity index 100% rename from launcher/modplatform/packwiz/testdata/screenshot-to-clipboard-fabric.pw.toml rename to tests/testdata/Packwiz/screenshot-to-clipboard-fabric.pw.toml diff --git a/tests/testdata/ResourceFolderModel b/tests/testdata/ResourceFolderModel new file mode 120000 index 00000000..c653d859 --- /dev/null +++ b/tests/testdata/ResourceFolderModel @@ -0,0 +1 @@ +ResourcePackParse \ No newline at end of file diff --git a/launcher/minecraft/mod/testdata/another_test_folder/pack.mcmeta b/tests/testdata/ResourcePackParse/another_test_folder/pack.mcmeta similarity index 100% rename from launcher/minecraft/mod/testdata/another_test_folder/pack.mcmeta rename to tests/testdata/ResourcePackParse/another_test_folder/pack.mcmeta diff --git a/launcher/minecraft/mod/testdata/supercoolmod.jar b/tests/testdata/ResourcePackParse/supercoolmod.jar similarity index 100% rename from launcher/minecraft/mod/testdata/supercoolmod.jar rename to tests/testdata/ResourcePackParse/supercoolmod.jar diff --git a/launcher/testdata/test_folder/assets/minecraft/textures/blah.txt b/tests/testdata/ResourcePackParse/test_folder/assets/minecraft/textures/blah.txt similarity index 100% rename from launcher/testdata/test_folder/assets/minecraft/textures/blah.txt rename to tests/testdata/ResourcePackParse/test_folder/assets/minecraft/textures/blah.txt diff --git a/launcher/testdata/test_folder/pack.mcmeta b/tests/testdata/ResourcePackParse/test_folder/pack.mcmeta similarity index 100% rename from launcher/testdata/test_folder/pack.mcmeta rename to tests/testdata/ResourcePackParse/test_folder/pack.mcmeta diff --git a/launcher/testdata/test_folder/pack.nfo b/tests/testdata/ResourcePackParse/test_folder/pack.nfo similarity index 100% rename from launcher/testdata/test_folder/pack.nfo rename to tests/testdata/ResourcePackParse/test_folder/pack.nfo diff --git a/launcher/minecraft/mod/testdata/test_resource_pack_idk.zip b/tests/testdata/ResourcePackParse/test_resource_pack_idk.zip similarity index 100% rename from launcher/minecraft/mod/testdata/test_resource_pack_idk.zip rename to tests/testdata/ResourcePackParse/test_resource_pack_idk.zip From 69d18f17a5a64a68ba6070fcda8dbfb46ca88aff Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Mon, 12 Sep 2022 12:57:18 +0200 Subject: [PATCH 169/273] fix(actions, win): only copy openssl libs on qt5 builds (#1130) --- .github/workflows/build.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 26820d47..434c5775 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -249,10 +249,8 @@ jobs: cmake --install ${{ env.BUILD_DIR }} cd ${{ env.INSTALL_DIR }} - if [ "${{ matrix.msystem }}" == "mingw32" ]; then + if [ "${{ matrix.qt_ver }}" == "5" ]; then cp /mingw32/bin/libcrypto-1_1.dll /mingw32/bin/libssl-1_1.dll ./ - elif [ "${{ matrix.msystem }}" == "mingw64" ]; then - cp /mingw64/bin/libcrypto-1_1-x64.dll /mingw64/bin/libssl-1_1-x64.dll ./ fi - name: Package (Windows, portable) From 5932f36285ebf3ec7d852db1b80630589224eccc Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 30 Jul 2022 19:38:56 -0300 Subject: [PATCH 170/273] refactor: use std::filesystem for file copy Signed-off-by: flow --- launcher/FileSystem.cpp | 74 ++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 38 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 8eeb2885..e4db6d35 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -37,6 +37,7 @@ #include #include +#include #include #include #include @@ -58,6 +59,8 @@ #include #endif +#include + namespace FS { void ensureExists(const QDir& dir) @@ -128,6 +131,8 @@ bool ensureFolderPathExists(QString foldernamepath) bool copy::operator()(const QString& offset) { + using copy_opts = std::filesystem::copy_options; + // NOTE always deep copy on windows. the alternatives are too messy. #if defined Q_OS_WIN32 m_followSymlinks = true; @@ -136,47 +141,40 @@ bool copy::operator()(const QString& offset) auto src = PathCombine(m_src.absolutePath(), offset); auto dst = PathCombine(m_dst.absolutePath(), offset); - QFileInfo currentSrc(src); - if (!currentSrc.exists()) - return false; + std::error_code err; - if (!m_followSymlinks && currentSrc.isSymLink()) { - qDebug() << "creating symlink" << src << " - " << dst; - if (!ensureFilePathExists(dst)) { - qWarning() << "Cannot create path!"; - return false; + std::filesystem::copy_options opt = copy_opts::none; + + // The default behavior is to follow symlinks + if (!m_followSymlinks) + opt |= copy_opts::copy_symlinks; + + + // We can't use copy_opts::recursive because we need to take into account the + // blacklisted paths, so we iterate over the source directory, and if there's no blacklist + // match, we copy the file. + QDir src_dir(src); + QDirIterator source_it(src, QDir::Filter::Files, QDirIterator::Subdirectories); + + while (source_it.hasNext()) { + auto src_path = source_it.next(); + auto relative_path = src_dir.relativeFilePath(src_path); + + if (m_blacklist && m_blacklist->matches(relative_path)) + continue; + + auto dst_path = PathCombine(dst, relative_path); + ensureFilePathExists(dst_path); + + std::filesystem::copy(src_path.toStdString(), dst_path.toStdString(), opt, err); + if (err) { + qWarning() << "Failed to copy files:" << QString::fromStdString(err.message()); + qDebug() << "Source file:" << src_path; + qDebug() << "Destination file:" << dst_path; } - return QFile::link(currentSrc.symLinkTarget(), dst); - } else if (currentSrc.isFile()) { - qDebug() << "copying file" << src << " - " << dst; - if (!ensureFilePathExists(dst)) { - qWarning() << "Cannot create path!"; - return false; - } - return QFile::copy(src, dst); - } else if (currentSrc.isDir()) { - qDebug() << "recursing" << offset; - if (!ensureFolderPathExists(dst)) { - qWarning() << "Cannot create path!"; - return false; - } - QDir currentDir(src); - for (auto& f : currentDir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot | QDir::Hidden | QDir::System)) { - auto inner_offset = PathCombine(offset, f); - // ignore and skip stuff that matches the blacklist. - if (m_blacklist && m_blacklist->matches(inner_offset)) { - continue; - } - if (!operator()(inner_offset)) { - qWarning() << "Failed to copy" << inner_offset; - return false; - } - } - } else { - qCritical() << "Copy ERROR: Unknown filesystem object:" << src; - return false; } - return true; + + return err.value() == 0; } bool deletePath(QString path) From be3fae65113023d77d2df59f9fd765a4c83f07f2 Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 30 Jul 2022 20:40:25 -0300 Subject: [PATCH 171/273] refactor: use std::filesystem for path deletion Signed-off-by: flow --- launcher/FileSystem.cpp | 48 ++++++----------------------------------- 1 file changed, 7 insertions(+), 41 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index e4db6d35..c1baf20a 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -179,49 +179,15 @@ bool copy::operator()(const QString& offset) bool deletePath(QString path) { - bool OK = true; - QFileInfo finfo(path); - if (finfo.isFile()) { - return QFile::remove(path); + std::error_code err; + + std::filesystem::remove_all(path.toStdString(), err); + + if (err) { + qWarning() << "Failed to remove files:" << QString::fromStdString(err.message()); } - QDir dir(path); - - if (!dir.exists()) { - return OK; - } - auto allEntries = dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden | QDir::AllDirs | QDir::Files, QDir::DirsFirst); - - for (auto& info : allEntries) { -#if defined Q_OS_WIN32 - QString nativePath = QDir::toNativeSeparators(info.absoluteFilePath()); - auto wString = nativePath.toStdWString(); - DWORD dwAttrs = GetFileAttributesW(wString.c_str()); - // Windows: check for junctions, reparse points and other nasty things of that sort - if (dwAttrs & FILE_ATTRIBUTE_REPARSE_POINT) { - if (info.isFile()) { - OK &= QFile::remove(info.absoluteFilePath()); - } else if (info.isDir()) { - OK &= dir.rmdir(info.absoluteFilePath()); - } - } -#else - // We do not trust Qt with reparse points, but do trust it with unix symlinks. - if (info.isSymLink()) { - OK &= QFile::remove(info.absoluteFilePath()); - } -#endif - else if (info.isDir()) { - OK &= deletePath(info.absoluteFilePath()); - } else if (info.isFile()) { - OK &= QFile::remove(info.absoluteFilePath()); - } else { - OK = false; - qCritical() << "Delete ERROR: Unknown filesystem object:" << info.absoluteFilePath(); - } - } - OK &= dir.rmdir(dir.absolutePath()); - return OK; + return err.value() == 0; } bool trash(QString path, QString *pathInTrash = nullptr) From 1cf949226e786e827de2d477516c5a14ecfbd6bd Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 7 Aug 2022 08:50:39 -0300 Subject: [PATCH 172/273] refactor: use std::filesystem for overrides Signed-off-by: flow --- launcher/FileSystem.cpp | 41 +++++++++-------------------------------- 1 file changed, 9 insertions(+), 32 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index c1baf20a..fefa6dd1 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -403,47 +403,24 @@ bool createShortCut(QString location, QString dest, QStringList args, QString na #endif } -QStringList listFolderPaths(QDir root) -{ - auto createAbsPath = [](QFileInfo const& entry) { return FS::PathCombine(entry.path(), entry.fileName()); }; - - QStringList entries; - - root.refresh(); - for (auto entry : root.entryInfoList(QDir::Filter::Files)) { - entries.append(createAbsPath(entry)); - } - - for (auto entry : root.entryInfoList(QDir::Filter::AllDirs | QDir::Filter::NoDotAndDotDot)) { - entries.append(listFolderPaths(createAbsPath(entry))); - } - - return entries; -} - bool overrideFolder(QString overwritten_path, QString override_path) { + using copy_opts = std::filesystem::copy_options; + if (!FS::ensureFolderPathExists(overwritten_path)) return false; - QStringList paths_to_override; - QDir root_override (override_path); - for (auto file : listFolderPaths(root_override)) { - QString destination = file; - destination.replace(override_path, overwritten_path); - ensureFilePathExists(destination); + std::error_code err; + std::filesystem::copy_options opt = copy_opts::recursive | copy_opts::overwrite_existing; - qDebug() << QString("Applying override %1 in %2").arg(file, destination); + std::filesystem::copy(override_path.toStdString(), overwritten_path.toStdString(), opt, err); - if (QFile::exists(destination)) - QFile::remove(destination); - if (!QFile::rename(file, destination)) { - qCritical() << QString("Failed to apply override from %1 to %2").arg(file, destination); - return false; - } + if (err) { + qCritical() << QString("Failed to apply override from %1 to %2").arg(override_path, overwritten_path); + qCritical() << "Reason:" << QString::fromStdString(err.message()); } - return true; + return err.value() == 0; } } From 277fa21f5f7dcdec1bde11f345136f464ee8d37a Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 7 Aug 2022 07:18:37 -0300 Subject: [PATCH 173/273] refactor: remove Win32 'crap' in FileSystem We should use std::filesystem symlink and hardlink functions instead. Signed-off-by: flow --- launcher/FileSystem.cpp | 44 ----------------------------------------- 1 file changed, 44 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index fefa6dd1..8966413a 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -300,50 +300,6 @@ bool checkProblemticPathJava(QDir folder) return pathfoldername.contains("!", Qt::CaseInsensitive); } -// Win32 crap -#ifdef Q_OS_WIN - -bool called_coinit = false; - -HRESULT CreateLink(LPCCH linkPath, LPCWSTR targetPath, LPCWSTR args) -{ - HRESULT hres; - - if (!called_coinit) { - hres = CoInitialize(NULL); - called_coinit = true; - - if (!SUCCEEDED(hres)) { - qWarning("Failed to initialize COM. Error 0x%08lX", hres); - return hres; - } - } - - IShellLink* link; - hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&link); - - if (SUCCEEDED(hres)) { - IPersistFile* persistFile; - - link->SetPath(targetPath); - link->SetArguments(args); - - hres = link->QueryInterface(IID_IPersistFile, (LPVOID*)&persistFile); - if (SUCCEEDED(hres)) { - WCHAR wstr[MAX_PATH]; - - MultiByteToWideChar(CP_ACP, 0, linkPath, -1, wstr, MAX_PATH); - - hres = persistFile->Save(wstr, TRUE); - persistFile->Release(); - } - link->Release(); - } - return hres; -} - -#endif - QString getDesktopDir() { return QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); From c496ad1237198cf9c5a97b3b02c2897b5ac1990a Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 7 Aug 2022 07:19:29 -0300 Subject: [PATCH 174/273] chore: make DirNameFromString add normal duplicate identifier Wrap the number in parenthesis to be similar to other software. Signed-off-by: flow --- launcher/FileSystem.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 8966413a..2409696b 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -280,8 +280,7 @@ QString DirNameFromString(QString string, QString inDir) if (num == 0) { dirName = baseName; } else { - dirName = baseName + QString::number(num); - ; + dirName = baseName + "(" + QString::number(num) + ")"; } // If it's over 9000 From ee0fb2d0e00bba8c000a277d5f7199ae46a095df Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 7 Aug 2022 14:30:38 -0300 Subject: [PATCH 175/273] fix: use std::wstring for Windows filenames Signed-off-by: flow --- launcher/FileSystem.cpp | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 2409696b..5d179641 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -61,6 +61,22 @@ #include +#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) @@ -166,7 +182,7 @@ bool copy::operator()(const QString& offset) auto dst_path = PathCombine(dst, relative_path); ensureFilePathExists(dst_path); - std::filesystem::copy(src_path.toStdString(), dst_path.toStdString(), opt, err); + std::filesystem::copy(toStdString(src_path), toStdString(dst_path), opt, err); if (err) { qWarning() << "Failed to copy files:" << QString::fromStdString(err.message()); qDebug() << "Source file:" << src_path; @@ -181,7 +197,7 @@ bool deletePath(QString path) { std::error_code err; - std::filesystem::remove_all(path.toStdString(), err); + std::filesystem::remove_all(toStdString(path), err); if (err) { qWarning() << "Failed to remove files:" << QString::fromStdString(err.message()); @@ -368,7 +384,7 @@ bool overrideFolder(QString overwritten_path, QString override_path) std::error_code err; std::filesystem::copy_options opt = copy_opts::recursive | copy_opts::overwrite_existing; - std::filesystem::copy(override_path.toStdString(), overwritten_path.toStdString(), opt, err); + std::filesystem::copy(toStdString(override_path), toStdString(overwritten_path), opt, err); if (err) { qCritical() << QString("Failed to apply override from %1 to %2").arg(override_path, overwritten_path); From 931d6c280a877cefddcc93660aba0c1c0c014366 Mon Sep 17 00:00:00 2001 From: flow Date: Mon, 12 Sep 2022 13:12:55 -0300 Subject: [PATCH 176/273] chore(tests): add test for copy operation with blacklist I almost :skull: because no tests used this x.x Signed-off-by: flow --- tests/FileSystem_test.cpp | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/tests/FileSystem_test.cpp b/tests/FileSystem_test.cpp index 6df13e80..4efb90ac 100644 --- a/tests/FileSystem_test.cpp +++ b/tests/FileSystem_test.cpp @@ -4,6 +4,8 @@ #include +#include + class FileSystemTest : public QObject { Q_OBJECT @@ -111,6 +113,40 @@ slots: f(); } + void test_copy_with_blacklist() + { + QString folder = QFINDTESTDATA("testdata/FileSystem/test_folder"); + auto f = [&folder]() + { + QTemporaryDir tempDir; + tempDir.setAutoRemove(true); + qDebug() << "From:" << folder << "To:" << tempDir.path(); + + QDir target_dir(FS::PathCombine(tempDir.path(), "test_folder")); + qDebug() << tempDir.path(); + qDebug() << target_dir.path(); + FS::copy c(folder, target_dir.path()); + c.blacklist(new RegexpMatcher("[.]?mcmeta")); + c(); + + for(auto entry: target_dir.entryList()) + { + qDebug() << entry; + } + QVERIFY(!target_dir.entryList().contains("pack.mcmeta")); + QVERIFY(target_dir.entryList().contains("assets")); + }; + + // first try variant without trailing / + QVERIFY(!folder.endsWith('/')); + f(); + + // then variant with trailing / + folder.append('/'); + QVERIFY(folder.endsWith('/')); + f(); + } + void test_getDesktop() { QCOMPARE(FS::getDesktopDir(), QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); From 8c41ff68f7b09957c8b2f63c7dbdc1045a7fc756 Mon Sep 17 00:00:00 2001 From: flow Date: Mon, 12 Sep 2022 18:41:13 -0300 Subject: [PATCH 177/273] chore(actions)!: bump macOS required version to 10.15 This is needed for std::filesystem support in macOS's libc. Signed-off-by: flow --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 434c5775..ae8947ab 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,7 +39,7 @@ jobs: qt_ver: 6 - os: macos-12 - macosx_deployment_target: 10.14 + macosx_deployment_target: 10.15 qt_ver: 6 qt_host: mac qt_version: '6.3.0' From 684b8f24f3fb99659f474a8cd1319c6aaeba1655 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Thu, 15 Sep 2022 09:29:48 +0200 Subject: [PATCH 178/273] fix: allow starting rd- versions Using `Collections.emptyList()` doesn't allow us to later append stuff into that list. Use an empty `ArrayList` instead. Signed-off-by: Sefa Eyeoglu --- libraries/launcher/org/polymc/impl/OneSixLauncher.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/launcher/org/polymc/impl/OneSixLauncher.java b/libraries/launcher/org/polymc/impl/OneSixLauncher.java index 362ff8d6..4ab46678 100644 --- a/libraries/launcher/org/polymc/impl/OneSixLauncher.java +++ b/libraries/launcher/org/polymc/impl/OneSixLauncher.java @@ -24,8 +24,8 @@ import java.applet.Applet; import java.io.File; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.Collections; import java.util.List; +import java.util.ArrayList; import java.util.logging.Level; import java.util.logging.Logger; @@ -58,10 +58,10 @@ public final class OneSixLauncher implements Launcher { public OneSixLauncher(Parameters params) { classLoader = ClassLoader.getSystemClassLoader(); - mcParams = params.allSafe("param", Collections.emptyList()); + mcParams = params.allSafe("param", new ArrayList()); mainClass = params.firstSafe("mainClass", "net.minecraft.client.Minecraft"); appletClass = params.firstSafe("appletClass", "net.minecraft.client.MinecraftApplet"); - traits = params.allSafe("traits", Collections.emptyList()); + traits = params.allSafe("traits", new ArrayList()); userName = params.first("userName"); sessionId = params.first("sessionId"); From 29dcb9d2747c61b20fe9f870574bc9c4c958e82a Mon Sep 17 00:00:00 2001 From: jopejoe1 Date: Mon, 11 Jul 2022 20:46:11 +0200 Subject: [PATCH 179/273] Added Launch Demo button. Signed-off-by: jopejoe1 --- launcher/Application.cpp | 5 +- launcher/Application.h | 1 + launcher/LaunchController.cpp | 9 +++- launcher/LaunchController.h | 5 ++ launcher/ui/InstanceWindow.cpp | 18 ++++++- launcher/ui/InstanceWindow.h | 2 + launcher/ui/MainWindow.cpp | 55 ++++++++++++++++++++-- launcher/ui/MainWindow.h | 2 + launcher/ui/pages/instance/ServersPage.cpp | 2 +- 9 files changed, 90 insertions(+), 9 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index c4179b49..0d1db11c 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -1041,7 +1041,7 @@ void Application::performMainStartupAction() qDebug() << " Launching with account" << m_profileToUse; } - launch(inst, true, nullptr, serverToJoin, accountToUse); + launch(inst, true, false, nullptr, serverToJoin, accountToUse); return; } } @@ -1145,6 +1145,7 @@ void Application::messageReceived(const QByteArray& message) launch( instance, true, + false, nullptr, serverObject, accountObject @@ -1245,6 +1246,7 @@ bool Application::openJsonEditor(const QString &filename) bool Application::launch( InstancePtr instance, bool online, + bool demo, BaseProfilerFactory *profiler, MinecraftServerTargetPtr serverToJoin, MinecraftAccountPtr accountToUse @@ -1268,6 +1270,7 @@ bool Application::launch( controller.reset(new LaunchController()); controller->setInstance(instance); controller->setOnline(online); + controller->setDemo(demo); controller->setProfiler(profiler); controller->setServerToJoin(serverToJoin); controller->setAccountToUse(accountToUse); diff --git a/launcher/Application.h b/launcher/Application.h index 41fd4c47..34ad8c15 100644 --- a/launcher/Application.h +++ b/launcher/Application.h @@ -213,6 +213,7 @@ public slots: bool launch( InstancePtr instance, bool online = true, + bool demo = false, BaseProfilerFactory *profiler = nullptr, MinecraftServerTargetPtr serverToJoin = nullptr, MinecraftAccountPtr accountToUse = nullptr diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 11f9b2bb..b4a58d01 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -167,6 +167,7 @@ void LaunchController::login() { tries++; m_session = std::make_shared(); m_session->wants_online = m_online; + m_session->demo = m_demo; m_accountToUse->fillSession(m_session); // Launch immediately in true offline mode @@ -184,12 +185,18 @@ void LaunchController::login() { if(!m_session->wants_online) { // we ask the user for a player name bool ok = false; + + QString message = tr("Choose your offline mode player name."); + if(m_session->demo) { + message = tr("Choose your demo mode player name."); + } + QString lastOfflinePlayerName = APPLICATION->settings()->get("LastOfflinePlayerName").toString(); QString usedname = lastOfflinePlayerName.isEmpty() ? m_session->player_name : lastOfflinePlayerName; QString name = QInputDialog::getText( m_parentWidget, tr("Player name"), - tr("Choose your offline mode player name."), + message, QLineEdit::Normal, usedname, &ok diff --git a/launcher/LaunchController.h b/launcher/LaunchController.h index 2171ad5e..af6c98d1 100644 --- a/launcher/LaunchController.h +++ b/launcher/LaunchController.h @@ -63,6 +63,10 @@ public: m_online = online; } + void setDemo(bool demo) { + m_demo = demo; + } + void setProfiler(BaseProfilerFactory *profiler) { m_profiler = profiler; } @@ -101,6 +105,7 @@ private slots: private: BaseProfilerFactory *m_profiler = nullptr; bool m_online = true; + bool m_demo = false; InstancePtr m_instance; QWidget * m_parentWidget = nullptr; InstanceWindow *m_console = nullptr; diff --git a/launcher/ui/InstanceWindow.cpp b/launcher/ui/InstanceWindow.cpp index 0ad8c594..5bf42bb5 100644 --- a/launcher/ui/InstanceWindow.cpp +++ b/launcher/ui/InstanceWindow.cpp @@ -95,8 +95,14 @@ InstanceWindow::InstanceWindow(InstancePtr instance, QWidget *parent) m_launchOfflineButton = new QPushButton(); horizontalLayout->addWidget(m_launchOfflineButton); m_launchOfflineButton->setText(tr("Launch Offline")); + + m_launchDemoButton = new QPushButton(); + horizontalLayout->addWidget(m_launchDemoButton); + m_launchDemoButton->setText(tr("Launch Demo")); + updateLaunchButtons(); connect(m_launchOfflineButton, SIGNAL(clicked(bool)), SLOT(on_btnLaunchMinecraftOffline_clicked())); + connect(m_launchDemoButton, SIGNAL(clicked(bool)), SLOT(on_btnLaunchMinecraftDemo_clicked())); m_closeButton = new QPushButton(); m_closeButton->setText(tr("Close")); @@ -143,6 +149,7 @@ void InstanceWindow::updateLaunchButtons() if(m_instance->isRunning()) { m_launchOfflineButton->setEnabled(false); + m_launchDemoButton->setEnabled(false); m_killButton->setText(tr("Kill")); m_killButton->setObjectName("killButton"); m_killButton->setToolTip(tr("Kill the running instance")); @@ -150,6 +157,7 @@ void InstanceWindow::updateLaunchButtons() else if(!m_instance->canLaunch()) { m_launchOfflineButton->setEnabled(false); + m_launchDemoButton->setEnabled(false); m_killButton->setText(tr("Launch")); m_killButton->setObjectName("launchButton"); m_killButton->setToolTip(tr("Launch the instance")); @@ -158,6 +166,7 @@ void InstanceWindow::updateLaunchButtons() else { m_launchOfflineButton->setEnabled(true); + m_launchDemoButton->setEnabled(true); m_killButton->setText(tr("Launch")); m_killButton->setObjectName("launchButton"); m_killButton->setToolTip(tr("Launch the instance")); @@ -169,7 +178,12 @@ void InstanceWindow::updateLaunchButtons() void InstanceWindow::on_btnLaunchMinecraftOffline_clicked() { - APPLICATION->launch(m_instance, false, nullptr); + APPLICATION->launch(m_instance, false, false, nullptr); +} + +void InstanceWindow::on_btnLaunchMinecraftDemo_clicked() +{ + APPLICATION->launch(m_instance, false, true, nullptr); } void InstanceWindow::instanceLaunchTaskChanged(shared_qobject_ptr proc) @@ -223,7 +237,7 @@ void InstanceWindow::on_btnKillMinecraft_clicked() } else { - APPLICATION->launch(m_instance, true, nullptr); + APPLICATION->launch(m_instance, true, false, nullptr); } } diff --git a/launcher/ui/InstanceWindow.h b/launcher/ui/InstanceWindow.h index aec07868..554c4c74 100644 --- a/launcher/ui/InstanceWindow.h +++ b/launcher/ui/InstanceWindow.h @@ -74,6 +74,7 @@ slots: void on_closeButton_clicked(); void on_btnKillMinecraft_clicked(); void on_btnLaunchMinecraftOffline_clicked(); + void on_btnLaunchMinecraftDemo_clicked(); void instanceLaunchTaskChanged(shared_qobject_ptr proc); void runningStateChanged(bool running); @@ -93,4 +94,5 @@ private: QPushButton *m_closeButton = nullptr; QPushButton *m_killButton = nullptr; QPushButton *m_launchOfflineButton = nullptr; + QPushButton *m_launchDemoButton = nullptr; }; diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 299401f5..2d8ed24c 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -240,6 +240,7 @@ public: TranslatedAction actionCAT; TranslatedAction actionCopyInstance; TranslatedAction actionLaunchInstanceOffline; + TranslatedAction actionLaunchInstanceDemo; TranslatedAction actionScreenshots; TranslatedAction actionExportInstance; QVector all_actions; @@ -499,6 +500,7 @@ public: fileMenu->addAction(actionAddInstance); fileMenu->addAction(actionLaunchInstance); fileMenu->addAction(actionLaunchInstanceOffline); + fileMenu->addAction(actionLaunchInstanceDemo); fileMenu->addAction(actionKillInstance); fileMenu->addAction(actionCloseWindow); fileMenu->addSeparator(); @@ -669,6 +671,12 @@ public: actionLaunchInstanceOffline.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Launch the selected instance in offline mode.")); all_actions.append(&actionLaunchInstanceOffline); + actionLaunchInstanceDemo = TranslatedAction(MainWindow); + actionLaunchInstanceDemo->setObjectName(QStringLiteral("actionLaunchInstanceDemo")); + actionLaunchInstanceDemo.setTextId(QT_TRANSLATE_NOOP("MainWindow", "Launch &Demo")); + actionLaunchInstanceDemo.setTooltipId(QT_TRANSLATE_NOOP("MainWindow", "Launch the selected instance in demo mode.")); + all_actions.append(&actionLaunchInstanceDemo); + actionKillInstance = TranslatedAction(MainWindow); actionKillInstance->setObjectName(QStringLiteral("actionKillInstance")); actionKillInstance->setDisabled(true); @@ -785,6 +793,7 @@ public: instanceToolBar->addAction(actionLaunchInstance); instanceToolBar->addAction(actionLaunchInstanceOffline); + instanceToolBar->addAction(actionLaunchInstanceDemo); instanceToolBar->addAction(actionKillInstance); instanceToolBar->addSeparator(); @@ -1190,16 +1199,20 @@ void MainWindow::updateToolsMenu() { QToolButton *launchButton = dynamic_cast(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstance)); QToolButton *launchOfflineButton = dynamic_cast(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstanceOffline)); + QToolButton *launchDemoButton = dynamic_cast(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstanceDemo)); bool currentInstanceRunning = m_selectedInstance && m_selectedInstance->isRunning(); ui->actionLaunchInstance->setDisabled(!m_selectedInstance || currentInstanceRunning); ui->actionLaunchInstanceOffline->setDisabled(!m_selectedInstance || currentInstanceRunning); + ui->actionLaunchInstanceDemo->setDisabled(!m_selectedInstance || currentInstanceRunning); QMenu *launchMenu = ui->actionLaunchInstance->menu(); QMenu *launchOfflineMenu = ui->actionLaunchInstanceOffline->menu(); + QMenu *launchDemoMenu = ui->actionLaunchInstanceDemo->menu(); launchButton->setPopupMode(QToolButton::MenuButtonPopup); launchOfflineButton->setPopupMode(QToolButton::MenuButtonPopup); + launchDemoButton->setPopupMode(QToolButton::MenuButtonPopup); if (launchMenu) { launchMenu->clear(); @@ -1215,66 +1228,90 @@ void MainWindow::updateToolsMenu() { launchOfflineMenu = new QMenu(this); } + if (launchDemoMenu) { + launchDemoMenu->clear(); + } + else + { + launchDemoMenu = new QMenu(this); + } QAction *normalLaunch = launchMenu->addAction(tr("Launch")); normalLaunch->setShortcut(QKeySequence::Open); QAction *normalLaunchOffline = launchOfflineMenu->addAction(tr("Launch Offline")); normalLaunchOffline->setShortcut(QKeySequence(tr("Ctrl+Shift+O"))); + QAction *normalLaunchDemo = launchDemoMenu->addAction(tr("Launch Demo")); if (m_selectedInstance) { normalLaunch->setEnabled(m_selectedInstance->canLaunch()); normalLaunchOffline->setEnabled(m_selectedInstance->canLaunch()); + normalLaunchDemo->setEnabled(m_selectedInstance->canLaunch()); connect(normalLaunch, &QAction::triggered, [this]() { - APPLICATION->launch(m_selectedInstance, true); + APPLICATION->launch(m_selectedInstance, true, false); }); connect(normalLaunchOffline, &QAction::triggered, [this]() { - APPLICATION->launch(m_selectedInstance, false); + APPLICATION->launch(m_selectedInstance, false, false); + }); + connect(normalLaunchDemo, &QAction::triggered, [this]() { + APPLICATION->launch(m_selectedInstance, false, true); }); } else { normalLaunch->setDisabled(true); normalLaunchOffline->setDisabled(true); + normalLaunchDemo->setDisabled(true); } QString profilersTitle = tr("Profilers"); launchMenu->addSeparator()->setText(profilersTitle); launchOfflineMenu->addSeparator()->setText(profilersTitle); + launchDemoMenu->addSeparator()->setText(profilersTitle); for (auto profiler : APPLICATION->profilers().values()) { QAction *profilerAction = launchMenu->addAction(profiler->name()); QAction *profilerOfflineAction = launchOfflineMenu->addAction(profiler->name()); + QAction *profilerDemoAction = launchDemoMenu->addAction(profiler->name()); QString error; if (!profiler->check(&error)) { profilerAction->setDisabled(true); profilerOfflineAction->setDisabled(true); + profilerDemoAction->setDisabled(true); QString profilerToolTip = tr("Profiler not setup correctly. Go into settings, \"External Tools\"."); profilerAction->setToolTip(profilerToolTip); profilerOfflineAction->setToolTip(profilerToolTip); + profilerDemoAction->setToolTip(profilerToolTip); } else if (m_selectedInstance) { profilerAction->setEnabled(m_selectedInstance->canLaunch()); profilerOfflineAction->setEnabled(m_selectedInstance->canLaunch()); + profilerDemoAction->setEnabled(m_selectedInstance->canLaunch()); connect(profilerAction, &QAction::triggered, [this, profiler]() { - APPLICATION->launch(m_selectedInstance, true, profiler.get()); + APPLICATION->launch(m_selectedInstance, true, false, profiler.get()); }); connect(profilerOfflineAction, &QAction::triggered, [this, profiler]() { - APPLICATION->launch(m_selectedInstance, false, profiler.get()); + APPLICATION->launch(m_selectedInstance, false, false, profiler.get()); + }); + connect(profilerDemoAction, &QAction::triggered, [this, profiler]() + { + APPLICATION->launch(m_selectedInstance, false, true, profiler.get()); }); } else { profilerAction->setDisabled(true); profilerOfflineAction->setDisabled(true); + profilerDemoAction->setDisabled(true); } } ui->actionLaunchInstance->setMenu(launchMenu); ui->actionLaunchInstanceOffline->setMenu(launchOfflineMenu); + ui->actionLaunchInstanceDemo->setMenu(launchDemoMenu); } void MainWindow::repopulateAccountsMenu() @@ -2096,6 +2133,14 @@ void MainWindow::on_actionLaunchInstanceOffline_triggered() } } +void MainWindow::on_actionLaunchInstanceDemo_triggered() +{ + if (m_selectedInstance) + { + APPLICATION->launch(m_selectedInstance, false, true); + } +} + void MainWindow::on_actionKillInstance_triggered() { if(m_selectedInstance && m_selectedInstance->isRunning()) @@ -2139,6 +2184,7 @@ void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex & ui->setInstanceActionsEnabled(true); ui->actionLaunchInstance->setEnabled(m_selectedInstance->canLaunch()); ui->actionLaunchInstanceOffline->setEnabled(m_selectedInstance->canLaunch()); + ui->actionLaunchInstanceDemo->setEnabled(m_selectedInstance->canLaunch()); ui->actionKillInstance->setEnabled(m_selectedInstance->isRunning()); ui->actionExportInstance->setEnabled(m_selectedInstance->canExport()); ui->renameButton->setText(m_selectedInstance->name()); @@ -2158,6 +2204,7 @@ void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex & ui->setInstanceActionsEnabled(false); ui->actionLaunchInstance->setEnabled(false); ui->actionLaunchInstanceOffline->setEnabled(false); + ui->actionLaunchInstanceDemo->setEnabled(false); ui->actionKillInstance->setEnabled(false); APPLICATION->settings()->set("SelectedInstance", QString()); selectionBad(); diff --git a/launcher/ui/MainWindow.h b/launcher/ui/MainWindow.h index dde3d02c..8b41bf94 100644 --- a/launcher/ui/MainWindow.h +++ b/launcher/ui/MainWindow.h @@ -140,6 +140,8 @@ private slots: void on_actionLaunchInstanceOffline_triggered(); + void on_actionLaunchInstanceDemo_triggered(); + void on_actionKillInstance_triggered(); void on_actionDeleteInstance_triggered(); diff --git a/launcher/ui/pages/instance/ServersPage.cpp b/launcher/ui/pages/instance/ServersPage.cpp index e5cce96c..5e8bd7cc 100644 --- a/launcher/ui/pages/instance/ServersPage.cpp +++ b/launcher/ui/pages/instance/ServersPage.cpp @@ -811,7 +811,7 @@ void ServersPage::on_actionMove_Down_triggered() void ServersPage::on_actionJoin_triggered() { const auto &address = m_model->at(currentServer)->m_address; - APPLICATION->launch(m_inst, true, nullptr, std::make_shared(MinecraftServerTarget::parse(address))); + APPLICATION->launch(m_inst, true, false, nullptr, std::make_shared(MinecraftServerTarget::parse(address))); } #include "ServersPage.moc" From 5765a1fdf149e350f3d1869d38d27223980de08c Mon Sep 17 00:00:00 2001 From: flow Date: Wed, 13 Jul 2022 15:45:41 -0300 Subject: [PATCH 180/273] fix: allow demo for older versions We were not propagating the '--demo' flag in the legacy launcher, unconditionally setting the 'demo' parameter to false. Signed-off-by: flow --- libraries/launcher/org/polymc/applet/LegacyFrame.java | 5 +++-- libraries/launcher/org/polymc/impl/OneSixLauncher.java | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/libraries/launcher/org/polymc/applet/LegacyFrame.java b/libraries/launcher/org/polymc/applet/LegacyFrame.java index 2cdd17d7..7ae56e60 100644 --- a/libraries/launcher/org/polymc/applet/LegacyFrame.java +++ b/libraries/launcher/org/polymc/applet/LegacyFrame.java @@ -63,7 +63,8 @@ public final class LegacyFrame extends Frame { int winSizeH, boolean maximize, String serverAddress, - String serverPort + String serverPort, + boolean isDemo ) { // Implements support for launching in to multiplayer on classic servers using a mpticket // file generated by an external program and stored in the instance's root folder. @@ -106,7 +107,7 @@ public final class LegacyFrame extends Frame { appletWrap.setParameter("sessionid", session); appletWrap.setParameter("stand-alone", "true"); // Show the quit button. appletWrap.setParameter("haspaid", "true"); // Some old versions need this for world saves to work. - appletWrap.setParameter("demo", "false"); + appletWrap.setParameter("demo", isDemo ? "true" : "false"); appletWrap.setParameter("fullscreen", "false"); add(appletWrap); diff --git a/libraries/launcher/org/polymc/impl/OneSixLauncher.java b/libraries/launcher/org/polymc/impl/OneSixLauncher.java index 362ff8d6..28c3aaa6 100644 --- a/libraries/launcher/org/polymc/impl/OneSixLauncher.java +++ b/libraries/launcher/org/polymc/impl/OneSixLauncher.java @@ -137,7 +137,8 @@ public final class OneSixLauncher implements Launcher { winSizeH, maximize, serverAddress, - serverPort + serverPort, + mcParams.contains("--demo") ); return; From 777be6a48debe82f1cd1863dde2e09487e2dfafa Mon Sep 17 00:00:00 2001 From: jopejoe1 Date: Wed, 13 Jul 2022 22:53:36 +0200 Subject: [PATCH 181/273] Add 'Ctrl+Alt+O' Shortcut to launch demo instance. Signed-off-by: jopejoe1 --- launcher/ui/MainWindow.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 2d8ed24c..473f0c03 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1241,6 +1241,8 @@ void MainWindow::updateToolsMenu() QAction *normalLaunchOffline = launchOfflineMenu->addAction(tr("Launch Offline")); normalLaunchOffline->setShortcut(QKeySequence(tr("Ctrl+Shift+O"))); QAction *normalLaunchDemo = launchDemoMenu->addAction(tr("Launch Demo")); + normalLaunchDemo->setShortcut(QKeySequence(tr("Ctrl+Alt+O"))); + if (m_selectedInstance) { normalLaunch->setEnabled(m_selectedInstance->canLaunch()); From 11216d200c0184e419507e9b607fd78e62838966 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 15 Sep 2022 18:24:21 -0300 Subject: [PATCH 182/273] change: move demo action to "Play offline" menu Signed-off-by: flow --- launcher/ui/MainWindow.cpp | 25 +------------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 473f0c03..9b195d26 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -793,7 +793,6 @@ public: instanceToolBar->addAction(actionLaunchInstance); instanceToolBar->addAction(actionLaunchInstanceOffline); - instanceToolBar->addAction(actionLaunchInstanceDemo); instanceToolBar->addAction(actionKillInstance); instanceToolBar->addSeparator(); @@ -1199,7 +1198,6 @@ void MainWindow::updateToolsMenu() { QToolButton *launchButton = dynamic_cast(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstance)); QToolButton *launchOfflineButton = dynamic_cast(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstanceOffline)); - QToolButton *launchDemoButton = dynamic_cast(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstanceDemo)); bool currentInstanceRunning = m_selectedInstance && m_selectedInstance->isRunning(); @@ -1209,10 +1207,8 @@ void MainWindow::updateToolsMenu() QMenu *launchMenu = ui->actionLaunchInstance->menu(); QMenu *launchOfflineMenu = ui->actionLaunchInstanceOffline->menu(); - QMenu *launchDemoMenu = ui->actionLaunchInstanceDemo->menu(); launchButton->setPopupMode(QToolButton::MenuButtonPopup); launchOfflineButton->setPopupMode(QToolButton::MenuButtonPopup); - launchDemoButton->setPopupMode(QToolButton::MenuButtonPopup); if (launchMenu) { launchMenu->clear(); @@ -1228,21 +1224,13 @@ void MainWindow::updateToolsMenu() { launchOfflineMenu = new QMenu(this); } - if (launchDemoMenu) { - launchDemoMenu->clear(); - } - else - { - launchDemoMenu = new QMenu(this); - } QAction *normalLaunch = launchMenu->addAction(tr("Launch")); normalLaunch->setShortcut(QKeySequence::Open); QAction *normalLaunchOffline = launchOfflineMenu->addAction(tr("Launch Offline")); normalLaunchOffline->setShortcut(QKeySequence(tr("Ctrl+Shift+O"))); - QAction *normalLaunchDemo = launchDemoMenu->addAction(tr("Launch Demo")); + QAction *normalLaunchDemo = launchOfflineMenu->addAction(tr("Launch Demo")); normalLaunchDemo->setShortcut(QKeySequence(tr("Ctrl+Alt+O"))); - if (m_selectedInstance) { normalLaunch->setEnabled(m_selectedInstance->canLaunch()); @@ -1268,28 +1256,23 @@ void MainWindow::updateToolsMenu() QString profilersTitle = tr("Profilers"); launchMenu->addSeparator()->setText(profilersTitle); launchOfflineMenu->addSeparator()->setText(profilersTitle); - launchDemoMenu->addSeparator()->setText(profilersTitle); for (auto profiler : APPLICATION->profilers().values()) { QAction *profilerAction = launchMenu->addAction(profiler->name()); QAction *profilerOfflineAction = launchOfflineMenu->addAction(profiler->name()); - QAction *profilerDemoAction = launchDemoMenu->addAction(profiler->name()); QString error; if (!profiler->check(&error)) { profilerAction->setDisabled(true); profilerOfflineAction->setDisabled(true); - profilerDemoAction->setDisabled(true); QString profilerToolTip = tr("Profiler not setup correctly. Go into settings, \"External Tools\"."); profilerAction->setToolTip(profilerToolTip); profilerOfflineAction->setToolTip(profilerToolTip); - profilerDemoAction->setToolTip(profilerToolTip); } else if (m_selectedInstance) { profilerAction->setEnabled(m_selectedInstance->canLaunch()); profilerOfflineAction->setEnabled(m_selectedInstance->canLaunch()); - profilerDemoAction->setEnabled(m_selectedInstance->canLaunch()); connect(profilerAction, &QAction::triggered, [this, profiler]() { @@ -1299,21 +1282,15 @@ void MainWindow::updateToolsMenu() { APPLICATION->launch(m_selectedInstance, false, false, profiler.get()); }); - connect(profilerDemoAction, &QAction::triggered, [this, profiler]() - { - APPLICATION->launch(m_selectedInstance, false, true, profiler.get()); - }); } else { profilerAction->setDisabled(true); profilerOfflineAction->setDisabled(true); - profilerDemoAction->setDisabled(true); } } ui->actionLaunchInstance->setMenu(launchMenu); ui->actionLaunchInstanceOffline->setMenu(launchOfflineMenu); - ui->actionLaunchInstanceDemo->setMenu(launchDemoMenu); } void MainWindow::repopulateAccountsMenu() From 1b2a7de4e2d643f49c36dc424c9244a98ce93f7e Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 15 Sep 2022 18:32:14 -0300 Subject: [PATCH 183/273] fix: show 'demo' instead of 'offline' in log when in demo mode Signed-off-by: flow --- launcher/LaunchController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index b4a58d01..830fcf82 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -376,7 +376,7 @@ void LaunchController::launchInstance() } m_launcher->prependStep(new TextPrint(m_launcher.get(), resolved_servers, MessageLevel::Launcher)); } else { - online_mode = "offline"; + online_mode = m_demo ? "demo" : "offline"; } m_launcher->prependStep(new TextPrint(m_launcher.get(), "Launched instance in " + online_mode + " mode\n", MessageLevel::Launcher)); From 81e326571bb31dc4a92f1dbe32cb3ec50ace71f8 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 15 Sep 2022 19:23:58 -0300 Subject: [PATCH 184/273] fix: enable demo launch only on supported instances e.g. >= 1.3.1 instances Signed-off-by: flow --- launcher/minecraft/MinecraftInstance.cpp | 8 ++++++++ launcher/minecraft/MinecraftInstance.h | 2 ++ launcher/ui/InstanceWindow.cpp | 8 +++++++- launcher/ui/MainWindow.cpp | 14 ++++++++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 9478b1b8..ab904fa2 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -245,6 +245,14 @@ QString MinecraftInstance::getLocalLibraryPath() const return libraries_dir.absolutePath(); } +bool MinecraftInstance::supportsDemo() const +{ + Version instance_ver { getPackProfile()->getComponentVersion("net.minecraft") }; + // Demo mode was introduced in 1.3.1: https://minecraft.fandom.com/wiki/Demo_mode#History + // FIXME: Due to Version constraints atm, this can't handle well non-release versions + return instance_ver >= Version("1.3.1"); +} + QString MinecraftInstance::jarModsDir() const { QDir jarmods_dir(FS::PathCombine(instanceRoot(), "jarmods/")); diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h index d62ac655..fe39674a 100644 --- a/launcher/minecraft/MinecraftInstance.h +++ b/launcher/minecraft/MinecraftInstance.h @@ -69,6 +69,8 @@ public: // where the instance-local libraries should be QString getLocalLibraryPath() const; + /** Returns whether the instance, with its version, has support for demo mode. */ + [[nodiscard]] bool supportsDemo() const; ////// Profile management ////// std::shared_ptr getPackProfile() const; diff --git a/launcher/ui/InstanceWindow.cpp b/launcher/ui/InstanceWindow.cpp index 5bf42bb5..09ce0d67 100644 --- a/launcher/ui/InstanceWindow.cpp +++ b/launcher/ui/InstanceWindow.cpp @@ -166,7 +166,13 @@ void InstanceWindow::updateLaunchButtons() else { m_launchOfflineButton->setEnabled(true); - m_launchDemoButton->setEnabled(true); + + // Disable demo-mode if not available. + auto instance = dynamic_cast(m_instance.get()); + if (instance) { + m_launchDemoButton->setEnabled(instance->supportsDemo()); + } + m_killButton->setText(tr("Launch")); m_killButton->setObjectName("launchButton"); m_killButton->setToolTip(tr("Launch the instance")); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 9b195d26..58b1ae80 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1253,6 +1253,13 @@ void MainWindow::updateToolsMenu() normalLaunchOffline->setDisabled(true); normalLaunchDemo->setDisabled(true); } + + // Disable demo-mode if not available. + auto instance = dynamic_cast(m_selectedInstance.get()); + if (instance) { + normalLaunchDemo->setEnabled(instance->supportsDemo()); + } + QString profilersTitle = tr("Profilers"); launchMenu->addSeparator()->setText(profilersTitle); launchOfflineMenu->addSeparator()->setText(profilersTitle); @@ -2164,6 +2171,13 @@ void MainWindow::instanceChanged(const QModelIndex ¤t, const QModelIndex & ui->actionLaunchInstance->setEnabled(m_selectedInstance->canLaunch()); ui->actionLaunchInstanceOffline->setEnabled(m_selectedInstance->canLaunch()); ui->actionLaunchInstanceDemo->setEnabled(m_selectedInstance->canLaunch()); + + // Disable demo-mode if not available. + auto instance = dynamic_cast(m_selectedInstance.get()); + if (instance) { + ui->actionLaunchInstanceDemo->setEnabled(instance->supportsDemo()); + } + ui->actionKillInstance->setEnabled(m_selectedInstance->isRunning()); ui->actionExportInstance->setEnabled(m_selectedInstance->canExport()); ui->renameButton->setText(m_selectedInstance->name()); From 9e35230467267691ce745429de8d7e0b18095084 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 16 Sep 2022 19:22:37 -0300 Subject: [PATCH 185/273] fix: memory leak when getting mods from the mods folder friendly reminder to always delete your news. Signed-off-by: flow --- launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp index 3a857740..afe892f4 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp @@ -57,6 +57,8 @@ void ModFolderLoadTask::executeTask() if (mod->enabled()) { if (m_result->mods.contains(mod->internal_id())) { m_result->mods[mod->internal_id()]->setStatus(ModStatus::Installed); + // Delete the object we just created, since a valid one is already in the mods list. + delete mod; } else { m_result->mods[mod->internal_id()] = mod; From 10493bd44ab59171ac4f2e3ab7b600bcff8e4af6 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 16 Sep 2022 19:25:53 -0300 Subject: [PATCH 186/273] fix: move newly allocated resources to the main thread This avoids them getting deleted when the worker thread exits, due to thread affinity on the created thread. Signed-off-by: flow --- .../minecraft/mod/tasks/BasicFolderLoadTask.h | 12 ++++++++++-- .../minecraft/mod/tasks/ModFolderLoadTask.cpp | 16 +++++++++++++++- launcher/minecraft/mod/tasks/ModFolderLoadTask.h | 3 +++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h b/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h index be0e752d..2fce2942 100644 --- a/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h +++ b/launcher/minecraft/mod/tasks/BasicFolderLoadTask.h @@ -3,6 +3,7 @@ #include #include #include +#include #include @@ -23,14 +24,14 @@ class BasicFolderLoadTask : public Task { [[nodiscard]] ResultPtr result() const { return m_result; } public: - BasicFolderLoadTask(QDir dir) : Task(nullptr, false), m_dir(dir), m_result(new Result) + BasicFolderLoadTask(QDir dir) : Task(nullptr, false), m_dir(dir), m_result(new Result), m_thread_to_spawn_into(thread()) { m_create_func = [](QFileInfo const& entry) -> Resource* { return new Resource(entry); }; } BasicFolderLoadTask(QDir dir, std::function create_function) - : Task(nullptr, false), m_dir(dir), m_result(new Result), m_create_func(std::move(create_function)) + : Task(nullptr, false), m_dir(dir), m_result(new Result), m_create_func(std::move(create_function)), m_thread_to_spawn_into(thread()) {} [[nodiscard]] bool canAbort() const override { return true; } @@ -42,9 +43,13 @@ class BasicFolderLoadTask : public Task { void executeTask() override { + if (thread() != m_thread_to_spawn_into) + connect(this, &Task::finished, this->thread(), &QThread::quit); + m_dir.refresh(); for (auto entry : m_dir.entryInfoList()) { auto resource = m_create_func(entry); + resource->moveToThread(m_thread_to_spawn_into); m_result->resources.insert(resource->internal_id(), resource); } @@ -61,4 +66,7 @@ private: std::atomic m_aborted = false; std::function m_create_func; + + /** This is the thread in which we should put new mod objects */ + QThread* m_thread_to_spawn_into; }; diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp index afe892f4..40a6ba18 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp @@ -38,12 +38,23 @@ #include "minecraft/mod/MetadataHandler.h" +#include + ModFolderLoadTask::ModFolderLoadTask(QDir mods_dir, QDir index_dir, bool is_indexed, bool clean_orphan) - : Task(nullptr, false), m_mods_dir(mods_dir), m_index_dir(index_dir), m_is_indexed(is_indexed), m_clean_orphan(clean_orphan), m_result(new Result()) + : Task(nullptr, false) + , m_mods_dir(mods_dir) + , m_index_dir(index_dir) + , m_is_indexed(is_indexed) + , m_clean_orphan(clean_orphan) + , m_result(new Result()) + , m_thread_to_spawn_into(thread()) {} void ModFolderLoadTask::executeTask() { + if (thread() != m_thread_to_spawn_into) + connect(this, &Task::finished, this->thread(), &QThread::quit); + if (m_is_indexed) { // Read metadata first getFromMetadata(); @@ -98,6 +109,9 @@ void ModFolderLoadTask::executeTask() } } + for (auto mod : m_result->mods) + mod->moveToThread(m_thread_to_spawn_into); + if (m_aborted) emit finished(); else diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h index 0f18b8b9..af5f58a5 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.h +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.h @@ -79,4 +79,7 @@ private: ResultPtr m_result; std::atomic m_aborted = false; + + /** This is the thread in which we should put new mod objects */ + QThread* m_thread_to_spawn_into; }; From c9eb584ac80956730dd56068945f6791e29716b3 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 16 Sep 2022 20:00:36 -0300 Subject: [PATCH 187/273] fix: prevent deletes by shared pointer accidental creation This fixes the launcher crashing when opening the game :iea: Signed-off-by: flow --- launcher/minecraft/MinecraftInstance.cpp | 2 +- launcher/minecraft/mod/ResourceFolderModel.cpp | 2 +- launcher/minecraft/mod/ResourceFolderModel.h | 6 +++--- launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 9478b1b8..47f53948 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -706,7 +706,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr { out << QString("%1:").arg(label); auto modList = model.allMods(); - std::sort(modList.begin(), modList.end(), [](Mod::Ptr a, Mod::Ptr b) { + std::sort(modList.begin(), modList.end(), [](auto a, auto b) { auto aName = a->fileinfo().completeBaseName(); auto bName = b->fileinfo().completeBaseName(); return aName.localeAwareCompare(bName) < 0; diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 45d1db59..95bd5648 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -251,7 +251,7 @@ bool ResourceFolderModel::update() return true; } -void ResourceFolderModel::resolveResource(Resource::Ptr res) +void ResourceFolderModel::resolveResource(Resource* res) { if (!res->shouldResolve()) { return; diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index 5652c156..7edbe030 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -68,7 +68,7 @@ class ResourceFolderModel : public QAbstractListModel { virtual bool update(); /** Creates a new parse task, if needed, for 'res' and start it.*/ - virtual void resolveResource(Resource::Ptr res); + virtual void resolveResource(Resource* res); [[nodiscard]] size_t size() const { return m_resources.size(); }; [[nodiscard]] bool empty() const { return size() == 0; } @@ -265,7 +265,7 @@ void ResourceFolderModel::applyUpdates(QSet& current_set, QSet } m_resources[row].reset(new_resource); - resolveResource(m_resources.at(row)); + resolveResource(m_resources[row].get()); emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1)); } } @@ -313,7 +313,7 @@ void ResourceFolderModel::applyUpdates(QSet& current_set, QSet for (auto& added : added_set) { auto res = new_resources[added]; m_resources.append(res); - resolveResource(res); + resolveResource(m_resources.last().get()); } endInsertRows(); diff --git a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp index 40a6ba18..78ef4386 100644 --- a/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp +++ b/launcher/minecraft/mod/tasks/ModFolderLoadTask.cpp @@ -99,7 +99,7 @@ void ModFolderLoadTask::executeTask() // Remove orphan metadata to prevent issues // See https://github.com/PolyMC/PolyMC/issues/996 if (m_clean_orphan) { - QMutableMapIterator iter(m_result->mods); + QMutableMapIterator iter(m_result->mods); while (iter.hasNext()) { auto mod = iter.next().value(); if (mod->status() == ModStatus::NotInstalled) { From 0873b8d3046db38a8dbdde70a3d7f294c14dcf86 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 16 Sep 2022 20:02:21 -0300 Subject: [PATCH 188/273] fix: prevent container detaching in ResourceFolderModel and use const accessors whenever we can! Signed-off-by: flow --- launcher/minecraft/mod/ModFolderModel.cpp | 2 +- launcher/minecraft/mod/ResourceFolderModel.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 13fed1c9..9aca686f 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -234,7 +234,7 @@ auto ModFolderModel::allMods() -> QList { QList mods; - for (auto& res : m_resources) { + for (auto& res : qAsConst(m_resources)) { mods.append(static_cast(res.get())); } diff --git a/launcher/minecraft/mod/ResourceFolderModel.h b/launcher/minecraft/mod/ResourceFolderModel.h index 7edbe030..25095a45 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.h +++ b/launcher/minecraft/mod/ResourceFolderModel.h @@ -247,7 +247,7 @@ void ResourceFolderModel::applyUpdates(QSet& current_set, QSet auto row = row_it.value(); auto& new_resource = new_resources[kept]; - auto const& current_resource = m_resources[row]; + auto const& current_resource = m_resources.at(row); if (new_resource->dateTimeChanged() == current_resource->dateTimeChanged()) { // no significant change, ignore... @@ -265,7 +265,7 @@ void ResourceFolderModel::applyUpdates(QSet& current_set, QSet } m_resources[row].reset(new_resource); - resolveResource(m_resources[row].get()); + resolveResource(m_resources.at(row).get()); emit dataChanged(index(row, 0), index(row, columnCount(QModelIndex()) - 1)); } } @@ -324,7 +324,7 @@ void ResourceFolderModel::applyUpdates(QSet& current_set, QSet { m_resources_index.clear(); int idx = 0; - for (auto const& mod : m_resources) { + for (auto const& mod : qAsConst(m_resources)) { m_resources_index[mod->internal_id()] = idx; idx++; } From 07dcefabcbe3436ae6de09bc8c99120ab3f0a745 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 4 Sep 2022 14:45:09 +0200 Subject: [PATCH 189/273] feat: add texture pack parsing Signed-off-by: Sefa Eyeoglu --- launcher/CMakeLists.txt | 4 + launcher/minecraft/mod/TexturePack.cpp | 45 +++++ launcher/minecraft/mod/TexturePack.h | 53 ++++++ .../minecraft/mod/TexturePackFolderModel.cpp | 78 +++++---- .../minecraft/mod/TexturePackFolderModel.h | 2 + .../minecraft/mod/TexturePackParse_test.cpp | 70 ++++++++ .../mod/tasks/LocalTexturePackParseTask.cpp | 154 ++++++++++++++++++ .../mod/tasks/LocalTexturePackParseTask.h | 56 +++++++ .../another_test_texturefolder/pack.txt | Bin 0 -> 16 bytes .../mod/testdata/test_texture_pack_idk.zip | Bin 0 -> 184 bytes .../assets/minecraft/textures/blah.txt | Bin 0 -> 2 bytes .../mod/testdata/test_texturefolder/pack.txt | Bin 0 -> 24 bytes launcher/ui/pages/instance/TexturePackPage.h | 12 ++ launcher/ui/widgets/InfoFrame.cpp | 47 +++--- launcher/ui/widgets/InfoFrame.h | 4 + tests/CMakeLists.txt | 3 + 16 files changed, 476 insertions(+), 52 deletions(-) create mode 100644 launcher/minecraft/mod/TexturePack.cpp create mode 100644 launcher/minecraft/mod/TexturePack.h create mode 100644 launcher/minecraft/mod/TexturePackParse_test.cpp create mode 100644 launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp create mode 100644 launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h create mode 100644 launcher/minecraft/mod/testdata/another_test_texturefolder/pack.txt create mode 100644 launcher/minecraft/mod/testdata/test_texture_pack_idk.zip create mode 100644 launcher/minecraft/mod/testdata/test_texturefolder/assets/minecraft/textures/blah.txt create mode 100644 launcher/minecraft/mod/testdata/test_texturefolder/pack.txt diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index e44b98eb..848d2e51 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -320,6 +320,8 @@ set(MINECRAFT_SOURCES minecraft/mod/ResourcePack.cpp minecraft/mod/ResourcePackFolderModel.h minecraft/mod/ResourcePackFolderModel.cpp + minecraft/mod/TexturePack.h + minecraft/mod/TexturePack.cpp minecraft/mod/TexturePackFolderModel.h minecraft/mod/TexturePackFolderModel.cpp minecraft/mod/ShaderPackFolderModel.h @@ -332,6 +334,8 @@ set(MINECRAFT_SOURCES minecraft/mod/tasks/LocalModUpdateTask.cpp minecraft/mod/tasks/LocalResourcePackParseTask.h minecraft/mod/tasks/LocalResourcePackParseTask.cpp + minecraft/mod/tasks/LocalTexturePackParseTask.h + minecraft/mod/tasks/LocalTexturePackParseTask.cpp # Assets minecraft/AssetsUtils.h diff --git a/launcher/minecraft/mod/TexturePack.cpp b/launcher/minecraft/mod/TexturePack.cpp new file mode 100644 index 00000000..32f69fc7 --- /dev/null +++ b/launcher/minecraft/mod/TexturePack.cpp @@ -0,0 +1,45 @@ +#include "TexturePack.h" + +#include +#include +#include + +#include "minecraft/mod/tasks/LocalTexturePackParseTask.h" + +void TexturePack::setDescription(QString new_description) +{ + QMutexLocker locker(&m_data_lock); + + m_description = new_description; +} + +void TexturePack::setImage(QImage new_image) +{ + QMutexLocker locker(&m_data_lock); + + Q_ASSERT(!new_image.isNull()); + + if (m_pack_image_cache_key.key.isValid()) + QPixmapCache::remove(m_pack_image_cache_key.key); + + m_pack_image_cache_key.key = QPixmapCache::insert(QPixmap::fromImage(new_image)); + m_pack_image_cache_key.was_ever_used = true; +} + +QPixmap TexturePack::image(QSize size) +{ + QPixmap cached_image; + if (QPixmapCache::find(m_pack_image_cache_key.key, &cached_image)) { + if (size.isNull()) + return cached_image; + return cached_image.scaled(size); + } + + // No valid image we can get + if (!m_pack_image_cache_key.was_ever_used) + return {}; + + // Imaged got evicted from the cache. Re-process it and retry. + TexturePackUtils::process(*this); + return image(size); +} diff --git a/launcher/minecraft/mod/TexturePack.h b/launcher/minecraft/mod/TexturePack.h new file mode 100644 index 00000000..cd5e4b67 --- /dev/null +++ b/launcher/minecraft/mod/TexturePack.h @@ -0,0 +1,53 @@ +#pragma once + +#include "Resource.h" + +#include +#include +#include +#include + +class Version; + +/* TODO: + * + * Store localized descriptions + * */ + +class TexturePack : public Resource { + Q_OBJECT + public: + using Ptr = shared_qobject_ptr; + + TexturePack(QObject* parent = nullptr) : Resource(parent) {} + TexturePack(QFileInfo file_info) : Resource(file_info) {} + + /** Gets the description of the resource pack. */ + [[nodiscard]] QString description() const { return m_description; } + + /** Gets the image of the resource pack, converted to a QPixmap for drawing, and scaled to size. */ + [[nodiscard]] QPixmap image(QSize size); + + /** Thread-safe. */ + void setDescription(QString new_description); + + /** Thread-safe. */ + void setImage(QImage new_image); + + protected: + mutable QMutex m_data_lock; + + /** The texture pack's description, as defined in the pack.txt file. + */ + QString m_description; + + /** The texture pack's image file cache key, for access in the QPixmapCache global instance. + * + * The 'was_ever_used' state simply identifies whether the key was never inserted on the cache (true), + * so as to tell whether a cache entry is inexistent or if it was just evicted from the cache. + */ + struct { + QPixmapCache::Key key; + bool was_ever_used = false; + } m_pack_image_cache_key; +}; diff --git a/launcher/minecraft/mod/TexturePackFolderModel.cpp b/launcher/minecraft/mod/TexturePackFolderModel.cpp index 2c7c945b..561f6202 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.cpp +++ b/launcher/minecraft/mod/TexturePackFolderModel.cpp @@ -1,38 +1,52 @@ // SPDX-License-Identifier: GPL-3.0-only /* -* PolyMC - Minecraft Launcher -* Copyright (C) 2022 Sefa Eyeoglu -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, version 3. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see . -* -* This file incorporates work covered by the following copyright and -* permission notice: -* -* Copyright 2013-2021 MultiMC Contributors -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ #include "TexturePackFolderModel.h" +#include "minecraft/mod/tasks/BasicFolderLoadTask.h" +#include "minecraft/mod/tasks/LocalTexturePackParseTask.h" + TexturePackFolderModel::TexturePackFolderModel(const QString &dir) : ResourceFolderModel(QDir(dir)) {} + +Task* TexturePackFolderModel::createUpdateTask() +{ + return new BasicFolderLoadTask(m_dir, [](QFileInfo const& entry) { return new TexturePack(entry); }); +} + +Task* TexturePackFolderModel::createParseTask(Resource& resource) +{ + return new LocalTexturePackParseTask(m_next_resolution_ticket, static_cast(resource)); +} diff --git a/launcher/minecraft/mod/TexturePackFolderModel.h b/launcher/minecraft/mod/TexturePackFolderModel.h index 69e98661..f9a95466 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.h +++ b/launcher/minecraft/mod/TexturePackFolderModel.h @@ -8,4 +8,6 @@ class TexturePackFolderModel : public ResourceFolderModel public: explicit TexturePackFolderModel(const QString &dir); + [[nodiscard]] Task* createUpdateTask() override; + [[nodiscard]] Task* createParseTask(Resource&) override; }; diff --git a/launcher/minecraft/mod/TexturePackParse_test.cpp b/launcher/minecraft/mod/TexturePackParse_test.cpp new file mode 100644 index 00000000..207c2c87 --- /dev/null +++ b/launcher/minecraft/mod/TexturePackParse_test.cpp @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include + +#include "FileSystem.h" + +#include "TexturePack.h" +#include "tasks/LocalTexturePackParseTask.h" + +class TexturePackParseTest : public QObject { + Q_OBJECT + + private slots: + void test_parseZIP() + { + QString source = QFINDTESTDATA("testdata"); + + QString zip_rp = FS::PathCombine(source, "test_texture_pack_idk.zip"); + TexturePack pack { QFileInfo(zip_rp) }; + + TexturePackUtils::processZIP(pack); + + QVERIFY(pack.description() == "joe biden, wake up"); + } + + void test_parseFolder() + { + QString source = QFINDTESTDATA("testdata"); + + QString folder_rp = FS::PathCombine(source, "test_texturefolder"); + TexturePack pack { QFileInfo(folder_rp) }; + + TexturePackUtils::processFolder(pack); + + QVERIFY(pack.description() == "Some texture pack surely"); + } + + void test_parseFolder2() + { + QString source = QFINDTESTDATA("testdata"); + + QString folder_rp = FS::PathCombine(source, "another_test_texturefolder"); + TexturePack pack { QFileInfo(folder_rp) }; + + TexturePackUtils::process(pack); + + QVERIFY(pack.description() == "quieres\nfor real"); + } +}; + +QTEST_GUILESS_MAIN(TexturePackParseTest) + +#include "TexturePackParse_test.moc" diff --git a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp new file mode 100644 index 00000000..2d5a557e --- /dev/null +++ b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "LocalTexturePackParseTask.h" + +#include "FileSystem.h" + +#include +#include + +#include + +namespace TexturePackUtils { + +bool process(TexturePack& pack) +{ + switch (pack.type()) { + case ResourceType::FOLDER: + TexturePackUtils::processFolder(pack); + return true; + case ResourceType::ZIPFILE: + TexturePackUtils::processZIP(pack); + return true; + default: + qWarning() << "Invalid type for resource pack parse task!"; + return false; + } +} + +void processFolder(TexturePack& pack) +{ + Q_ASSERT(pack.type() == ResourceType::FOLDER); + + QFileInfo mcmeta_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.txt")); + if (mcmeta_file_info.isFile()) { + QFile mcmeta_file(mcmeta_file_info.filePath()); + if (!mcmeta_file.open(QIODevice::ReadOnly)) + return; + + auto data = mcmeta_file.readAll(); + + TexturePackUtils::processPackTXT(pack, std::move(data)); + + mcmeta_file.close(); + } + + QFileInfo image_file_info(FS::PathCombine(pack.fileinfo().filePath(), "pack.png")); + if (image_file_info.isFile()) { + QFile mcmeta_file(image_file_info.filePath()); + if (!mcmeta_file.open(QIODevice::ReadOnly)) + return; + + auto data = mcmeta_file.readAll(); + + TexturePackUtils::processPackPNG(pack, std::move(data)); + + mcmeta_file.close(); + } +} + +void processZIP(TexturePack& pack) +{ + Q_ASSERT(pack.type() == ResourceType::ZIPFILE); + + QuaZip zip(pack.fileinfo().filePath()); + if (!zip.open(QuaZip::mdUnzip)) + return; + + QuaZipFile file(&zip); + + if (zip.setCurrentFile("pack.txt")) { + if (!file.open(QIODevice::ReadOnly)) { + qCritical() << "Failed to open file in zip."; + zip.close(); + return; + } + + auto data = file.readAll(); + + TexturePackUtils::processPackTXT(pack, std::move(data)); + + file.close(); + } + + if (zip.setCurrentFile("pack.png")) { + if (!file.open(QIODevice::ReadOnly)) { + qCritical() << "Failed to open file in zip."; + zip.close(); + return; + } + + auto data = file.readAll(); + + TexturePackUtils::processPackPNG(pack, std::move(data)); + + file.close(); + } + + zip.close(); +} + +void processPackTXT(TexturePack& pack, QByteArray&& raw_data) +{ + pack.setDescription(QString(raw_data)); +} + +void processPackPNG(TexturePack& pack, QByteArray&& raw_data) +{ + auto img = QImage::fromData(raw_data); + if (!img.isNull()) { + pack.setImage(img); + } else { + qWarning() << "Failed to parse pack.png."; + } +} +} // namespace TexturePackUtils + +LocalTexturePackParseTask::LocalTexturePackParseTask(int token, TexturePack& rp) + : Task(nullptr, false), m_token(token), m_resource_pack(rp) +{} + +bool LocalTexturePackParseTask::abort() +{ + m_aborted = true; + return true; +} + +void LocalTexturePackParseTask::executeTask() +{ + Q_ASSERT(m_resource_pack.valid()); + + if (!TexturePackUtils::process(m_resource_pack)) + return; + + if (m_aborted) + emitAborted(); + else + emitSucceeded(); +} diff --git a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h new file mode 100644 index 00000000..239e1197 --- /dev/null +++ b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include + +#include "minecraft/mod/TexturePack.h" + +#include "tasks/Task.h" + +namespace TexturePackUtils { +bool process(TexturePack& pack); + +void processZIP(TexturePack& pack); +void processFolder(TexturePack& pack); + +void processPackTXT(TexturePack& pack, QByteArray&& raw_data); +void processPackPNG(TexturePack& pack, QByteArray&& raw_data); +} // namespace TexturePackUtils + +class LocalTexturePackParseTask : public Task { + Q_OBJECT + public: + LocalTexturePackParseTask(int token, TexturePack& rp); + + [[nodiscard]] bool canAbort() const override { return true; } + bool abort() override; + + void executeTask() override; + + [[nodiscard]] int token() const { return m_token; } + + private: + int m_token; + + TexturePack& m_resource_pack; + + bool m_aborted = false; +}; diff --git a/launcher/minecraft/mod/testdata/another_test_texturefolder/pack.txt b/launcher/minecraft/mod/testdata/another_test_texturefolder/pack.txt new file mode 100644 index 0000000000000000000000000000000000000000..bbc0d271af4cb5e90e82f4ea825a47f02fd27705 GIT binary patch literal 16 XcmXRc%}gyyE#^wgFH$Htraits().contains("texturepacks"); } + + public slots: + bool onSelectionChanged(const QModelIndex& current, const QModelIndex& previous) override + { + auto sourceCurrent = m_filterModel->mapToSource(current); + int row = sourceCurrent.row(); + auto& rp = static_cast(m_model->at(row)); + ui->frame->updateWithTexturePack(rp); + + return true; + } }; diff --git a/launcher/ui/widgets/InfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp index 9e0553f8..bb895c9b 100644 --- a/launcher/ui/widgets/InfoFrame.cpp +++ b/launcher/ui/widgets/InfoFrame.cpp @@ -105,10 +105,7 @@ static const QMap s_value_to_color = { {'f', "#FFFFFF"} }; -void InfoFrame::updateWithResourcePack(ResourcePack& resource_pack) -{ - setName(resource_pack.name()); - +QString InfoFrame::renderColorCodes(QString input) { // We have to manually set the colors for use. // // A color is set using §x, with x = a hex number from 0 to f. @@ -119,39 +116,49 @@ void InfoFrame::updateWithResourcePack(ResourcePack& resource_pack) // TODO: Make the same logic for font formatting too. // TODO: Wrap links inside tags - auto description = resource_pack.description(); - - QString description_parsed(""); + QString html(""); bool in_div = false; - auto desc_it = description.constBegin(); - while (desc_it != description.constEnd()) { - if (*desc_it == u'§') { + auto it = input.constBegin(); + while (it != input.constEnd()) { + if (*it == u'§') { if (in_div) - description_parsed += ""; + html += ""; - auto const& num = *(++desc_it); - description_parsed += QString("").arg(s_value_to_color.constFind(num).value()); + auto const& num = *(++it); + html += QString("").arg(s_value_to_color.constFind(num).value()); in_div = true; - desc_it++; + it++; } - description_parsed += *desc_it; - desc_it++; + html += *it; + it++; } if (in_div) - description_parsed += ""; - description_parsed += ""; + html += ""; + html += ""; - description_parsed.replace("\n", "
"); + html.replace("\n", "
"); + return html; +} - setDescription(description_parsed); +void InfoFrame::updateWithResourcePack(ResourcePack& resource_pack) +{ + setName(resource_pack.name()); + setDescription(renderColorCodes(resource_pack.description())); setImage(resource_pack.image({64, 64})); } +void InfoFrame::updateWithTexturePack(TexturePack& texture_pack) +{ + setName(texture_pack.name()); + setDescription(texture_pack.description()); + setImage(texture_pack.image({64, 64})); +} + void InfoFrame::clear() { setName(); diff --git a/launcher/ui/widgets/InfoFrame.h b/launcher/ui/widgets/InfoFrame.h index 70d15b1e..84523e28 100644 --- a/launcher/ui/widgets/InfoFrame.h +++ b/launcher/ui/widgets/InfoFrame.h @@ -19,6 +19,7 @@ #include "minecraft/mod/Mod.h" #include "minecraft/mod/ResourcePack.h" +#include "minecraft/mod/TexturePack.h" namespace Ui { @@ -41,6 +42,9 @@ class InfoFrame : public QFrame { void updateWithMod(Mod const& m); void updateWithResource(Resource const& resource); void updateWithResourcePack(ResourcePack& rp); + void updateWithTexturePack(TexturePack& tp); + + static QString renderColorCodes(QString input); public slots: void descriptionEllipsisHandler(QString link); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1265d7a5..9c86c221 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -24,6 +24,9 @@ ecm_add_test(ResourceFolderModel_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_V ecm_add_test(ResourcePackParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME ResourcePackParse) +ecm_add_test(minecraft/mod/TexturePackParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test + TEST_NAME TexturePackParse) + ecm_add_test(ParseUtils_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME ParseUtils) From aad6f74db6a08ca23a9b4ee7f261a051d230bef7 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 4 Sep 2022 15:05:11 +0200 Subject: [PATCH 190/273] fix: tests Signed-off-by: Sefa Eyeoglu --- tests/CMakeLists.txt | 2 +- tests/ResourceFolderModel_test.cpp | 1 - .../mod => tests}/TexturePackParse_test.cpp | 10 +++++----- .../another_test_texturefolder/pack.txt | Bin .../TexturePackParse}/test_texture_pack_idk.zip | Bin .../assets/minecraft/textures/blah.txt | Bin .../TexturePackParse}/test_texturefolder/pack.txt | Bin 7 files changed, 6 insertions(+), 7 deletions(-) rename {launcher/minecraft/mod => tests}/TexturePackParse_test.cpp (85%) rename {launcher/minecraft/mod/testdata => tests/testdata/TexturePackParse}/another_test_texturefolder/pack.txt (100%) rename {launcher/minecraft/mod/testdata => tests/testdata/TexturePackParse}/test_texture_pack_idk.zip (100%) rename {launcher/minecraft/mod/testdata => tests/testdata/TexturePackParse}/test_texturefolder/assets/minecraft/textures/blah.txt (100%) rename {launcher/minecraft/mod/testdata => tests/testdata/TexturePackParse}/test_texturefolder/pack.txt (100%) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9c86c221..630f1200 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -24,7 +24,7 @@ ecm_add_test(ResourceFolderModel_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_V ecm_add_test(ResourcePackParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME ResourcePackParse) -ecm_add_test(minecraft/mod/TexturePackParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test +ecm_add_test(TexturePackParse_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test TEST_NAME TexturePackParse) ecm_add_test(ParseUtils_test.cpp LINK_LIBRARIES Launcher_logic Qt${QT_VERSION_MAJOR}::Test diff --git a/tests/ResourceFolderModel_test.cpp b/tests/ResourceFolderModel_test.cpp index 3f0f3ba1..e38b8e93 100644 --- a/tests/ResourceFolderModel_test.cpp +++ b/tests/ResourceFolderModel_test.cpp @@ -146,7 +146,6 @@ slots: for (auto mod : model.allMods()) qDebug() << mod->name(); - // FIXME: It considers every file in the directory as a mod, but we should probably filter that out somehow. QCOMPARE(model.size(), 4); model.stopWatching(); diff --git a/launcher/minecraft/mod/TexturePackParse_test.cpp b/tests/TexturePackParse_test.cpp similarity index 85% rename from launcher/minecraft/mod/TexturePackParse_test.cpp rename to tests/TexturePackParse_test.cpp index 207c2c87..3e9eaf23 100644 --- a/launcher/minecraft/mod/TexturePackParse_test.cpp +++ b/tests/TexturePackParse_test.cpp @@ -21,8 +21,8 @@ #include "FileSystem.h" -#include "TexturePack.h" -#include "tasks/LocalTexturePackParseTask.h" +#include "minecraft/mod/TexturePack.h" +#include "minecraft/mod/tasks/LocalTexturePackParseTask.h" class TexturePackParseTest : public QObject { Q_OBJECT @@ -30,7 +30,7 @@ class TexturePackParseTest : public QObject { private slots: void test_parseZIP() { - QString source = QFINDTESTDATA("testdata"); + QString source = QFINDTESTDATA("testdata/TexturePackParse"); QString zip_rp = FS::PathCombine(source, "test_texture_pack_idk.zip"); TexturePack pack { QFileInfo(zip_rp) }; @@ -42,7 +42,7 @@ class TexturePackParseTest : public QObject { void test_parseFolder() { - QString source = QFINDTESTDATA("testdata"); + QString source = QFINDTESTDATA("testdata/TexturePackParse"); QString folder_rp = FS::PathCombine(source, "test_texturefolder"); TexturePack pack { QFileInfo(folder_rp) }; @@ -54,7 +54,7 @@ class TexturePackParseTest : public QObject { void test_parseFolder2() { - QString source = QFINDTESTDATA("testdata"); + QString source = QFINDTESTDATA("testdata/TexturePackParse"); QString folder_rp = FS::PathCombine(source, "another_test_texturefolder"); TexturePack pack { QFileInfo(folder_rp) }; diff --git a/launcher/minecraft/mod/testdata/another_test_texturefolder/pack.txt b/tests/testdata/TexturePackParse/another_test_texturefolder/pack.txt similarity index 100% rename from launcher/minecraft/mod/testdata/another_test_texturefolder/pack.txt rename to tests/testdata/TexturePackParse/another_test_texturefolder/pack.txt diff --git a/launcher/minecraft/mod/testdata/test_texture_pack_idk.zip b/tests/testdata/TexturePackParse/test_texture_pack_idk.zip similarity index 100% rename from launcher/minecraft/mod/testdata/test_texture_pack_idk.zip rename to tests/testdata/TexturePackParse/test_texture_pack_idk.zip diff --git a/launcher/minecraft/mod/testdata/test_texturefolder/assets/minecraft/textures/blah.txt b/tests/testdata/TexturePackParse/test_texturefolder/assets/minecraft/textures/blah.txt similarity index 100% rename from launcher/minecraft/mod/testdata/test_texturefolder/assets/minecraft/textures/blah.txt rename to tests/testdata/TexturePackParse/test_texturefolder/assets/minecraft/textures/blah.txt diff --git a/launcher/minecraft/mod/testdata/test_texturefolder/pack.txt b/tests/testdata/TexturePackParse/test_texturefolder/pack.txt similarity index 100% rename from launcher/minecraft/mod/testdata/test_texturefolder/pack.txt rename to tests/testdata/TexturePackParse/test_texturefolder/pack.txt From 23fc453fae7091d6ef8176b3ba265e9242ebd540 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Thu, 8 Sep 2022 09:43:55 +0200 Subject: [PATCH 191/273] fix: comments and naming of texture pack stuff Signed-off-by: Sefa Eyeoglu --- launcher/minecraft/mod/TexturePack.cpp | 19 ++++++++++ launcher/minecraft/mod/TexturePack.h | 28 +++++++++++---- .../minecraft/mod/TexturePackFolderModel.h | 36 +++++++++++++++++++ .../mod/tasks/LocalTexturePackParseTask.cpp | 7 ++-- .../mod/tasks/LocalTexturePackParseTask.h | 3 +- tests/TexturePackParse_test.cpp | 1 + 6 files changed, 83 insertions(+), 11 deletions(-) diff --git a/launcher/minecraft/mod/TexturePack.cpp b/launcher/minecraft/mod/TexturePack.cpp index 32f69fc7..796eb69d 100644 --- a/launcher/minecraft/mod/TexturePack.cpp +++ b/launcher/minecraft/mod/TexturePack.cpp @@ -1,3 +1,22 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "TexturePack.h" #include diff --git a/launcher/minecraft/mod/TexturePack.h b/launcher/minecraft/mod/TexturePack.h index cd5e4b67..6aa5e18e 100644 --- a/launcher/minecraft/mod/TexturePack.h +++ b/launcher/minecraft/mod/TexturePack.h @@ -1,3 +1,22 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #pragma once #include "Resource.h" @@ -9,11 +28,6 @@ class Version; -/* TODO: - * - * Store localized descriptions - * */ - class TexturePack : public Resource { Q_OBJECT public: @@ -22,10 +36,10 @@ class TexturePack : public Resource { TexturePack(QObject* parent = nullptr) : Resource(parent) {} TexturePack(QFileInfo file_info) : Resource(file_info) {} - /** Gets the description of the resource pack. */ + /** Gets the description of the texture pack. */ [[nodiscard]] QString description() const { return m_description; } - /** Gets the image of the resource pack, converted to a QPixmap for drawing, and scaled to size. */ + /** Gets the image of the texture pack, converted to a QPixmap for drawing, and scaled to size. */ [[nodiscard]] QPixmap image(QSize size); /** Thread-safe. */ diff --git a/launcher/minecraft/mod/TexturePackFolderModel.h b/launcher/minecraft/mod/TexturePackFolderModel.h index f9a95466..261f83b4 100644 --- a/launcher/minecraft/mod/TexturePackFolderModel.h +++ b/launcher/minecraft/mod/TexturePackFolderModel.h @@ -1,3 +1,39 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #pragma once #include "ResourceFolderModel.h" diff --git a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp index 2d5a557e..bf1e308f 100644 --- a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 flowln + * Copyright (C) 2022 Sefa Eyeoglu * * 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,7 +132,7 @@ void processPackPNG(TexturePack& pack, QByteArray&& raw_data) } // namespace TexturePackUtils LocalTexturePackParseTask::LocalTexturePackParseTask(int token, TexturePack& rp) - : Task(nullptr, false), m_token(token), m_resource_pack(rp) + : Task(nullptr, false), m_token(token), m_texture_pack(rp) {} bool LocalTexturePackParseTask::abort() @@ -142,9 +143,9 @@ bool LocalTexturePackParseTask::abort() void LocalTexturePackParseTask::executeTask() { - Q_ASSERT(m_resource_pack.valid()); + Q_ASSERT(m_texture_pack.valid()); - if (!TexturePackUtils::process(m_resource_pack)) + if (!TexturePackUtils::process(m_texture_pack)) return; if (m_aborted) diff --git a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h index 239e1197..cb0e404a 100644 --- a/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h +++ b/launcher/minecraft/mod/tasks/LocalTexturePackParseTask.h @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 flowln + * Copyright (C) 2022 Sefa Eyeoglu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -50,7 +51,7 @@ class LocalTexturePackParseTask : public Task { private: int m_token; - TexturePack& m_resource_pack; + TexturePack& m_texture_pack; bool m_aborted = false; }; diff --git a/tests/TexturePackParse_test.cpp b/tests/TexturePackParse_test.cpp index 3e9eaf23..0771f79f 100644 --- a/tests/TexturePackParse_test.cpp +++ b/tests/TexturePackParse_test.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 flowln + * Copyright (C) 2022 Sefa Eyeoglu * * 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 From ebbcc9f6da37d0a28f171dfef7d025354056b0e4 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Thu, 8 Sep 2022 11:54:04 +0200 Subject: [PATCH 192/273] fix: actually render color codes for texture packs Signed-off-by: Sefa Eyeoglu --- launcher/ui/widgets/InfoFrame.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/widgets/InfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp index bb895c9b..f78dbe16 100644 --- a/launcher/ui/widgets/InfoFrame.cpp +++ b/launcher/ui/widgets/InfoFrame.cpp @@ -155,7 +155,7 @@ void InfoFrame::updateWithResourcePack(ResourcePack& resource_pack) void InfoFrame::updateWithTexturePack(TexturePack& texture_pack) { setName(texture_pack.name()); - setDescription(texture_pack.description()); + setDescription(renderColorCodes(texture_pack.description())); setImage(texture_pack.image({64, 64})); } From a24d589845aded0a485ddced900768efaca5328b Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Tue, 20 Sep 2022 10:36:34 +0200 Subject: [PATCH 193/273] fix: ensure all resource folders exist Signed-off-by: Sefa Eyeoglu --- launcher/minecraft/mod/ModFolderModel.cpp | 1 - launcher/minecraft/mod/ResourceFolderModel.cpp | 2 ++ 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/launcher/minecraft/mod/ModFolderModel.cpp b/launcher/minecraft/mod/ModFolderModel.cpp index 9aca686f..66e80f4a 100644 --- a/launcher/minecraft/mod/ModFolderModel.cpp +++ b/launcher/minecraft/mod/ModFolderModel.cpp @@ -51,7 +51,6 @@ ModFolderModel::ModFolderModel(const QString &dir, bool is_indexed) : ResourceFolderModel(QDir(dir)), m_is_indexed(is_indexed) { - FS::ensureFolderPathExists(m_dir.absolutePath()); m_column_sort_keys = { SortType::ENABLED, SortType::NAME, SortType::VERSION, SortType::DATE }; } diff --git a/launcher/minecraft/mod/ResourceFolderModel.cpp b/launcher/minecraft/mod/ResourceFolderModel.cpp index 95bd5648..b2356309 100644 --- a/launcher/minecraft/mod/ResourceFolderModel.cpp +++ b/launcher/minecraft/mod/ResourceFolderModel.cpp @@ -14,6 +14,8 @@ ResourceFolderModel::ResourceFolderModel(QDir dir, QObject* parent) : QAbstractListModel(parent), m_dir(dir), m_watcher(this) { + FS::ensureFolderPathExists(m_dir.absolutePath()); + m_dir.setFilter(QDir::Readable | QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); m_dir.setSorting(QDir::Name | QDir::IgnoreCase | QDir::LocaleAware); From 9ec1c00887579e97c7b7a190756f6ddae583563f Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sat, 11 Jun 2022 10:48:56 +0200 Subject: [PATCH 194/273] fix: register JavaRealArchitecture for MinecraftInstance Signed-off-by: Sefa Eyeoglu --- launcher/minecraft/MinecraftInstance.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 62540c75..8496adee 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -147,6 +147,7 @@ void MinecraftInstance::loadSpecificSettings() m_settings->registerPassthrough(global_settings->getSetting("JavaTimestamp"), javaOrLocation); m_settings->registerPassthrough(global_settings->getSetting("JavaVersion"), javaOrLocation); m_settings->registerPassthrough(global_settings->getSetting("JavaArchitecture"), javaOrLocation); + m_settings->registerPassthrough(global_settings->getSetting("JavaRealArchitecture"), javaOrLocation); // Window Size auto windowSetting = m_settings->registerSetting("OverrideWindow", false); From 09e85e948cdb361c306a1cccbc3557a464366a21 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Mon, 11 Jul 2022 09:01:07 +0200 Subject: [PATCH 195/273] refactor: introduce RuntimeContext Signed-off-by: Sefa Eyeoglu --- launcher/BaseInstance.cpp | 5 ++ launcher/BaseInstance.h | 8 ++++ launcher/CMakeLists.txt | 3 +- launcher/NullInstance.h | 4 ++ launcher/RuntimeContext.h | 39 ++++++++++++++++ launcher/minecraft/Component.cpp | 2 +- launcher/minecraft/LaunchProfile.cpp | 24 +++++----- launcher/minecraft/LaunchProfile.h | 8 ++-- launcher/minecraft/Library.cpp | 32 ++++++------- launcher/minecraft/Library.h | 16 +++---- launcher/minecraft/MinecraftInstance.cpp | 29 +++++++----- launcher/minecraft/MinecraftInstance.h | 2 + launcher/minecraft/MojangVersionFormat.cpp | 9 ++-- launcher/minecraft/OpSys.cpp | 46 ------------------- launcher/minecraft/OpSys.h | 38 --------------- launcher/minecraft/PackProfile.cpp | 7 ++- launcher/minecraft/PackProfile.h | 3 ++ launcher/minecraft/Rule.cpp | 6 +-- launcher/minecraft/Rule.h | 10 ++-- launcher/minecraft/VersionFile.cpp | 8 ++-- launcher/minecraft/VersionFile.h | 3 +- launcher/minecraft/launch/ModMinecraftJar.cpp | 3 +- launcher/minecraft/launch/ScanModFolders.cpp | 1 - launcher/minecraft/update/LibrariesTask.cpp | 2 +- .../pages/instance/InstanceSettingsPage.cpp | 3 ++ tests/Library_test.cpp | 10 ++-- 26 files changed, 152 insertions(+), 169 deletions(-) create mode 100644 launcher/RuntimeContext.h delete mode 100644 launcher/minecraft/OpSys.cpp delete mode 100644 launcher/minecraft/OpSys.h diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index e6d4d8e3..944fd4b2 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -358,3 +358,8 @@ shared_qobject_ptr BaseInstance::getLaunchTask() { return m_launchProcess; } + +void BaseInstance::updateRuntimeContext() +{ + // NOOP +} diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 3af104e9..b86401d6 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -54,6 +54,7 @@ #include "net/Mode.h" #include "minecraft/launch/MinecraftServerTarget.h" +#include "RuntimeContext.h" class QDir; class Task; @@ -219,6 +220,12 @@ public: virtual QString typeName() const = 0; + void updateRuntimeContext(); + RuntimeContext runtimeContext() const + { + return m_runtimeContext; + } + bool hasVersionBroken() const { return m_hasBrokenVersion; @@ -304,6 +311,7 @@ protected: /* data */ bool m_isRunning = false; shared_qobject_ptr m_launchProcess; QDateTime m_timeStarted; + RuntimeContext m_runtimeContext; private: /* data */ Status m_status = Status::Present; diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index e44b98eb..f7bcb1d8 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -26,6 +26,7 @@ set(CORE_SOURCES MMCZip.cpp MMCStrings.h MMCStrings.cpp + RuntimeContext.h # Basic instance manipulation tasks (derived from InstanceTask) InstanceCreationTask.h @@ -288,8 +289,6 @@ set(MINECRAFT_SOURCES minecraft/Rule.h minecraft/OneSixVersionFormat.cpp minecraft/OneSixVersionFormat.h - minecraft/OpSys.cpp - minecraft/OpSys.h minecraft/ParseUtils.cpp minecraft/ParseUtils.h minecraft/ProfileUtils.cpp diff --git a/launcher/NullInstance.h b/launcher/NullInstance.h index 53e64a05..628aff5f 100644 --- a/launcher/NullInstance.h +++ b/launcher/NullInstance.h @@ -84,4 +84,8 @@ public: QString modsRoot() const override { return QString(); } + void updateRuntimeContext() + { + // NOOP + } }; diff --git a/launcher/RuntimeContext.h b/launcher/RuntimeContext.h new file mode 100644 index 00000000..76785728 --- /dev/null +++ b/launcher/RuntimeContext.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include "settings/SettingsObject.h" + +struct RuntimeContext { + QString javaArchitecture; + QString javaRealArchitecture; + QString javaPath; + + QString mappedJavaRealArchitecture() const { + if (javaRealArchitecture == "aarch64") { + return "arm64"; + } + return javaRealArchitecture; + } + + void updateFromInstanceSettings(SettingsObjectPtr instanceSettings) { + javaArchitecture = instanceSettings->get("JavaArchitecture").toString(); + javaRealArchitecture = instanceSettings->get("JavaRealArchitecture").toString(); + javaPath = instanceSettings->get("JavaPath").toString(); + } + + static QString currentSystem() { +#if defined(Q_OS_LINUX) + return "linux"; +#elif defined(Q_OS_MACOS) + return "osx"; +#elif defined(Q_OS_WINDOWS) + return "windows"; +#elif defined(Q_OS_FREEBSD) + return "freebsd"; +#elif defined(Q_OS_OPENBSD) + return "openbsd"; +#else + return "unknown"; +#endif + } +}; diff --git a/launcher/minecraft/Component.cpp b/launcher/minecraft/Component.cpp index c7dd5e36..ebe4eac7 100644 --- a/launcher/minecraft/Component.cpp +++ b/launcher/minecraft/Component.cpp @@ -60,7 +60,7 @@ void Component::applyTo(LaunchProfile* profile) auto vfile = getVersionFile(); if(vfile) { - vfile->applyTo(profile); + vfile->applyTo(profile, m_parent->runtimeContext()); } else { diff --git a/launcher/minecraft/LaunchProfile.cpp b/launcher/minecraft/LaunchProfile.cpp index 39a342ca..9a6cea11 100644 --- a/launcher/minecraft/LaunchProfile.cpp +++ b/launcher/minecraft/LaunchProfile.cpp @@ -173,9 +173,9 @@ void LaunchProfile::applyCompatibleJavaMajors(QList& javaMajor) m_compatibleJavaMajors.append(javaMajor); } -void LaunchProfile::applyLibrary(LibraryPtr library) +void LaunchProfile::applyLibrary(LibraryPtr library, const RuntimeContext & runtimeContext) { - if(!library->isActive()) + if(!library->isActive(runtimeContext)) { return; } @@ -205,9 +205,9 @@ void LaunchProfile::applyLibrary(LibraryPtr library) } } -void LaunchProfile::applyMavenFile(LibraryPtr mavenFile) +void LaunchProfile::applyMavenFile(LibraryPtr mavenFile, const RuntimeContext & runtimeContext) { - if(!mavenFile->isActive()) + if(!mavenFile->isActive(runtimeContext)) { return; } @@ -221,10 +221,10 @@ void LaunchProfile::applyMavenFile(LibraryPtr mavenFile) m_mavenFiles.append(Library::limitedCopy(mavenFile)); } -void LaunchProfile::applyAgent(AgentPtr agent) +void LaunchProfile::applyAgent(AgentPtr agent, const RuntimeContext & runtimeContext) { auto lib = agent->library(); - if(!lib->isActive()) + if(!lib->isActive(runtimeContext)) { return; } @@ -354,7 +354,7 @@ const QList & LaunchProfile::getCompatibleJavaMajors() const } void LaunchProfile::getLibraryFiles( - const QString& architecture, + const RuntimeContext & runtimeContext, QStringList& jars, QStringList& nativeJars, const QString& overridePath, @@ -366,7 +366,7 @@ void LaunchProfile::getLibraryFiles( nativeJars.clear(); for (auto lib : getLibraries()) { - lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); + lib->getApplicableFiles(runtimeContext, jars, nativeJars, native32, native64, overridePath); } // NOTE: order is important here, add main jar last to the lists if(m_mainJar) @@ -379,18 +379,18 @@ void LaunchProfile::getLibraryFiles( } else { - m_mainJar->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); + m_mainJar->getApplicableFiles(runtimeContext, jars, nativeJars, native32, native64, overridePath); } } for (auto lib : getNativeLibraries()) { - lib->getApplicableFiles(currentSystem, jars, nativeJars, native32, native64, overridePath); + lib->getApplicableFiles(runtimeContext, jars, nativeJars, native32, native64, overridePath); } - if(architecture == "32") + if(runtimeContext.javaArchitecture == "32") { nativeJars.append(native32); } - else if(architecture == "64") + else if(runtimeContext.javaArchitecture == "64") { nativeJars.append(native64); } diff --git a/launcher/minecraft/LaunchProfile.h b/launcher/minecraft/LaunchProfile.h index b55cf661..49c1217d 100644 --- a/launcher/minecraft/LaunchProfile.h +++ b/launcher/minecraft/LaunchProfile.h @@ -56,9 +56,9 @@ public: /* application of profile variables from patches */ void applyTweakers(const QStringList &tweakers); void applyJarMods(const QList &jarMods); void applyMods(const QList &jarMods); - void applyLibrary(LibraryPtr library); - void applyMavenFile(LibraryPtr library); - void applyAgent(AgentPtr agent); + void applyLibrary(LibraryPtr library, const RuntimeContext & runtimeContext); + void applyMavenFile(LibraryPtr library, const RuntimeContext & runtimeContext); + void applyAgent(AgentPtr agent, const RuntimeContext & runtimeContext); void applyCompatibleJavaMajors(QList& javaMajor); void applyMainJar(LibraryPtr jar); void applyProblemSeverity(ProblemSeverity severity); @@ -83,7 +83,7 @@ public: /* getters for profile variables */ const QList & getCompatibleJavaMajors() const; const LibraryPtr getMainJar() const; void getLibraryFiles( - const QString & architecture, + const RuntimeContext & runtimeContext, QStringList & jars, QStringList & nativeJars, const QString & overridePath, diff --git a/launcher/minecraft/Library.cpp b/launcher/minecraft/Library.cpp index ba7aed4b..1689e27c 100644 --- a/launcher/minecraft/Library.cpp +++ b/launcher/minecraft/Library.cpp @@ -7,7 +7,7 @@ #include -void Library::getApplicableFiles(OpSys system, QStringList& jar, QStringList& native, QStringList& native32, +void Library::getApplicableFiles(const RuntimeContext & runtimeContext, QStringList& jar, QStringList& native, QStringList& native32, QStringList& native64, const QString &overridePath) const { bool local = isLocal(); @@ -21,7 +21,7 @@ void Library::getApplicableFiles(OpSys system, QStringList& jar, QStringList& na } return out.absoluteFilePath(); }; - QString raw_storage = storageSuffix(system); + QString raw_storage = storageSuffix(runtimeContext); if(isNative()) { if (raw_storage.contains("${arch}")) @@ -45,7 +45,7 @@ void Library::getApplicableFiles(OpSys system, QStringList& jar, QStringList& na } QList Library::getDownloads( - OpSys system, + const RuntimeContext & runtimeContext, class HttpMetaCache* cache, QStringList& failedLocalFiles, const QString & overridePath @@ -107,14 +107,14 @@ QList Library::getDownloads( return true; }; - QString raw_storage = storageSuffix(system); + QString raw_storage = storageSuffix(runtimeContext); if(m_mojangDownloads) { if(isNative()) { - if(m_nativeClassifiers.contains(system)) + if(m_nativeClassifiers.contains(runtimeContext.currentSystem())) { - auto nativeClassifier = m_nativeClassifiers[system]; + auto nativeClassifier = m_nativeClassifiers[runtimeContext.currentSystem()]; if(nativeClassifier.contains("${arch}")) { auto nat32Classifier = nativeClassifier; @@ -203,7 +203,7 @@ QList Library::getDownloads( return out; } -bool Library::isActive() const +bool Library::isActive(const RuntimeContext & runtimeContext) const { bool result = true; if (m_rules.empty()) @@ -223,7 +223,7 @@ bool Library::isActive() const } if (isNative()) { - result = result && m_nativeClassifiers.contains(currentSystem); + result = result && m_nativeClassifiers.contains(runtimeContext.currentSystem()); } return result; } @@ -257,7 +257,7 @@ QString Library::storagePrefix() const return m_storagePrefix; } -QString Library::filename(OpSys system) const +QString Library::filename(const RuntimeContext & runtimeContext) const { if(!m_filename.isEmpty()) { @@ -271,9 +271,9 @@ QString Library::filename(OpSys system) const // otherwise native, override classifiers. Mojang HACK! GradleSpecifier nativeSpec = m_name; - if (m_nativeClassifiers.contains(system)) + if (m_nativeClassifiers.contains(runtimeContext.currentSystem())) { - nativeSpec.setClassifier(m_nativeClassifiers[system]); + nativeSpec.setClassifier(m_nativeClassifiers[runtimeContext.currentSystem()]); } else { @@ -282,14 +282,14 @@ QString Library::filename(OpSys system) const return nativeSpec.getFileName(); } -QString Library::displayName(OpSys system) const +QString Library::displayName(const RuntimeContext & runtimeContext) const { if(!m_displayname.isEmpty()) return m_displayname; - return filename(system); + return filename(runtimeContext); } -QString Library::storageSuffix(OpSys system) const +QString Library::storageSuffix(const RuntimeContext & runtimeContext) const { // non-native? use only the gradle specifier if (!isNative()) @@ -299,9 +299,9 @@ QString Library::storageSuffix(OpSys system) const // otherwise native, override classifiers. Mojang HACK! GradleSpecifier nativeSpec = m_name; - if (m_nativeClassifiers.contains(system)) + if (m_nativeClassifiers.contains(runtimeContext.currentSystem())) { - nativeSpec.setClassifier(m_nativeClassifiers[system]); + nativeSpec.setClassifier(m_nativeClassifiers[runtimeContext.currentSystem()]); } else { diff --git a/launcher/minecraft/Library.h b/launcher/minecraft/Library.h index 0740a7ca..e0432fb8 100644 --- a/launcher/minecraft/Library.h +++ b/launcher/minecraft/Library.h @@ -10,9 +10,9 @@ #include #include "Rule.h" -#include "minecraft/OpSys.h" #include "GradleSpecifier.h" #include "MojangDownloadInfo.h" +#include "RuntimeContext.h" class Library; class MinecraftInstance; @@ -98,7 +98,7 @@ public: /* methods */ m_repositoryURL = base_url; } - void getApplicableFiles(OpSys system, QStringList & jar, QStringList & native, + void getApplicableFiles(const RuntimeContext & runtimeContext, QStringList & jar, QStringList & native, QStringList & native32, QStringList & native64, const QString & overridePath) const; void setAbsoluteUrl(const QString &absolute_url) @@ -112,7 +112,7 @@ public: /* methods */ } /// Get the file name of the library - QString filename(OpSys system) const; + QString filename(const RuntimeContext & runtimeContext) const; // DEPRECATED: set a display name, used by jar mods only void setDisplayName(const QString & displayName) @@ -121,7 +121,7 @@ public: /* methods */ } /// Get the file name of the library - QString displayName(OpSys system) const; + QString displayName(const RuntimeContext & runtimeContext) const; void setMojangDownloadInfo(MojangLibraryDownloadInfo::Ptr info) { @@ -140,7 +140,7 @@ public: /* methods */ } /// Returns true if the library should be loaded (or extracted, in case of natives) - bool isActive() const; + bool isActive(const RuntimeContext & runtimeContext) const; /// Returns true if the library is contained in an instance and false if it is shared bool isLocal() const; @@ -152,7 +152,7 @@ public: /* methods */ bool isForge() const; // Get a list of downloads for this library - QList getDownloads(OpSys system, class HttpMetaCache * cache, + QList getDownloads(const RuntimeContext & runtimeContext, class HttpMetaCache * cache, QStringList & failedLocalFiles, const QString & overridePath) const; private: /* methods */ @@ -163,7 +163,7 @@ private: /* methods */ QString storagePrefix() const; /// Get the relative file path where the library should be saved - QString storageSuffix(OpSys system) const; + QString storageSuffix(const RuntimeContext & runtimeContext) const; QString hint() const { @@ -204,7 +204,7 @@ protected: /* data */ QStringList m_extractExcludes; /// native suffixes per OS - QMap m_nativeClassifiers; + QMap m_nativeClassifiers; /// true if the library had a rules section (even empty) bool applyRules = false; diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 8496adee..3a820951 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -191,6 +191,13 @@ void MinecraftInstance::loadSpecificSettings() qDebug() << "Instance-type specific settings were loaded!"; setSpecificSettingsLoaded(true); + + updateRuntimeContext(); +} + +void MinecraftInstance::updateRuntimeContext() +{ + m_runtimeContext.updateFromInstanceSettings(m_settings); } QString MinecraftInstance::typeName() const @@ -328,9 +335,8 @@ QDir MinecraftInstance::versionsPath() const QStringList MinecraftInstance::getClassPath() { QStringList jars, nativeJars; - auto javaArchitecture = settings()->get("JavaArchitecture").toString(); auto profile = m_components->getProfile(); - profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); + profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot()); return jars; } @@ -343,9 +349,8 @@ QString MinecraftInstance::getMainClass() const QStringList MinecraftInstance::getNativeJars() { QStringList jars, nativeJars; - auto javaArchitecture = settings()->get("JavaArchitecture").toString(); auto profile = m_components->getProfile(); - profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); + profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot()); return nativeJars; } @@ -369,7 +374,7 @@ QStringList MinecraftInstance::extraArguments() for (auto agent : agents) { QStringList jar, temp1, temp2, temp3; - agent->library()->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, getLocalLibraryPath()); + agent->library()->getApplicableFiles(runtimeContext(), jar, temp1, temp2, temp3, getLocalLibraryPath()); list.append("-javaagent:"+jar[0]+(agent->argument().isEmpty() ? "" : "="+agent->argument())); } return list; @@ -626,8 +631,7 @@ QString MinecraftInstance::createLaunchScript(AuthSessionPtr session, MinecraftS // libraries and class path. { QStringList jars, nativeJars; - auto javaArchitecture = settings()->get("JavaArchitecture").toString(); - profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); + profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot()); for(auto file: jars) { launchScript += "cp " + file + "\n"; @@ -683,8 +687,7 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr { out << "Libraries:"; QStringList jars, nativeJars; - auto javaArchitecture = settings->get("JavaArchitecture").toString(); - profile->getLibraryFiles(javaArchitecture, jars, nativeJars, getLocalLibraryPath(), binRoot()); + profile->getLibraryFiles(runtimeContext(), jars, nativeJars, getLocalLibraryPath(), binRoot()); auto printLibFile = [&](const QString & path) { QFileInfo info(path); @@ -749,8 +752,8 @@ QStringList MinecraftInstance::verboseDescription(AuthSessionPtr session, Minecr out << "Jar Mods:"; for(auto & jarmod: jarMods) { - auto displayname = jarmod->displayName(currentSystem); - auto realname = jarmod->filename(currentSystem); + auto displayname = jarmod->displayName(runtimeContext()); + auto realname = jarmod->filename(runtimeContext()); if(displayname != realname) { out << " " + displayname + " (" + realname + ")"; @@ -912,6 +915,7 @@ QString MinecraftInstance::getStatusbarDescription() Task::Ptr MinecraftInstance::createUpdateTask(Net::Mode mode) { + updateRuntimeContext(); switch (mode) { case Net::Mode::Offline: @@ -928,6 +932,7 @@ Task::Ptr MinecraftInstance::createUpdateTask(Net::Mode mode) shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPtr session, MinecraftServerTargetPtr serverToJoin) { + updateRuntimeContext(); // FIXME: get rid of shared_from_this ... auto process = LaunchTask::create(std::dynamic_pointer_cast(shared_from_this())); auto pptr = process.get(); @@ -1158,7 +1163,7 @@ QList MinecraftInstance::getJarMods() const for (auto jarmod : profile->getJarMods()) { QStringList jar, temp1, temp2, temp3; - jarmod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, jarmodsPath().absolutePath()); + jarmod->getApplicableFiles(runtimeContext(), jar, temp1, temp2, temp3, jarmodsPath().absolutePath()); // QString filePath = jarmodsPath().absoluteFilePath(jarmod->filename(currentSystem)); mods.push_back(new Mod(QFileInfo(jar[0]))); } diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h index fe39674a..ef48b286 100644 --- a/launcher/minecraft/MinecraftInstance.h +++ b/launcher/minecraft/MinecraftInstance.h @@ -72,6 +72,8 @@ public: /** Returns whether the instance, with its version, has support for demo mode. */ [[nodiscard]] bool supportsDemo() const; + void updateRuntimeContext(); + ////// Profile management ////// std::shared_ptr getPackProfile() const; diff --git a/launcher/minecraft/MojangVersionFormat.cpp b/launcher/minecraft/MojangVersionFormat.cpp index 94c58676..e00d2c6e 100644 --- a/launcher/minecraft/MojangVersionFormat.cpp +++ b/launcher/minecraft/MojangVersionFormat.cpp @@ -362,11 +362,8 @@ LibraryPtr MojangVersionFormat::libraryFromJson(ProblemContainer & problems, con { qWarning() << filename << "contains an invalid native (skipping)"; } - OpSys opSys = OpSys_fromString(it.key()); - if (opSys != Os_Other) - { - out->m_nativeClassifiers[opSys] = it.value().toString(); - } + // FIXME: Skip unknown platforms + out->m_nativeClassifiers[it.key()] = it.value().toString(); } } if (libObj.contains("rules")) @@ -395,7 +392,7 @@ QJsonObject MojangVersionFormat::libraryToJson(Library *library) auto iter = library->m_nativeClassifiers.begin(); while (iter != library->m_nativeClassifiers.end()) { - nativeList.insert(OpSys_toString(iter.key()), iter.value()); + nativeList.insert(iter.key(), iter.value()); iter++; } libRoot.insert("natives", nativeList); diff --git a/launcher/minecraft/OpSys.cpp b/launcher/minecraft/OpSys.cpp deleted file mode 100644 index 093ec419..00000000 --- a/launcher/minecraft/OpSys.cpp +++ /dev/null @@ -1,46 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include "OpSys.h" - -OpSys OpSys_fromString(QString name) -{ - if (name == "freebsd") - return Os_FreeBSD; - if (name == "linux") - return Os_Linux; - if (name == "windows") - return Os_Windows; - if (name == "osx") - return Os_OSX; - return Os_Other; -} - -QString OpSys_toString(OpSys name) -{ - switch (name) - { - case Os_FreeBSD: - return "freebsd"; - case Os_Linux: - return "linux"; - case Os_OSX: - return "osx"; - case Os_Windows: - return "windows"; - default: - return "other"; - } -} \ No newline at end of file diff --git a/launcher/minecraft/OpSys.h b/launcher/minecraft/OpSys.h deleted file mode 100644 index 0936f817..00000000 --- a/launcher/minecraft/OpSys.h +++ /dev/null @@ -1,38 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#pragma once -#include -enum OpSys -{ - Os_Windows, - Os_FreeBSD, - Os_Linux, - Os_OSX, - Os_Other -}; - -OpSys OpSys_fromString(QString); -QString OpSys_toString(OpSys); - -#ifdef Q_OS_WIN32 - #define currentSystem Os_Windows -#elif defined Q_OS_MAC - #define currentSystem Os_OSX -#elif defined Q_OS_FREEBSD - #define currentSystem Os_FreeBSD -#else - #define currentSystem Os_Linux -#endif diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp index 5e76b892..1618458f 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -273,6 +273,11 @@ void PackProfile::scheduleSave() d->m_saveTimer.start(); } +RuntimeContext PackProfile::runtimeContext() +{ + return d->m_instance->runtimeContext(); +} + QString PackProfile::componentsFilePath() const { return FS::PathCombine(d->m_instance->instanceRoot(), "mmc-pack.json"); @@ -784,7 +789,7 @@ bool PackProfile::removeComponent_internal(ComponentPtr patch) return true; } QStringList jar, temp1, temp2, temp3; - jarMod->getApplicableFiles(currentSystem, jar, temp1, temp2, temp3, d->m_instance->jarmodsPath().absolutePath()); + jarMod->getApplicableFiles(d->m_instance->runtimeContext(), jar, temp1, temp2, temp3, d->m_instance->jarmodsPath().absolutePath()); QFileInfo finfo (jar[0]); if(finfo.exists()) { diff --git a/launcher/minecraft/PackProfile.h b/launcher/minecraft/PackProfile.h index 918e7f7a..11057a0c 100644 --- a/launcher/minecraft/PackProfile.h +++ b/launcher/minecraft/PackProfile.h @@ -104,6 +104,9 @@ public: /// if there is a save scheduled, do it now. void saveNow(); + /// helper method, returns RuntimeContext of instance + RuntimeContext runtimeContext(); + signals: void minecraftChanged(); diff --git a/launcher/minecraft/Rule.cpp b/launcher/minecraft/Rule.cpp index af2861e3..2f41e1fb 100644 --- a/launcher/minecraft/Rule.cpp +++ b/launcher/minecraft/Rule.cpp @@ -60,10 +60,10 @@ QList> rulesFromJsonV4(const QJsonObject &objectWithRules) auto osNameVal = osObj.value("name"); if (!osNameVal.isString()) continue; - OpSys requiredOs = OpSys_fromString(osNameVal.toString()); + QString osName = osNameVal.toString(); QString versionRegex = osObj.value("version").toString(); // add a new OS rule - rules.append(OsRule::create(action, requiredOs, versionRegex)); + rules.append(OsRule::create(action, osName, versionRegex)); } return rules; } @@ -81,7 +81,7 @@ QJsonObject OsRule::toJson() ruleObj.insert("action", m_result == Allow ? QString("allow") : QString("disallow")); QJsonObject osObj; { - osObj.insert("name", OpSys_toString(m_system)); + osObj.insert("name", m_system); if(!m_version_regexp.isEmpty()) { osObj.insert("version", m_version_regexp); diff --git a/launcher/minecraft/Rule.h b/launcher/minecraft/Rule.h index 7aa34d96..2ed49d78 100644 --- a/launcher/minecraft/Rule.h +++ b/launcher/minecraft/Rule.h @@ -19,7 +19,7 @@ #include #include #include -#include "OpSys.h" +#include "RuntimeContext.h" class Library; class Rule; @@ -58,23 +58,23 @@ class OsRule : public Rule { private: // the OS - OpSys m_system; + QString m_system; // the OS version regexp QString m_version_regexp; protected: virtual bool applies(const Library *) { - return (m_system == currentSystem); + return (m_system == RuntimeContext::currentSystem()); } - OsRule(RuleAction result, OpSys system, QString version_regexp) + OsRule(RuleAction result, QString system, QString version_regexp) : Rule(result), m_system(system), m_version_regexp(version_regexp) { } public: virtual QJsonObject toJson(); - static std::shared_ptr create(RuleAction result, OpSys system, + static std::shared_ptr create(RuleAction result, QString system, QString version_regexp) { return std::shared_ptr(new OsRule(result, system, version_regexp)); diff --git a/launcher/minecraft/VersionFile.cpp b/launcher/minecraft/VersionFile.cpp index a9a0f7f4..76f41600 100644 --- a/launcher/minecraft/VersionFile.cpp +++ b/launcher/minecraft/VersionFile.cpp @@ -51,7 +51,7 @@ static bool isMinecraftVersion(const QString &uid) return uid == "net.minecraft"; } -void VersionFile::applyTo(LaunchProfile *profile) +void VersionFile::applyTo(LaunchProfile *profile, const RuntimeContext & runtimeContext) { // Only real Minecraft can set those. Don't let anything override them. if (isMinecraftVersion(uid)) @@ -77,15 +77,15 @@ void VersionFile::applyTo(LaunchProfile *profile) for (auto library : libraries) { - profile->applyLibrary(library); + profile->applyLibrary(library, runtimeContext); } for (auto mavenFile : mavenFiles) { - profile->applyMavenFile(mavenFile); + profile->applyMavenFile(mavenFile, runtimeContext); } for (auto agent : agents) { - profile->applyAgent(agent); + profile->applyAgent(agent, runtimeContext); } profile->applyProblemSeverity(getProblemSeverity()); } diff --git a/launcher/minecraft/VersionFile.h b/launcher/minecraft/VersionFile.h index d4b29719..e1b62f6a 100644 --- a/launcher/minecraft/VersionFile.h +++ b/launcher/minecraft/VersionFile.h @@ -41,7 +41,6 @@ #include #include -#include "minecraft/OpSys.h" #include "minecraft/Rule.h" #include "ProblemProvider.h" #include "Library.h" @@ -60,7 +59,7 @@ class VersionFile : public ProblemContainer friend class MojangVersionFormat; friend class OneSixVersionFormat; public: /* methods */ - void applyTo(LaunchProfile* profile); + void applyTo(LaunchProfile* profile, const RuntimeContext & runtimeContext); public: /* data */ /// PolyMC: order hint for this version file if no explicit order is set diff --git a/launcher/minecraft/launch/ModMinecraftJar.cpp b/launcher/minecraft/launch/ModMinecraftJar.cpp index 93de9d59..8e47e0e5 100644 --- a/launcher/minecraft/launch/ModMinecraftJar.cpp +++ b/launcher/minecraft/launch/ModMinecraftJar.cpp @@ -16,7 +16,6 @@ #include "ModMinecraftJar.h" #include "launch/LaunchTask.h" #include "MMCZip.h" -#include "minecraft/OpSys.h" #include "FileSystem.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" @@ -50,7 +49,7 @@ void ModMinecraftJar::executeTask() { auto mainJar = profile->getMainJar(); QStringList jars, temp1, temp2, temp3, temp4; - mainJar->getApplicableFiles(currentSystem, jars, temp1, temp2, temp3, m_inst->getLocalLibraryPath()); + mainJar->getApplicableFiles(m_inst->runtimeContext(), jars, temp1, temp2, temp3, m_inst->getLocalLibraryPath()); auto sourceJarPath = jars[0]; if(!MMCZip::createModdedJar(sourceJarPath, finalJarPath, jarMods)) { diff --git a/launcher/minecraft/launch/ScanModFolders.cpp b/launcher/minecraft/launch/ScanModFolders.cpp index 2a0e21b3..2e817e0a 100644 --- a/launcher/minecraft/launch/ScanModFolders.cpp +++ b/launcher/minecraft/launch/ScanModFolders.cpp @@ -16,7 +16,6 @@ #include "ScanModFolders.h" #include "launch/LaunchTask.h" #include "MMCZip.h" -#include "minecraft/OpSys.h" #include "FileSystem.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/mod/ModFolderModel.h" diff --git a/launcher/minecraft/update/LibrariesTask.cpp b/launcher/minecraft/update/LibrariesTask.cpp index aa2bf407..3b129fe1 100644 --- a/launcher/minecraft/update/LibrariesTask.cpp +++ b/launcher/minecraft/update/LibrariesTask.cpp @@ -34,7 +34,7 @@ void LibrariesTask::executeTask() emitFailed(tr("Null jar is specified in the metadata, aborting.")); return false; } - auto dls = lib->getDownloads(currentSystem, metacache.get(), errors, localPath); + auto dls = lib->getDownloads(inst->runtimeContext(), metacache.get(), errors, localPath); for(auto dl : dls) { downloadJob->addNetAction(dl); diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index 03910745..5da7f19f 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp @@ -274,6 +274,9 @@ void InstanceSettingsPage::applySettings() { m_settings->reset("JoinServerOnLaunchAddress"); } + + // FIXME: This should probably be called by a signal instead + m_instance->updateRuntimeContext(); } void InstanceSettingsPage::loadSettings() diff --git a/tests/Library_test.cpp b/tests/Library_test.cpp index 869c7673..a4ca9232 100644 --- a/tests/Library_test.cpp +++ b/tests/Library_test.cpp @@ -87,7 +87,7 @@ slots: void test_legacy_native() { Library test("test.package:testname:testversion"); - test.m_nativeClassifiers[OpSys::Os_Linux]="linux"; + test.m_nativeClassifiers["linux"] = "linux"; QCOMPARE(test.isNative(), true); test.setRepositoryURL("file://foo/bar"); { @@ -108,9 +108,9 @@ slots: void test_legacy_native_arch() { Library test("test.package:testname:testversion"); - test.m_nativeClassifiers[OpSys::Os_Linux]="linux-${arch}"; - test.m_nativeClassifiers[OpSys::Os_OSX]="osx-${arch}"; - test.m_nativeClassifiers[OpSys::Os_Windows]="windows-${arch}"; + test.m_nativeClassifiers["linux"]="linux-${arch}"; + test.m_nativeClassifiers["osx"]="osx-${arch}"; + test.m_nativeClassifiers["windows"]="windows-${arch}"; QCOMPARE(test.isNative(), true); test.setRepositoryURL("file://foo/bar"); { @@ -159,7 +159,7 @@ slots: void test_legacy_native_arch_local_override() { Library test("test.package:testname:testversion"); - test.m_nativeClassifiers[OpSys::Os_Linux]="linux-${arch}"; + test.m_nativeClassifiers["linux"]="linux-${arch}"; test.setHint("local"); QCOMPARE(test.isNative(), true); test.setRepositoryURL("file://foo/bar"); From 7bd8bd13feddded96b087bb142101f87cb0003b8 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sun, 7 Aug 2022 00:06:32 +0200 Subject: [PATCH 196/273] feat: support multiarch system classifiers Signed-off-by: Sefa Eyeoglu --- launcher/RuntimeContext.h | 23 ++++++++++ launcher/minecraft/Library.cpp | 31 ++++++++++---- launcher/minecraft/Library.h | 3 ++ launcher/minecraft/Rule.h | 12 +++--- tests/Library_test.cpp | 76 +++++++++++++++++++++++----------- 5 files changed, 106 insertions(+), 39 deletions(-) diff --git a/launcher/RuntimeContext.h b/launcher/RuntimeContext.h index 76785728..d98d407f 100644 --- a/launcher/RuntimeContext.h +++ b/launcher/RuntimeContext.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include "settings/SettingsObject.h" @@ -7,6 +8,7 @@ struct RuntimeContext { QString javaArchitecture; QString javaRealArchitecture; QString javaPath; + QString system; QString mappedJavaRealArchitecture() const { if (javaRealArchitecture == "aarch64") { @@ -19,6 +21,27 @@ struct RuntimeContext { javaArchitecture = instanceSettings->get("JavaArchitecture").toString(); javaRealArchitecture = instanceSettings->get("JavaRealArchitecture").toString(); javaPath = instanceSettings->get("JavaPath").toString(); + system = currentSystem(); + } + + QString getClassifier() const { + return system + "-" + mappedJavaRealArchitecture(); + } + + // "Legacy" refers to the fact that Mojang assumed that these are the only two architectures + bool isLegacyArch() const { + QSet legacyArchitectures{"amd64", "x86_64", "i686"}; + return legacyArchitectures.contains(mappedJavaRealArchitecture()); + } + + bool classifierMatches(QString target) const { + // try to match precise classifier "[os]-[arch]" + bool x = target == getClassifier(); + // try to match imprecise classifier on legacy architectures "[os]" + if (!x && isLegacyArch()) + x = target == system; + + return x; } static QString currentSystem() { diff --git a/launcher/minecraft/Library.cpp b/launcher/minecraft/Library.cpp index 1689e27c..42a42d87 100644 --- a/launcher/minecraft/Library.cpp +++ b/launcher/minecraft/Library.cpp @@ -112,9 +112,9 @@ QList Library::getDownloads( { if(isNative()) { - if(m_nativeClassifiers.contains(runtimeContext.currentSystem())) + auto nativeClassifier = getCompatibleNative(runtimeContext); + if(!nativeClassifier.isNull()) { - auto nativeClassifier = m_nativeClassifiers[runtimeContext.currentSystem()]; if(nativeClassifier.contains("${arch}")) { auto nat32Classifier = nativeClassifier; @@ -215,7 +215,7 @@ bool Library::isActive(const RuntimeContext & runtimeContext) const RuleAction ruleResult = Disallow; for (auto rule : m_rules) { - RuleAction temp = rule->apply(this); + RuleAction temp = rule->apply(this, runtimeContext); if (temp != Defer) ruleResult = temp; } @@ -223,7 +223,7 @@ bool Library::isActive(const RuntimeContext & runtimeContext) const } if (isNative()) { - result = result && m_nativeClassifiers.contains(runtimeContext.currentSystem()); + result = result && !getCompatibleNative(runtimeContext).isNull(); } return result; } @@ -238,6 +238,19 @@ bool Library::isAlwaysStale() const return m_hint == "always-stale"; } +QString Library::getCompatibleNative(const RuntimeContext & runtimeContext) const { + // try to match precise classifier "[os]-[arch]" + auto entry = m_nativeClassifiers.constFind(runtimeContext.getClassifier()); + // try to match imprecise classifier on legacy architectures "[os]" + if (entry == m_nativeClassifiers.constEnd() && runtimeContext.isLegacyArch()) + entry = m_nativeClassifiers.constFind(runtimeContext.system); + + if (entry == m_nativeClassifiers.constEnd()) + return QString(); + + return entry.value(); +} + void Library::setStoragePrefix(QString prefix) { m_storagePrefix = prefix; @@ -271,9 +284,10 @@ QString Library::filename(const RuntimeContext & runtimeContext) const // otherwise native, override classifiers. Mojang HACK! GradleSpecifier nativeSpec = m_name; - if (m_nativeClassifiers.contains(runtimeContext.currentSystem())) + QString nativeClassifier = getCompatibleNative(runtimeContext); + if (!nativeClassifier.isNull()) { - nativeSpec.setClassifier(m_nativeClassifiers[runtimeContext.currentSystem()]); + nativeSpec.setClassifier(nativeClassifier); } else { @@ -299,9 +313,10 @@ QString Library::storageSuffix(const RuntimeContext & runtimeContext) const // otherwise native, override classifiers. Mojang HACK! GradleSpecifier nativeSpec = m_name; - if (m_nativeClassifiers.contains(runtimeContext.currentSystem())) + QString nativeClassifier = getCompatibleNative(runtimeContext); + if (!nativeClassifier.isNull()) { - nativeSpec.setClassifier(m_nativeClassifiers[runtimeContext.currentSystem()]); + nativeSpec.setClassifier(nativeClassifier); } else { diff --git a/launcher/minecraft/Library.h b/launcher/minecraft/Library.h index e0432fb8..b8fae9ec 100644 --- a/launcher/minecraft/Library.h +++ b/launcher/minecraft/Library.h @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -155,6 +156,8 @@ public: /* methods */ QList getDownloads(const RuntimeContext & runtimeContext, class HttpMetaCache * cache, QStringList & failedLocalFiles, const QString & overridePath) const; + QString getCompatibleNative(const RuntimeContext & runtimeContext) const; + private: /* methods */ /// the default storage prefix used by PolyMC static QString defaultStoragePrefix(); diff --git a/launcher/minecraft/Rule.h b/launcher/minecraft/Rule.h index 2ed49d78..4c75cee4 100644 --- a/launcher/minecraft/Rule.h +++ b/launcher/minecraft/Rule.h @@ -37,7 +37,7 @@ class Rule { protected: RuleAction m_result; - virtual bool applies(const Library *parent) = 0; + virtual bool applies(const Library *parent, const RuntimeContext & runtimeContext) = 0; public: Rule(RuleAction result) : m_result(result) @@ -45,9 +45,9 @@ public: } virtual ~Rule() {}; virtual QJsonObject toJson() = 0; - RuleAction apply(const Library *parent) + RuleAction apply(const Library *parent, const RuntimeContext & runtimeContext) { - if (applies(parent)) + if (applies(parent, runtimeContext)) return m_result; else return Defer; @@ -63,9 +63,9 @@ private: QString m_version_regexp; protected: - virtual bool applies(const Library *) + virtual bool applies(const Library *, const RuntimeContext & runtimeContext) { - return (m_system == RuntimeContext::currentSystem()); + return runtimeContext.classifierMatches(m_system); } OsRule(RuleAction result, QString system, QString version_regexp) : Rule(result), m_system(system), m_version_regexp(version_regexp) @@ -84,7 +84,7 @@ public: class ImplicitRule : public Rule { protected: - virtual bool applies(const Library *) + virtual bool applies(const Library *, const RuntimeContext & runtimeContext) { return true; } diff --git a/tests/Library_test.cpp b/tests/Library_test.cpp index a4ca9232..b9027e12 100644 --- a/tests/Library_test.cpp +++ b/tests/Library_test.cpp @@ -5,6 +5,7 @@ #include #include #include +#include class LibraryTest : public QObject { @@ -24,6 +25,14 @@ private: { return {FS::PathCombine(cache->getBasePath("libraries"), relative)}; } + + RuntimeContext dummyContext(QString system = "linux", QString arch = "64", QString realArch = "amd64") { + RuntimeContext r; + r.javaArchitecture = arch; + r.javaRealArchitecture = realArch; + r.system = system; + return r; + } private slots: void initTestCase() @@ -34,12 +43,13 @@ slots: } void test_legacy() { + RuntimeContext r = dummyContext(); Library test("test.package:testname:testversion"); QCOMPARE(test.artifactPrefix(), QString("test.package:testname")); QCOMPARE(test.isNative(), false); QStringList jar, native, native32, native64; - test.getApplicableFiles(currentSystem, jar, native, native32, native64, QString()); + test.getApplicableFiles(r, jar, native, native32, native64, QString()); QCOMPARE(jar, getStorage("test/package/testname/testversion/testname-testversion.jar")); QCOMPARE(native, {}); QCOMPARE(native32, {}); @@ -47,10 +57,11 @@ slots: } void test_legacy_url() { + RuntimeContext r = dummyContext(); QStringList failedFiles; Library test("test.package:testname:testversion"); test.setRepositoryURL("file://foo/bar"); - auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString()); + auto downloads = test.getDownloads(r, cache.get(), failedFiles, QString()); QCOMPARE(downloads.size(), 1); QCOMPARE(failedFiles, {}); NetAction::Ptr dl = downloads[0]; @@ -58,27 +69,29 @@ slots: } void test_legacy_url_local_broken() { + RuntimeContext r = dummyContext(); Library test("test.package:testname:testversion"); QCOMPARE(test.isNative(), false); QStringList failedFiles; test.setHint("local"); - auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QString()); + auto downloads = test.getDownloads(r, cache.get(), failedFiles, QString()); QCOMPARE(downloads.size(), 0); QCOMPARE(failedFiles, {"testname-testversion.jar"}); } void test_legacy_url_local_override() { + RuntimeContext r = dummyContext(); Library test("com.paulscode:codecwav:20101023"); QCOMPARE(test.isNative(), false); QStringList failedFiles; test.setHint("local"); - auto downloads = test.getDownloads(currentSystem, cache.get(), failedFiles, QFINDTESTDATA("testdata/Library")); + auto downloads = test.getDownloads(r, cache.get(), failedFiles, QFINDTESTDATA("testdata/Library")); QCOMPARE(downloads.size(), 0); qDebug() << failedFiles; QCOMPARE(failedFiles.size(), 0); QStringList jar, native, native32, native64; - test.getApplicableFiles(currentSystem, jar, native, native32, native64, QFINDTESTDATA("testdata/Library")); + test.getApplicableFiles(r, jar, native, native32, native64, QFINDTESTDATA("testdata/Library")); QCOMPARE(jar, {QFileInfo(QFINDTESTDATA("testdata/Library/codecwav-20101023.jar")).absoluteFilePath()}); QCOMPARE(native, {}); QCOMPARE(native32, {}); @@ -86,19 +99,20 @@ slots: } void test_legacy_native() { + RuntimeContext r = dummyContext(); Library test("test.package:testname:testversion"); test.m_nativeClassifiers["linux"] = "linux"; QCOMPARE(test.isNative(), true); test.setRepositoryURL("file://foo/bar"); { QStringList jar, native, native32, native64; - test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString()); + test.getApplicableFiles(r, jar, native, native32, native64, QString()); QCOMPARE(jar, {}); QCOMPARE(native, getStorage("test/package/testname/testversion/testname-testversion-linux.jar")); QCOMPARE(native32, {}); QCOMPARE(native64, {}); QStringList failedFiles; - auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString()); + auto dls = test.getDownloads(r, cache.get(), failedFiles, QString()); QCOMPARE(dls.size(), 1); QCOMPARE(failedFiles, {}); auto dl = dls[0]; @@ -107,6 +121,7 @@ slots: } void test_legacy_native_arch() { + RuntimeContext r = dummyContext(); Library test("test.package:testname:testversion"); test.m_nativeClassifiers["linux"]="linux-${arch}"; test.m_nativeClassifiers["osx"]="osx-${arch}"; @@ -115,41 +130,43 @@ slots: test.setRepositoryURL("file://foo/bar"); { QStringList jar, native, native32, native64; - test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QString()); + test.getApplicableFiles(r, jar, native, native32, native64, QString()); QCOMPARE(jar, {}); QCOMPARE(native, {}); QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-linux-32.jar")); QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-linux-64.jar")); QStringList failedFiles; - auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QString()); + auto dls = test.getDownloads(r, cache.get(), failedFiles, QString()); QCOMPARE(dls.size(), 2); QCOMPARE(failedFiles, {}); QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-32.jar")); QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-linux-64.jar")); } + r.system = "windows"; { QStringList jar, native, native32, native64; - test.getApplicableFiles(Os_Windows, jar, native, native32, native64, QString()); + test.getApplicableFiles(r, jar, native, native32, native64, QString()); QCOMPARE(jar, {}); QCOMPARE(native, {}); QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-windows-32.jar")); QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-windows-64.jar")); QStringList failedFiles; - auto dls = test.getDownloads(Os_Windows, cache.get(), failedFiles, QString()); + auto dls = test.getDownloads(r, cache.get(), failedFiles, QString()); QCOMPARE(dls.size(), 2); QCOMPARE(failedFiles, {}); QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-32.jar")); QCOMPARE(dls[1]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-windows-64.jar")); } + r.system = "osx"; { QStringList jar, native, native32, native64; - test.getApplicableFiles(Os_OSX, jar, native, native32, native64, QString()); + test.getApplicableFiles(r, jar, native, native32, native64, QString()); QCOMPARE(jar, {}); QCOMPARE(native, {}); QCOMPARE(native32, getStorage("test/package/testname/testversion/testname-testversion-osx-32.jar")); QCOMPARE(native64, getStorage("test/package/testname/testversion/testname-testversion-osx-64.jar")); QStringList failedFiles; - auto dls = test.getDownloads(Os_OSX, cache.get(), failedFiles, QString()); + auto dls = test.getDownloads(r, cache.get(), failedFiles, QString()); QCOMPARE(dls.size(), 2); QCOMPARE(failedFiles, {}); QCOMPARE(dls[0]->m_url, QUrl("file://foo/bar/test/package/testname/testversion/testname-testversion-osx-32.jar")); @@ -158,6 +175,7 @@ slots: } void test_legacy_native_arch_local_override() { + RuntimeContext r = dummyContext(); Library test("test.package:testname:testversion"); test.m_nativeClassifiers["linux"]="linux-${arch}"; test.setHint("local"); @@ -165,96 +183,104 @@ slots: test.setRepositoryURL("file://foo/bar"); { QStringList jar, native, native32, native64; - test.getApplicableFiles(Os_Linux, jar, native, native32, native64, QFINDTESTDATA("testdata/Library")); + test.getApplicableFiles(r, jar, native, native32, native64, QFINDTESTDATA("testdata/Library")); QCOMPARE(jar, {}); QCOMPARE(native, {}); QCOMPARE(native32, {QFileInfo(QFINDTESTDATA("testdata/Library/testname-testversion-linux-32.jar")).absoluteFilePath()}); QCOMPARE(native64, {QFileInfo(QFINDTESTDATA("testdata/Library") + "/testname-testversion-linux-64.jar").absoluteFilePath()}); QStringList failedFiles; - auto dls = test.getDownloads(Os_Linux, cache.get(), failedFiles, QFINDTESTDATA("testdata/Library")); + auto dls = test.getDownloads(r, cache.get(), failedFiles, QFINDTESTDATA("testdata/Library")); QCOMPARE(dls.size(), 0); QCOMPARE(failedFiles, {QFileInfo(QFINDTESTDATA("testdata/Library") + "/testname-testversion-linux-64.jar").absoluteFilePath()}); } } void test_onenine() { + RuntimeContext r = dummyContext("osx"); auto test = readMojangJson(QFINDTESTDATA("testdata/Library/lib-simple.json")); { QStringList jar, native, native32, native64; - test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString()); + test->getApplicableFiles(r, jar, native, native32, native64, QString()); QCOMPARE(jar, getStorage("com/paulscode/codecwav/20101023/codecwav-20101023.jar")); QCOMPARE(native, {}); QCOMPARE(native32, {}); QCOMPARE(native64, {}); } + r.system = "linux"; { QStringList failedFiles; - auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QString()); + auto dls = test->getDownloads(r, cache.get(), failedFiles, QString()); QCOMPARE(dls.size(), 1); QCOMPARE(failedFiles, {}); QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/com/paulscode/codecwav/20101023/codecwav-20101023.jar")); } + r.system = "osx"; test->setHint("local"); { QStringList jar, native, native32, native64; - test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QFINDTESTDATA("testdata/Library")); + test->getApplicableFiles(r, jar, native, native32, native64, QFINDTESTDATA("testdata/Library")); QCOMPARE(jar, {QFileInfo(QFINDTESTDATA("testdata/Library/codecwav-20101023.jar")).absoluteFilePath()}); QCOMPARE(native, {}); QCOMPARE(native32, {}); QCOMPARE(native64, {}); } + r.system = "linux"; { QStringList failedFiles; - auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QFINDTESTDATA("testdata/Library")); + auto dls = test->getDownloads(r, cache.get(), failedFiles, QFINDTESTDATA("testdata/Library")); QCOMPARE(dls.size(), 0); QCOMPARE(failedFiles, {}); } } void test_onenine_local_override() { + RuntimeContext r = dummyContext("osx"); auto test = readMojangJson(QFINDTESTDATA("testdata/Library/lib-simple.json")); test->setHint("local"); { QStringList jar, native, native32, native64; - test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QFINDTESTDATA("testdata/Library")); + test->getApplicableFiles(r, jar, native, native32, native64, QFINDTESTDATA("testdata/Library")); QCOMPARE(jar, {QFileInfo(QFINDTESTDATA("testdata/Library/codecwav-20101023.jar")).absoluteFilePath()}); QCOMPARE(native, {}); QCOMPARE(native32, {}); QCOMPARE(native64, {}); } + r.system = "linux"; { QStringList failedFiles; - auto dls = test->getDownloads(Os_Linux, cache.get(), failedFiles, QFINDTESTDATA("testdata/Library")); + auto dls = test->getDownloads(r, cache.get(), failedFiles, QFINDTESTDATA("testdata/Library")); QCOMPARE(dls.size(), 0); QCOMPARE(failedFiles, {}); } } void test_onenine_native() { + RuntimeContext r = dummyContext("osx"); auto test = readMojangJson(QFINDTESTDATA("testdata/Library/lib-native.json")); QStringList jar, native, native32, native64; - test->getApplicableFiles(Os_OSX, jar, native, native32, native64, QString()); + test->getApplicableFiles(r, jar, native, native32, native64, QString()); QCOMPARE(jar, QStringList()); QCOMPARE(native, getStorage("org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar")); QCOMPARE(native32, {}); QCOMPARE(native64, {}); QStringList failedFiles; - auto dls = test->getDownloads(Os_OSX, cache.get(), failedFiles, QString()); + auto dls = test->getDownloads(r, cache.get(), failedFiles, QString()); QCOMPARE(dls.size(), 1); QCOMPARE(failedFiles, {}); QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/org/lwjgl/lwjgl/lwjgl-platform/2.9.4-nightly-20150209/lwjgl-platform-2.9.4-nightly-20150209-natives-osx.jar")); } void test_onenine_native_arch() { + RuntimeContext r = dummyContext("windows"); auto test = readMojangJson(QFINDTESTDATA("testdata/Library/lib-native-arch.json")); QStringList jar, native, native32, native64; - test->getApplicableFiles(Os_Windows, jar, native, native32, native64, QString()); + test->getApplicableFiles(r, jar, native, native32, native64, QString()); QCOMPARE(jar, {}); QCOMPARE(native, {}); QCOMPARE(native32, getStorage("tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar")); QCOMPARE(native64, getStorage("tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-64.jar")); QStringList failedFiles; - auto dls = test->getDownloads(Os_Windows, cache.get(), failedFiles, QString()); + auto dls = test->getDownloads(r, cache.get(), failedFiles, QString()); QCOMPARE(dls.size(), 2); QCOMPARE(failedFiles, {}); QCOMPARE(dls[0]->m_url, QUrl("https://libraries.minecraft.net/tv/twitch/twitch-platform/5.16/twitch-platform-5.16-natives-windows-32.jar")); From 7e280de361584a70fae4426cf36ca47f694ef61a Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Mon, 8 Aug 2022 19:18:36 +0200 Subject: [PATCH 197/273] refactor: drop 64-bit check Signed-off-by: Sefa Eyeoglu --- launcher/launch/steps/CheckJava.cpp | 20 -------------------- libraries/systeminfo/include/sys.h | 4 ---- libraries/systeminfo/src/sys_apple.cpp | 12 ------------ libraries/systeminfo/src/sys_unix.cpp | 11 ----------- libraries/systeminfo/src/sys_win32.cpp | 22 ---------------------- 5 files changed, 69 deletions(-) diff --git a/launcher/launch/steps/CheckJava.cpp b/launcher/launch/steps/CheckJava.cpp index db56b652..7aeb61bf 100644 --- a/launcher/launch/steps/CheckJava.cpp +++ b/launcher/launch/steps/CheckJava.cpp @@ -121,7 +121,6 @@ void CheckJava::checkJavaFinished(JavaCheckResult result) emit logLine(QString("Could not start java:"), MessageLevel::Error); emit logLines(result.errorLog.split('\n'), MessageLevel::Error); emit logLine(QString("\nCheck your Java settings."), MessageLevel::Launcher); - printSystemInfo(false, false); emitFailed(QString("Could not start java!")); return; } @@ -130,7 +129,6 @@ void CheckJava::checkJavaFinished(JavaCheckResult result) emit logLine(QString("Java checker returned some invalid data we don't understand:"), MessageLevel::Error); emit logLines(result.outLog.split('\n'), MessageLevel::Warning); emit logLine("\nMinecraft might not start properly.", MessageLevel::Launcher); - printSystemInfo(false, false); emitSucceeded(); return; } @@ -138,7 +136,6 @@ void CheckJava::checkJavaFinished(JavaCheckResult result) { auto instance = m_parent->instance(); printJavaInfo(result.javaVersion.toString(), result.mojangPlatform, result.realPlatform, result.javaVendor); - printSystemInfo(true, result.is_64bit); instance->settings()->set("JavaVersion", result.javaVersion.toString()); instance->settings()->set("JavaArchitecture", result.mojangPlatform); instance->settings()->set("JavaRealArchitecture", result.realPlatform); @@ -155,20 +152,3 @@ void CheckJava::printJavaInfo(const QString& version, const QString& architectur emit logLine(QString("Java is version %1, using %2 (%3) architecture, from %4.\n\n") .arg(version, architecture, realArchitecture, vendor), MessageLevel::Launcher); } - -void CheckJava::printSystemInfo(bool javaIsKnown, bool javaIs64bit) -{ - auto cpu64 = Sys::isCPU64bit(); - auto system64 = Sys::isSystem64bit(); - if(cpu64 != system64) - { - emit logLine(QString("Your CPU architecture is not matching your system architecture. You might want to install a 64bit Operating System.\n\n"), MessageLevel::Error); - } - if(javaIsKnown) - { - if(javaIs64bit != system64) - { - emit logLine(QString("Your Java architecture is not matching your system architecture. You might want to install a 64bit Java version.\n\n"), MessageLevel::Error); - } - } -} diff --git a/libraries/systeminfo/include/sys.h b/libraries/systeminfo/include/sys.h index bd6e2486..6a6a7c82 100644 --- a/libraries/systeminfo/include/sys.h +++ b/libraries/systeminfo/include/sys.h @@ -56,8 +56,4 @@ struct DistributionInfo DistributionInfo getDistributionInfo(); uint64_t getSystemRam(); - -bool isSystem64bit(); - -bool isCPU64bit(); } diff --git a/libraries/systeminfo/src/sys_apple.cpp b/libraries/systeminfo/src/sys_apple.cpp index 6353b747..b6d62c1a 100644 --- a/libraries/systeminfo/src/sys_apple.cpp +++ b/libraries/systeminfo/src/sys_apple.cpp @@ -55,18 +55,6 @@ uint64_t Sys::getSystemRam() } } -bool Sys::isCPU64bit() -{ - // not even going to pretend I'm going to support anything else - return true; -} - -bool Sys::isSystem64bit() -{ - // yep. maybe when we have 128bit CPUs on consumer devices. - return true; -} - Sys::DistributionInfo Sys::getDistributionInfo() { DistributionInfo result; diff --git a/libraries/systeminfo/src/sys_unix.cpp b/libraries/systeminfo/src/sys_unix.cpp index b3098522..3c63e73a 100644 --- a/libraries/systeminfo/src/sys_unix.cpp +++ b/libraries/systeminfo/src/sys_unix.cpp @@ -82,17 +82,6 @@ uint64_t Sys::getSystemRam() return 0; // nothing found } -bool Sys::isCPU64bit() -{ - return isSystem64bit(); -} - -bool Sys::isSystem64bit() -{ - // kernel build arch on linux - return QSysInfo::currentCpuArchitecture() == "x86_64"; -} - Sys::DistributionInfo Sys::getDistributionInfo() { DistributionInfo systemd_info = read_os_release(); diff --git a/libraries/systeminfo/src/sys_win32.cpp b/libraries/systeminfo/src/sys_win32.cpp index 430b87e4..5bf510cf 100644 --- a/libraries/systeminfo/src/sys_win32.cpp +++ b/libraries/systeminfo/src/sys_win32.cpp @@ -27,28 +27,6 @@ uint64_t Sys::getSystemRam() return (uint64_t)status.ullTotalPhys; } -bool Sys::isSystem64bit() -{ -#if defined(_WIN64) - return true; -#elif defined(_WIN32) - BOOL f64 = false; - return IsWow64Process(GetCurrentProcess(), &f64) && f64; -#else - // it's some other kind of system... - return false; -#endif -} - -bool Sys::isCPU64bit() -{ - SYSTEM_INFO info; - ZeroMemory(&info, sizeof(SYSTEM_INFO)); - GetNativeSystemInfo(&info); - auto arch = info.wProcessorArchitecture; - return arch == PROCESSOR_ARCHITECTURE_AMD64 || arch == PROCESSOR_ARCHITECTURE_IA64; -} - Sys::DistributionInfo Sys::getDistributionInfo() { DistributionInfo result; From 98b6f901721aaeac3ba377729ef95272eba09410 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Wed, 10 Aug 2022 17:33:55 +0200 Subject: [PATCH 198/273] fix: add more legacy architectures Signed-off-by: Sefa Eyeoglu --- launcher/RuntimeContext.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/RuntimeContext.h b/launcher/RuntimeContext.h index d98d407f..6090897c 100644 --- a/launcher/RuntimeContext.h +++ b/launcher/RuntimeContext.h @@ -30,7 +30,7 @@ struct RuntimeContext { // "Legacy" refers to the fact that Mojang assumed that these are the only two architectures bool isLegacyArch() const { - QSet legacyArchitectures{"amd64", "x86_64", "i686"}; + QSet legacyArchitectures{"amd64", "x86_64", "i386", "i686", "x86"}; return legacyArchitectures.contains(mappedJavaRealArchitecture()); } From ec9ddc4f225c89ea3ccbf24c9a3be36848aa3172 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 31 Jul 2022 19:48:38 -0300 Subject: [PATCH 199/273] chore: add helper function for copying managed pack data between insts. Signed-off-by: flow --- launcher/BaseInstance.cpp | 10 ++++++++++ launcher/BaseInstance.h | 1 + 2 files changed, 11 insertions(+) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index e6d4d8e3..3b261678 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -154,6 +154,16 @@ void BaseInstance::setManagedPack(const QString& type, const QString& id, const settings()->set("ManagedPackVersionName", version); } +void BaseInstance::copyManagedPack(BaseInstance& other) +{ + settings()->set("ManagedPack", other.isManagedPack()); + settings()->set("ManagedPackType", other.getManagedPackType()); + settings()->set("ManagedPackID", other.getManagedPackID()); + settings()->set("ManagedPackName", other.getManagedPackName()); + settings()->set("ManagedPackVersionID", other.getManagedPackVersionID()); + settings()->set("ManagedPackVersionName", other.getManagedPackVersionName()); +} + int BaseInstance::getConsoleMaxLines() const { auto lineSetting = m_settings->getSetting("ConsoleMaxLines"); diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 3af104e9..653d1378 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -147,6 +147,7 @@ public: QString getManagedPackVersionID(); QString getManagedPackVersionName(); void setManagedPack(const QString& type, const QString& id, const QString& name, const QString& versionId, const QString& version); + void copyManagedPack(BaseInstance& other); /// guess log level from a line of game log virtual MessageLevel::Enum guessLevel(const QString &line, MessageLevel::Enum level) From 941d75824af8d8e3deb1dfc0597c9493911f0cf0 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 7 Jul 2022 20:31:24 -0300 Subject: [PATCH 200/273] refactor: add instance creation abstraction and move vanilla This is so that 1. Code is more cleanly separated, and 2. Allows to more easily add instance updating :) Signed-off-by: flow --- launcher/CMakeLists.txt | 2 + launcher/InstanceCreationTask.cpp | 42 ++++------------- launcher/InstanceCreationTask.h | 47 ++++++++++++------- .../minecraft/VanillaInstanceCreationTask.cpp | 32 +++++++++++++ .../minecraft/VanillaInstanceCreationTask.h | 20 ++++++++ launcher/ui/pages/modplatform/VanillaPage.cpp | 10 ++-- 6 files changed, 99 insertions(+), 54 deletions(-) create mode 100644 launcher/minecraft/VanillaInstanceCreationTask.cpp create mode 100644 launcher/minecraft/VanillaInstanceCreationTask.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 848d2e51..c51cd6bd 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -297,6 +297,8 @@ set(MINECRAFT_SOURCES minecraft/Library.cpp minecraft/Library.h minecraft/MojangDownloadInfo.h + minecraft/VanillaInstanceCreationTask.cpp + minecraft/VanillaInstanceCreationTask.h minecraft/VersionFile.cpp minecraft/VersionFile.h minecraft/VersionFilterData.h diff --git a/launcher/InstanceCreationTask.cpp b/launcher/InstanceCreationTask.cpp index e01bf306..c8c91997 100644 --- a/launcher/InstanceCreationTask.cpp +++ b/launcher/InstanceCreationTask.cpp @@ -1,40 +1,18 @@ #include "InstanceCreationTask.h" -#include "settings/INISettingsObject.h" -#include "FileSystem.h" -//FIXME: remove this -#include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" +#include -InstanceCreationTask::InstanceCreationTask(BaseVersionPtr version) -{ - m_version = version; - m_usingLoader = false; -} - -InstanceCreationTask::InstanceCreationTask(BaseVersionPtr version, QString loader, BaseVersionPtr loaderVersion) -{ - m_version = version; - m_usingLoader = true; - m_loader = loader; - m_loaderVersion = loaderVersion; -} +InstanceCreationTask::InstanceCreationTask() {} void InstanceCreationTask::executeTask() { - setStatus(tr("Creating instance from version %1").arg(m_version->name())); - { - auto instanceSettings = std::make_shared(FS::PathCombine(m_stagingPath, "instance.cfg")); - instanceSettings->suspendSave(); - MinecraftInstance inst(m_globalSettings, instanceSettings, m_stagingPath); - auto components = inst.getPackProfile(); - components->buildingFromScratch(); - components->setComponentVersion("net.minecraft", m_version->descriptor(), true); - if(m_usingLoader) - components->setComponentVersion(m_loader, m_loaderVersion->descriptor()); - inst.setName(m_instName); - inst.setIconKey(m_instIcon); - instanceSettings->resumeSave(); + if (updateInstance() || createInstance()) { + emitSucceeded(); + return; } - emitSucceeded(); + + qWarning() << "Instance creation failed!"; + if (!m_error_message.isEmpty()) + qWarning() << "Reason: " << m_error_message; + emitFailed(tr("Error while creating new instance.")); } diff --git a/launcher/InstanceCreationTask.h b/launcher/InstanceCreationTask.h index 23367c3f..af854713 100644 --- a/launcher/InstanceCreationTask.h +++ b/launcher/InstanceCreationTask.h @@ -1,26 +1,39 @@ #pragma once -#include "tasks/Task.h" -#include "net/NetJob.h" -#include -#include "settings/SettingsObject.h" #include "BaseVersion.h" #include "InstanceTask.h" -class InstanceCreationTask : public InstanceTask -{ +class InstanceCreationTask : public InstanceTask { Q_OBJECT -public: - explicit InstanceCreationTask(BaseVersionPtr version); - explicit InstanceCreationTask(BaseVersionPtr version, QString loader, BaseVersionPtr loaderVersion); + public: + InstanceCreationTask(); + virtual ~InstanceCreationTask() = default; -protected: - //! Entry point for tasks. - virtual void executeTask() override; + protected: + void executeTask() final override; -private: /* data */ - BaseVersionPtr m_version; - bool m_usingLoader; - QString m_loader; - BaseVersionPtr m_loaderVersion; + /** + * Tries to update an already existing instance. + * + * This can be implemented by subclasses to provide a way of updating an already existing + * instance, according to that implementation's concept of 'identity' (i.e. instances that + * are updates / downgrades of one another). + * + * If this returns true, createInstance() will not run, so you should do all update steps in here. + * Otherwise, createInstance() is run as normal. + */ + virtual bool updateInstance() { return false; }; + + /** + * Creates a new instance. + * + * Returns whether the instance creation was successful (true) or not (false). + */ + virtual bool createInstance() { return false; }; + + protected: + void setError(QString message) { m_error_message = message; }; + + private: + QString m_error_message; }; diff --git a/launcher/minecraft/VanillaInstanceCreationTask.cpp b/launcher/minecraft/VanillaInstanceCreationTask.cpp new file mode 100644 index 00000000..2d1593b6 --- /dev/null +++ b/launcher/minecraft/VanillaInstanceCreationTask.cpp @@ -0,0 +1,32 @@ +#include "VanillaInstanceCreationTask.h" + +#include "FileSystem.h" +#include "settings/INISettingsObject.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" + +VanillaCreationTask::VanillaCreationTask(BaseVersionPtr version, QString loader, BaseVersionPtr loader_version) + : InstanceCreationTask(), m_version(version), m_using_loader(true), m_loader(loader), m_loader_version(loader_version) +{} + +bool VanillaCreationTask::createInstance() +{ + setStatus(tr("Creating instance from version %1").arg(m_version->name())); + + auto instance_settings = std::make_shared(FS::PathCombine(m_stagingPath, "instance.cfg")); + instance_settings->suspendSave(); + { + MinecraftInstance inst(m_globalSettings, instance_settings, m_stagingPath); + auto components = inst.getPackProfile(); + components->buildingFromScratch(); + components->setComponentVersion("net.minecraft", m_version->descriptor(), true); + if(m_using_loader) + components->setComponentVersion(m_loader, m_loader_version->descriptor()); + + inst.setName(m_instName); + inst.setIconKey(m_instIcon); + } + instance_settings->resumeSave(); + + return true; +} diff --git a/launcher/minecraft/VanillaInstanceCreationTask.h b/launcher/minecraft/VanillaInstanceCreationTask.h new file mode 100644 index 00000000..540ecb70 --- /dev/null +++ b/launcher/minecraft/VanillaInstanceCreationTask.h @@ -0,0 +1,20 @@ +#pragma once + +#include "InstanceCreationTask.h" + +class VanillaCreationTask final : public InstanceCreationTask { + Q_OBJECT + public: + VanillaCreationTask(BaseVersionPtr version) : InstanceCreationTask(), m_version(version) {} + VanillaCreationTask(BaseVersionPtr version, QString loader, BaseVersionPtr loader_version); + + bool createInstance() override; + + private: + // Version to update to / create of the instance. + BaseVersionPtr m_version; + + bool m_using_loader = false; + QString m_loader; + BaseVersionPtr m_loader_version; +}; diff --git a/launcher/ui/pages/modplatform/VanillaPage.cpp b/launcher/ui/pages/modplatform/VanillaPage.cpp index a026947f..99190f31 100644 --- a/launcher/ui/pages/modplatform/VanillaPage.cpp +++ b/launcher/ui/pages/modplatform/VanillaPage.cpp @@ -39,12 +39,12 @@ #include #include "Application.h" +#include "Filter.h" +#include "Version.h" #include "meta/Index.h" #include "meta/VersionList.h" +#include "minecraft/VanillaInstanceCreationTask.h" #include "ui/dialogs/NewInstanceDialog.h" -#include "Filter.h" -#include "InstanceCreationTask.h" -#include "Version.h" VanillaPage::VanillaPage(NewInstanceDialog *dialog, QWidget *parent) : QWidget(parent), dialog(dialog), ui(new Ui::VanillaPage) @@ -217,11 +217,11 @@ void VanillaPage::suggestCurrent() // There isn't a selected version if the version list is empty if(ui->loaderVersionList->selectedVersion() == nullptr) - dialog->setSuggestedPack(m_selectedVersion->descriptor(), new InstanceCreationTask(m_selectedVersion)); + dialog->setSuggestedPack(m_selectedVersion->descriptor(), new VanillaCreationTask(m_selectedVersion)); else { dialog->setSuggestedPack(m_selectedVersion->descriptor(), - new InstanceCreationTask(m_selectedVersion, m_selectedLoader, + new VanillaCreationTask(m_selectedVersion, m_selectedLoader, m_selectedLoaderVersion)); } dialog->setSuggestedIcon("default"); From 4441b373385f9b7f77deed2a27751337951f38f6 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 7 Jul 2022 21:10:41 -0300 Subject: [PATCH 201/273] refactor: move modrinth modpack import to separate file Signed-off-by: flow --- launcher/CMakeLists.txt | 2 + launcher/InstanceImportTask.cpp | 204 ++-------------- .../modrinth/ModrinthInstanceCreationTask.cpp | 225 ++++++++++++++++++ .../modrinth/ModrinthInstanceCreationTask.h | 39 +++ 4 files changed, 281 insertions(+), 189 deletions(-) create mode 100644 launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp create mode 100644 launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index c51cd6bd..68439ee8 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -495,6 +495,8 @@ set(MODRINTH_SOURCES modplatform/modrinth/ModrinthPackManifest.h modplatform/modrinth/ModrinthCheckUpdate.cpp modplatform/modrinth/ModrinthCheckUpdate.h + modplatform/modrinth/ModrinthInstanceCreationTask.cpp + modplatform/modrinth/ModrinthInstanceCreationTask.h ) set(MODPACKSCH_SOURCES diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index de0afc96..b19b5fa6 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -52,8 +52,8 @@ #include "minecraft/PackProfile.h" #include "modplatform/flame/FileResolvingTask.h" #include "modplatform/flame/PackManifest.h" -#include "modplatform/modrinth/ModrinthPackManifest.h" #include "modplatform/technic/TechnicPackProcessor.h" +#include "modplatform/modrinth/ModrinthInstanceCreationTask.h" #include "Application.h" #include "icons/IconList.h" @@ -256,6 +256,7 @@ void InstanceImportTask::extractFinished() void InstanceImportTask::extractAborted() { emitFailed(tr("Instance import has been aborted.")); + emit aborted(); return; } @@ -584,198 +585,23 @@ void InstanceImportTask::processMultiMC() emitSucceeded(); } -// https://docs.modrinth.com/docs/modpacks/format_definition/ void InstanceImportTask::processModrinth() { - std::vector files; - QString minecraftVersion, fabricVersion, quiltVersion, forgeVersion; - try { - QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json"); - auto doc = Json::requireDocument(indexPath); - auto obj = Json::requireObject(doc, "modrinth.index.json"); - int formatVersion = Json::requireInteger(obj, "formatVersion", "modrinth.index.json"); - if (formatVersion == 1) { - auto game = Json::requireString(obj, "game", "modrinth.index.json"); - if (game != "minecraft") { - throw JSONValidationError("Unknown game: " + game); - } + auto* inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, m_sourceUrl.toString()); - auto jsonFiles = Json::requireIsArrayOf(obj, "files", "modrinth.index.json"); - bool had_optional = false; - for (auto modInfo : jsonFiles) { - Modrinth::File file; - file.path = Json::requireString(modInfo, "path"); - - auto env = Json::ensureObject(modInfo, "env"); - // 'env' field is optional - if (!env.isEmpty()) { - QString support = Json::ensureString(env, "client", "unsupported"); - if (support == "unsupported") { - continue; - } else if (support == "optional") { - // TODO: Make a review dialog for choosing which ones the user wants! - if (!had_optional) { - had_optional = true; - auto info = CustomMessageBox::selectable( - m_parent, tr("Optional mod detected!"), - tr("One or more mods from this modpack are optional. They will be downloaded, but disabled by default!"), - QMessageBox::Information); - info->exec(); - } - - if (file.path.endsWith(".jar")) - file.path += ".disabled"; - } - } - - QJsonObject hashes = Json::requireObject(modInfo, "hashes"); - QString hash; - QCryptographicHash::Algorithm hashAlgorithm; - hash = Json::ensureString(hashes, "sha1"); - hashAlgorithm = QCryptographicHash::Sha1; - if (hash.isEmpty()) { - hash = Json::ensureString(hashes, "sha512"); - hashAlgorithm = QCryptographicHash::Sha512; - if (hash.isEmpty()) { - hash = Json::ensureString(hashes, "sha256"); - hashAlgorithm = QCryptographicHash::Sha256; - if (hash.isEmpty()) { - throw JSONValidationError("No hash found for: " + file.path); - } - } - } - file.hash = QByteArray::fromHex(hash.toLatin1()); - file.hashAlgorithm = hashAlgorithm; - - // Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode - // (as Modrinth seems to incorrectly handle spaces) - - auto download_arr = Json::ensureArray(modInfo, "downloads"); - for(auto download : download_arr) { - qWarning() << download.toString(); - bool is_last = download.toString() == download_arr.last().toString(); - - auto download_url = QUrl(download.toString()); - - if (!download_url.isValid()) { - qDebug() << QString("Download URL (%1) for %2 is not a correctly formatted URL") - .arg(download_url.toString(), file.path); - if(is_last && file.downloads.isEmpty()) - throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path)); - } - else { - file.downloads.push_back(download_url); - } - } - - files.push_back(file); - } - - auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json"); - for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) { - QString name = it.key(); - if (name == "minecraft") { - minecraftVersion = Json::requireString(*it, "Minecraft version"); - } - else if (name == "fabric-loader") { - fabricVersion = Json::requireString(*it, "Fabric Loader version"); - } - else if (name == "quilt-loader") { - quiltVersion = Json::requireString(*it, "Quilt Loader version"); - } - else if (name == "forge") { - forgeVersion = Json::requireString(*it, "Forge version"); - } - else { - throw JSONValidationError("Unknown dependency type: " + name); - } - } - } else { - throw JSONValidationError(QStringLiteral("Unknown format version: %s").arg(formatVersion)); - } - QFile::remove(indexPath); - } catch (const JSONValidationError& e) { - emitFailed(tr("Could not understand pack index:\n") + e.cause()); - return; - } + inst_creation_task->setName(m_instName); + inst_creation_task->setIcon(m_instIcon); + inst_creation_task->setGroup(m_instGroup); - auto mcPath = FS::PathCombine(m_stagingPath, ".minecraft"); + connect(inst_creation_task, &Task::succeeded, this, &InstanceImportTask::emitSucceeded); + connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed); + connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress); + connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus); + connect(inst_creation_task, &Task::finished, this, [inst_creation_task]{ inst_creation_task->deleteLater(); }); - auto override_path = FS::PathCombine(m_stagingPath, "overrides"); - if (QFile::exists(override_path)) { - if (!QFile::rename(override_path, mcPath)) { - emitFailed(tr("Could not rename the overrides folder:\n") + "overrides"); - return; - } - } - - // Do client overrides - auto client_override_path = FS::PathCombine(m_stagingPath, "client-overrides"); - if (QFile::exists(client_override_path)) { - if (!FS::overrideFolder(mcPath, client_override_path)) { - emitFailed(tr("Could not rename the client overrides folder:\n") + "client overrides"); - return; - } - } - - QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); - auto instanceSettings = std::make_shared(configPath); - MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); - auto components = instance.getPackProfile(); - components->buildingFromScratch(); - components->setComponentVersion("net.minecraft", minecraftVersion, true); - if (!fabricVersion.isEmpty()) - components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion); - if (!quiltVersion.isEmpty()) - components->setComponentVersion("org.quiltmc.quilt-loader", quiltVersion); - if (!forgeVersion.isEmpty()) - components->setComponentVersion("net.minecraftforge", forgeVersion); - if (m_instIcon != "default") - { - instance.setIconKey(m_instIcon); - } - else - { - instance.setIconKey("modrinth"); - } - instance.setName(m_instName); - instance.saveNow(); - - m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network()); - for (auto file : files) - { - auto path = FS::PathCombine(m_stagingPath, ".minecraft", file.path); - qDebug() << "Will try to download" << file.downloads.front() << "to" << path; - auto dl = Net::Download::makeFile(file.downloads.dequeue(), path); - dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); - m_filesNetJob->addNetAction(dl); - - if (file.downloads.size() > 0) { - // FIXME: This really needs to be put into a ConcurrentTask of - // MultipleOptionsTask's , once those exist :) - connect(dl.get(), &NetAction::failed, [this, &file, path, dl]{ - auto dl = Net::Download::makeFile(file.downloads.dequeue(), path); - dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); - m_filesNetJob->addNetAction(dl); - dl->succeeded(); - }); - } - } - connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() - { - m_filesNetJob.reset(); - emitSucceeded(); - } - ); - connect(m_filesNetJob.get(), &NetJob::failed, [&](const QString &reason) - { - m_filesNetJob.reset(); - emitFailed(reason); + connect(this, &Task::aborted, inst_creation_task, [inst_creation_task] { + inst_creation_task->abort(); }); - connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) - { - setProgress(current, total); - }); - setStatus(tr("Downloading mods...")); - m_filesNetJob->start(); + + inst_creation_task->start(); } diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp new file mode 100644 index 00000000..7eb6cc8f --- /dev/null +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -0,0 +1,225 @@ +#include "ModrinthInstanceCreationTask.h" + +#include "Application.h" +#include "FileSystem.h" +#include "Json.h" + +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" + +#include "net/NetJob.h" +#include "net/ChecksumValidator.h" + +#include "settings/INISettingsObject.h" + +#include "ui/dialogs/CustomMessageBox.h" + +bool ModrinthCreationTask::createInstance() +{ + QEventLoop loop; + + if (m_files.empty() && !parseManifest()) + return false; + + auto mcPath = FS::PathCombine(m_stagingPath, ".minecraft"); + + auto override_path = FS::PathCombine(m_stagingPath, "overrides"); + if (QFile::exists(override_path)) { + if (!QFile::rename(override_path, mcPath)) { + setError(tr("Could not rename the overrides folder:\n") + "overrides"); + return false; + } + } + + // Do client overrides + auto client_override_path = FS::PathCombine(m_stagingPath, "client-overrides"); + if (QFile::exists(client_override_path)) { + if (!FS::overrideFolder(mcPath, client_override_path)) { + setError(tr("Could not rename the client overrides folder:\n") + "client overrides"); + return false; + } + } + + QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); + auto instanceSettings = std::make_shared(configPath); + MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); + auto components = instance.getPackProfile(); + components->buildingFromScratch(); + components->setComponentVersion("net.minecraft", minecraftVersion, true); + + if (!fabricVersion.isEmpty()) + components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion); + if (!quiltVersion.isEmpty()) + components->setComponentVersion("org.quiltmc.quilt-loader", quiltVersion); + if (!forgeVersion.isEmpty()) + components->setComponentVersion("net.minecraftforge", forgeVersion); + if (m_instIcon != "default") { + instance.setIconKey(m_instIcon); + } else { + instance.setIconKey("modrinth"); + } + instance.setName(m_instName); + instance.setManagedPack("modrinth", getManagedPackID(), m_managed_name, m_managed_id, {}); + instance.saveNow(); + + m_files_job = new NetJob(tr("Mod download"), APPLICATION->network()); + + for (auto file : m_files) { + auto path = FS::PathCombine(m_stagingPath, ".minecraft", file.path); + qDebug() << "Will try to download" << file.downloads.front() << "to" << path; + auto dl = Net::Download::makeFile(file.downloads.dequeue(), path); + dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); + m_files_job->addNetAction(dl); + + if (file.downloads.size() > 0) { + // FIXME: This really needs to be put into a ConcurrentTask of + // MultipleOptionsTask's , once those exist :) + connect(dl.get(), &NetAction::failed, [this, &file, path, dl] { + auto dl = Net::Download::makeFile(file.downloads.dequeue(), path); + dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); + m_files_job->addNetAction(dl); + dl->succeeded(); + }); + } + } + + bool ended_well = false; + + connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { ended_well = true; }); + connect(m_files_job.get(), &NetJob::failed, [&](const QString& reason) { + ended_well = false; + setError(reason); + }); + connect(m_files_job.get(), &NetJob::finished, &loop, &QEventLoop::quit); + connect(m_files_job.get(), &NetJob::progress, [&](qint64 current, qint64 total) { setProgress(current, total); }); + + setStatus(tr("Downloading mods...")); + m_files_job->start(); + + loop.exec(); + + return ended_well; +} + +bool ModrinthCreationTask::parseManifest() +{ + try { + QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json"); + auto doc = Json::requireDocument(indexPath); + auto obj = Json::requireObject(doc, "modrinth.index.json"); + int formatVersion = Json::requireInteger(obj, "formatVersion", "modrinth.index.json"); + if (formatVersion == 1) { + auto game = Json::requireString(obj, "game", "modrinth.index.json"); + if (game != "minecraft") { + throw JSONValidationError("Unknown game: " + game); + } + + m_managed_version_id = Json::ensureString(obj, "versionId", "Managed ID"); + m_managed_name = Json::ensureString(obj, "name", "Managed Name"); + + auto jsonFiles = Json::requireIsArrayOf(obj, "files", "modrinth.index.json"); + bool had_optional = false; + for (auto modInfo : jsonFiles) { + Modrinth::File file; + file.path = Json::requireString(modInfo, "path"); + + auto env = Json::ensureObject(modInfo, "env"); + // 'env' field is optional + if (!env.isEmpty()) { + QString support = Json::ensureString(env, "client", "unsupported"); + if (support == "unsupported") { + continue; + } else if (support == "optional") { + // TODO: Make a review dialog for choosing which ones the user wants! + if (!had_optional) { + had_optional = true; + auto info = CustomMessageBox::selectable( + m_parent, tr("Optional mod detected!"), + tr("One or more mods from this modpack are optional. They will be downloaded, but disabled by default!"), + QMessageBox::Information); + info->exec(); + } + + if (file.path.endsWith(".jar")) + file.path += ".disabled"; + } + } + + QJsonObject hashes = Json::requireObject(modInfo, "hashes"); + QString hash; + QCryptographicHash::Algorithm hashAlgorithm; + hash = Json::ensureString(hashes, "sha1"); + hashAlgorithm = QCryptographicHash::Sha1; + if (hash.isEmpty()) { + hash = Json::ensureString(hashes, "sha512"); + hashAlgorithm = QCryptographicHash::Sha512; + if (hash.isEmpty()) { + hash = Json::ensureString(hashes, "sha256"); + hashAlgorithm = QCryptographicHash::Sha256; + if (hash.isEmpty()) { + throw JSONValidationError("No hash found for: " + file.path); + } + } + } + file.hash = QByteArray::fromHex(hash.toLatin1()); + file.hashAlgorithm = hashAlgorithm; + + // Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode + // (as Modrinth seems to incorrectly handle spaces) + + auto download_arr = Json::ensureArray(modInfo, "downloads"); + for (auto download : download_arr) { + qWarning() << download.toString(); + bool is_last = download.toString() == download_arr.last().toString(); + + auto download_url = QUrl(download.toString()); + + if (!download_url.isValid()) { + qDebug() + << QString("Download URL (%1) for %2 is not a correctly formatted URL").arg(download_url.toString(), file.path); + if (is_last && file.downloads.isEmpty()) + throw JSONValidationError(tr("Download URL for %1 is not a correctly formatted URL").arg(file.path)); + } else { + file.downloads.push_back(download_url); + } + } + + m_files.push_back(file); + } + + auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json"); + for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) { + QString name = it.key(); + if (name == "minecraft") { + minecraftVersion = Json::requireString(*it, "Minecraft version"); + } else if (name == "fabric-loader") { + fabricVersion = Json::requireString(*it, "Fabric Loader version"); + } else if (name == "quilt-loader") { + quiltVersion = Json::requireString(*it, "Quilt Loader version"); + } else if (name == "forge") { + forgeVersion = Json::requireString(*it, "Forge version"); + } else { + throw JSONValidationError("Unknown dependency type: " + name); + } + } + } else { + throw JSONValidationError(QStringLiteral("Unknown format version: %s").arg(formatVersion)); + } + QFile::remove(indexPath); + } catch (const JSONValidationError& e) { + setError(tr("Could not understand pack index:\n") + e.cause()); + return false; + } + + return true; +} + +QString ModrinthCreationTask::getManagedPackID() const +{ + if (!m_source_url.isEmpty()) { + QRegularExpression regex(R"(data\/(.*)\/versions)"); + return regex.match(m_source_url).captured(0); + } + + return {}; +} diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h new file mode 100644 index 00000000..61f7dd5c --- /dev/null +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h @@ -0,0 +1,39 @@ +#pragma once + +#include "InstanceCreationTask.h" + +#include "modplatform/modrinth/ModrinthPackManifest.h" + +#include "net/NetJob.h" + +class ModrinthCreationTask final : public InstanceCreationTask { + Q_OBJECT + + public: + ModrinthCreationTask(QString staging_path, SettingsObjectPtr global_settings, QWidget* parent, QString source_url = {}) + : InstanceCreationTask(), m_parent(parent) + { + setStagingPath(staging_path); + setParentSettings(global_settings); + } + + bool abort() override; + bool canAbort() const override { return true; } + + bool updateInstance() override; + bool createInstance() override; + + private: + bool parseManifest(); + QString getManagedPackID() const; + + private: + QWidget* m_parent = nullptr; + + QString minecraftVersion, fabricVersion, quiltVersion, forgeVersion; + QString m_managed_id, m_managed_version_id, m_managed_name; + QString m_source_url; + + std::vector m_files; + NetJob::Ptr m_files_job; +}; From 208ed73e59c46ee7966f463558c07805a9b541e6 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 8 Jul 2022 13:00:44 -0300 Subject: [PATCH 202/273] feat: add early modrinth pack updating Still some FIXMEs and TODOs to consider, but the general thing is here! Signed-off-by: flow --- launcher/InstanceImportTask.cpp | 11 +- launcher/InstanceList.cpp | 66 ++++++++-- launcher/InstanceList.h | 7 +- launcher/InstanceTask.h | 7 ++ .../modrinth/ModrinthInstanceCreationTask.cpp | 118 ++++++++++++++++-- .../modrinth/ModrinthInstanceCreationTask.h | 2 +- 6 files changed, 185 insertions(+), 26 deletions(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index b19b5fa6..4bdf9cd2 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -593,15 +593,16 @@ void InstanceImportTask::processModrinth() inst_creation_task->setIcon(m_instIcon); inst_creation_task->setGroup(m_instGroup); - connect(inst_creation_task, &Task::succeeded, this, &InstanceImportTask::emitSucceeded); + connect(inst_creation_task, &Task::succeeded, this, [this, inst_creation_task] { + setOverride(inst_creation_task->shouldOverride()); + emitSucceeded(); + }); connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed); connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress); connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus); - connect(inst_creation_task, &Task::finished, this, [inst_creation_task]{ inst_creation_task->deleteLater(); }); + connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater); - connect(this, &Task::aborted, inst_creation_task, [inst_creation_task] { - inst_creation_task->abort(); - }); + connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort); inst_creation_task->start(); } diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 4447a17c..698aa24e 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -535,7 +535,20 @@ InstancePtr InstanceList::getInstanceById(QString instId) const return InstancePtr(); } -QModelIndex InstanceList::getInstanceIndexById(const QString& id) const +InstancePtr InstanceList::getInstanceByManagedName(QString managed_name) const +{ + if (managed_name.isEmpty()) + return {}; + + for (auto instance : m_instances) { + if (instance->getManagedPackName() == managed_name) + return instance; + } + + return {}; +} + +QModelIndex InstanceList::getInstanceIndexById(const QString &id) const { return index(getInstIndex(getInstanceById(id).get())); } @@ -764,9 +777,8 @@ class InstanceStaging : public Task { Q_OBJECT const unsigned minBackoff = 1; const unsigned maxBackoff = 16; - public: - InstanceStaging(InstanceList* parent, Task* child, const QString& stagingPath, const QString& instanceName, const QString& groupName) + InstanceStaging(InstanceList* parent, InstanceTask* child, const QString& stagingPath, const QString& instanceName, const QString& groupName) : backoff(minBackoff, maxBackoff) { m_parent = parent; @@ -808,7 +820,8 @@ class InstanceStaging : public Task { void childSucceded() { unsigned sleepTime = backoff(); - if (m_parent->commitStagedInstance(m_stagingPath, m_instanceName, m_groupName)) { + if (m_parent->commitStagedInstance(m_stagingPath, m_instanceName, m_groupName, m_child->shouldOverride())) + { emitSucceeded(); return; } @@ -834,8 +847,8 @@ class InstanceStaging : public Task { */ ExponentialSeries backoff; QString m_stagingPath; - InstanceList* m_parent; - unique_qobject_ptr m_child; + InstanceList * m_parent; + unique_qobject_ptr m_child; QString m_instanceName; QString m_groupName; QTimer m_backoffTimer; @@ -866,23 +879,52 @@ QString InstanceList::getStagedInstancePath() return path; } -bool InstanceList::commitStagedInstance(const QString& path, const QString& instanceName, const QString& groupName) +bool InstanceList::commitStagedInstance(const QString& path, const QString& instanceName, const QString& groupName, bool should_override) { QDir dir; - QString instID = FS::DirNameFromString(instanceName, m_instDir); + QString instID; + InstancePtr inst; + + QString raw_inst_name = instanceName.section(' ', 0, -2); + if (should_override) { + // This is to avoid problems when the instance folder gets manually renamed + if ((inst = getInstanceByManagedName(raw_inst_name))) { + instID = QFileInfo(inst->instanceRoot()).fileName(); + } else { + instID = FS::RemoveInvalidFilenameChars(raw_inst_name, '-'); + } + } else { + instID = FS::DirNameFromString(raw_inst_name, m_instDir); + } + { WatchLock lock(m_watcher, m_instDir); QString destination = FS::PathCombine(m_instDir, instID); - if (!dir.rename(path, destination)) { - qWarning() << "Failed to move" << path << "to" << destination; - return false; + + if (should_override) { + if (!FS::overrideFolder(destination, path)) { + qWarning() << "Failed to override" << path << "to" << destination; + return false; + } + + if (!inst) + inst = getInstanceById(instID); + if (inst) + inst->setName(instanceName); + } else { + if (!dir.rename(path, destination)) { + qWarning() << "Failed to move" << path << "to" << destination; + return false; + } } m_instanceGroupIndex[instID] = groupName; - instanceSet.insert(instID); m_groupNameCache.insert(groupName); + instanceSet.insert(instID); + emit instancesChanged(); emit instanceSelectRequest(instID); } + saveGroupList(); return true; } diff --git a/launcher/InstanceList.h b/launcher/InstanceList.h index 62282f04..6b4dcfa4 100644 --- a/launcher/InstanceList.h +++ b/launcher/InstanceList.h @@ -101,7 +101,10 @@ public: InstListError loadList(); void saveNow(); + /* O(n) */ InstancePtr getInstanceById(QString id) const; + /* O(n) */ + InstancePtr getInstanceByManagedName(QString managed_name) const; QModelIndex getInstanceIndexById(const QString &id) const; QStringList getGroups(); bool isGroupCollapsed(const QString &groupName); @@ -127,8 +130,10 @@ public: /** * Commit the staging area given by @keyPath to the provider - used when creation succeeds. * Used by instance manipulation tasks. + * should_override is used when another similar instance already exists, and we want to override it + * - for instance, when updating it. */ - bool commitStagedInstance(const QString & keyPath, const QString& instanceName, const QString & groupName); + bool commitStagedInstance(const QString & keyPath, const QString& instanceName, const QString & groupName, bool should_override); /** * Destroy a previously created staging area given by @keyPath - used when creation fails. diff --git a/launcher/InstanceTask.h b/launcher/InstanceTask.h index 82e23f11..02810a52 100644 --- a/launcher/InstanceTask.h +++ b/launcher/InstanceTask.h @@ -43,10 +43,17 @@ public: return m_instGroup; } + bool shouldOverride() const { return m_override_existing; } + +protected: + void setOverride(bool override) { m_override_existing = override; } + protected: /* data */ SettingsObjectPtr m_globalSettings; QString m_instName; QString m_instIcon; QString m_instGroup; QString m_stagingPath; + + bool m_override_existing = false; }; diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index 7eb6cc8f..efb8c99b 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -2,25 +2,128 @@ #include "Application.h" #include "FileSystem.h" +#include "InstanceList.h" #include "Json.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" -#include "net/NetJob.h" +#include "modplatform/ModIndex.h" + #include "net/ChecksumValidator.h" #include "settings/INISettingsObject.h" #include "ui/dialogs/CustomMessageBox.h" +#include + +bool ModrinthCreationTask::abort() +{ + if (m_files_job) + return m_files_job->abort(); + return true; +} + +bool ModrinthCreationTask::updateInstance() +{ + auto instance_list = APPLICATION->instances(); + + // FIXME: How to handle situations when there's more than one install already for a given modpack? + // Based on the way we create the instance name (name + " " + version). Is there a better way? + auto inst = instance_list->getInstanceByManagedName(m_instName.section(' ', 0, -2)); + + if (!inst) { + inst = instance_list->getInstanceById(m_instName); + + if (!inst) + return false; + } + + QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json"); + if (!parseManifest(index_path, m_files)) + return false; + + auto version_id = inst->getManagedPackVersionID(); + auto version_str = !version_id.isEmpty() ? tr(" (version %1)").arg(version_id) : ""; + + auto info = CustomMessageBox::selectable(m_parent, tr("Similar modpack was found!"), + tr("One or more of your instances are from this same modpack%1. Do you want to create a " + "separate instance, or update the existing one?") + .arg(version_str), + QMessageBox::Information, QMessageBox::Ok | QMessageBox::Abort); + info->setButtonText(QMessageBox::Ok, tr("Update existing instance")); + info->setButtonText(QMessageBox::Abort, tr("Create new instance")); + + if (info->exec() && info->clickedButton() == info->button(QMessageBox::Abort)) + return false; + + // Remove repeated files, we don't need to download them! + QDir old_inst_dir(inst->instanceRoot()); + + QString old_index_path(FS::PathCombine(old_inst_dir.absolutePath(), "mrpack", "modrinth.index.json")); + QFileInfo old_index_file(old_index_path); + if (old_index_file.exists()) { + std::vector old_files; + parseManifest(old_index_path, old_files); + + // Let's remove all duplicated, identical resources! + auto files_iterator = m_files.begin(); +begin: + while (files_iterator != m_files.end()) { + auto const& file = *files_iterator; + + auto old_files_iterator = old_files.begin(); + while (old_files_iterator != old_files.end()) { + auto const& old_file = *old_files_iterator; + + if (old_file.hash == file.hash) { + qDebug() << "Removed file at" << file.path << "from list of downloads"; + files_iterator = m_files.erase(files_iterator); + old_files_iterator = old_files.erase(old_files_iterator); + goto begin; // Sorry :c + } + + old_files_iterator++; + } + + files_iterator++; + } + + // Some files were removed from the old version, and some will be downloaded in an updated version, + // so we're fine removing them! + if (!old_files.empty()) { + QDir old_minecraft_dir(inst->gameRoot()); + for (auto const& file : old_files) { + qWarning() << "Removing" << file.path; + old_minecraft_dir.remove(file.path); + } + } + } + + // TODO: Currently 'overrides' will always override the stuff on update. How do we preserve unchanged overrides? + + setOverride(true); + qDebug() << "Will override instance!"; + + // We let it go through the createInstance() stage, just with a couple modifications for updating + return false; +} + +// https://docs.modrinth.com/docs/modpacks/format_definition/ bool ModrinthCreationTask::createInstance() { QEventLoop loop; - if (m_files.empty() && !parseManifest()) + QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json"); + if (m_files.empty() && !parseManifest(index_path, m_files)) return false; + // Keep index file in case we need it some other time (like when changing versions) + QString new_index_place(FS::PathCombine(m_stagingPath, "mrpack", "modrinth.index.json")); + FS::ensureFilePathExists(new_index_place); + QFile::rename(index_path, new_index_place); + auto mcPath = FS::PathCombine(m_stagingPath, ".minecraft"); auto override_path = FS::PathCombine(m_stagingPath, "overrides"); @@ -43,6 +146,7 @@ bool ModrinthCreationTask::createInstance() QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); auto instanceSettings = std::make_shared(configPath); MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); + auto components = instance.getPackProfile(); components->buildingFromScratch(); components->setComponentVersion("net.minecraft", minecraftVersion, true); @@ -53,6 +157,7 @@ bool ModrinthCreationTask::createInstance() components->setComponentVersion("org.quiltmc.quilt-loader", quiltVersion); if (!forgeVersion.isEmpty()) components->setComponentVersion("net.minecraftforge", forgeVersion); + if (m_instIcon != "default") { instance.setIconKey(m_instIcon); } else { @@ -101,11 +206,10 @@ bool ModrinthCreationTask::createInstance() return ended_well; } -bool ModrinthCreationTask::parseManifest() +bool ModrinthCreationTask::parseManifest(QString index_path, std::vector& files) { try { - QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json"); - auto doc = Json::requireDocument(indexPath); + auto doc = Json::requireDocument(index_path); auto obj = Json::requireObject(doc, "modrinth.index.json"); int formatVersion = Json::requireInteger(obj, "formatVersion", "modrinth.index.json"); if (formatVersion == 1) { @@ -184,7 +288,7 @@ bool ModrinthCreationTask::parseManifest() } } - m_files.push_back(file); + files.push_back(file); } auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json"); @@ -205,7 +309,7 @@ bool ModrinthCreationTask::parseManifest() } else { throw JSONValidationError(QStringLiteral("Unknown format version: %s").arg(formatVersion)); } - QFile::remove(indexPath); + } catch (const JSONValidationError& e) { setError(tr("Could not understand pack index:\n") + e.cause()); return false; diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h index 61f7dd5c..4e804e58 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h @@ -24,7 +24,7 @@ class ModrinthCreationTask final : public InstanceCreationTask { bool createInstance() override; private: - bool parseManifest(); + bool parseManifest(QString, std::vector&); QString getManagedPackID() const; private: From 242fb156a281ef188c9fd75969c5d70ba6f8c140 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 21 Jul 2022 16:40:28 -0300 Subject: [PATCH 203/273] feat: add 'getFiles' by fileIds route in Flame API Signed-off-by: flow --- launcher/modplatform/flame/FlameAPI.cpp | 23 +++++++++++++++++++++++ launcher/modplatform/flame/FlameAPI.h | 1 + 2 files changed, 24 insertions(+) diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp index 9c74918b..f8f50dc6 100644 --- a/launcher/modplatform/flame/FlameAPI.cpp +++ b/launcher/modplatform/flame/FlameAPI.cpp @@ -183,3 +183,26 @@ auto FlameAPI::getProjects(QStringList addonIds, QByteArray* response) const -> return netJob; } + +auto FlameAPI::getFiles(QStringList fileIds, QByteArray* response) const -> NetJob* +{ + auto* netJob = new NetJob(QString("Flame::GetFiles"), APPLICATION->network()); + + QJsonObject body_obj; + QJsonArray files_arr; + for (auto& fileId : fileIds) { + files_arr.append(fileId); + } + + body_obj["fileIds"] = files_arr; + + QJsonDocument body(body_obj); + auto body_raw = body.toJson(); + + netJob->addNetAction(Net::Upload::makeByteArray(QString("https://api.curseforge.com/v1/mods/files"), response, body_raw)); + + QObject::connect(netJob, &NetJob::finished, [response, netJob] { delete response; netJob->deleteLater(); }); + QObject::connect(netJob, &NetJob::failed, [body_raw] { qDebug() << body_raw; }); + + return netJob; +} diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 4eac0664..5e6166b9 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -12,6 +12,7 @@ class FlameAPI : public NetworkModAPI { auto getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion; auto getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* override; + auto getFiles(QStringList fileIds, QByteArray* response) const -> NetJob*; private: inline auto getSortFieldInt(QString sortString) const -> int From 2246c3359bcd07d8bfab949d79a1e428a948f1b7 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 21 Jul 2022 16:41:44 -0300 Subject: [PATCH 204/273] refactor: add `throw_on_blocked` arg to Flame file parse Signed-off-by: flow --- launcher/modplatform/flame/PackManifest.cpp | 4 ++-- launcher/modplatform/flame/PackManifest.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/launcher/modplatform/flame/PackManifest.cpp b/launcher/modplatform/flame/PackManifest.cpp index 12a4b990..81395fcd 100644 --- a/launcher/modplatform/flame/PackManifest.cpp +++ b/launcher/modplatform/flame/PackManifest.cpp @@ -61,7 +61,7 @@ void Flame::loadManifest(Flame::Manifest& m, const QString& filepath) loadManifestV1(m, obj); } -bool Flame::File::parseFromObject(const QJsonObject& obj) +bool Flame::File::parseFromObject(const QJsonObject& obj, bool throw_on_blocked) { fileName = Json::requireString(obj, "fileName"); // This is a piece of a Flame project JSON pulled out into the file metadata (here) for convenience @@ -91,7 +91,7 @@ bool Flame::File::parseFromObject(const QJsonObject& obj) // may throw, if the project is blocked QString rawUrl = Json::ensureString(obj, "downloadUrl"); url = QUrl(rawUrl, QUrl::TolerantMode); - if (!url.isValid()) { + if (!url.isValid() && throw_on_blocked) { throw JSONValidationError(QString("Invalid URL: %1").arg(rawUrl)); } diff --git a/launcher/modplatform/flame/PackManifest.h b/launcher/modplatform/flame/PackManifest.h index 677db1c3..a69e1321 100644 --- a/launcher/modplatform/flame/PackManifest.h +++ b/launcher/modplatform/flame/PackManifest.h @@ -46,7 +46,7 @@ namespace Flame struct File { // NOTE: throws JSONValidationError - bool parseFromObject(const QJsonObject& object); + bool parseFromObject(const QJsonObject& object, bool throw_on_blocked = true); int projectId = 0; int fileId = 0; From 72d2ca234e80fe65bb6a7d5fe106b01d9dc6f096 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 8 Jul 2022 18:44:43 -0300 Subject: [PATCH 205/273] refactor: move flame modpack import to separate file Signed-off-by: flow --- launcher/CMakeLists.txt | 2 + launcher/InstanceCreationTask.h | 2 + launcher/InstanceImportTask.cpp | 298 +----------------- .../flame/FlameInstanceCreationTask.cpp | 284 +++++++++++++++++ .../flame/FlameInstanceCreationTask.h | 32 ++ 5 files changed, 337 insertions(+), 281 deletions(-) create mode 100644 launcher/modplatform/flame/FlameInstanceCreationTask.cpp create mode 100644 launcher/modplatform/flame/FlameInstanceCreationTask.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 68439ee8..7bd92fac 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -486,6 +486,8 @@ set(FLAME_SOURCES modplatform/flame/FileResolvingTask.cpp modplatform/flame/FlameCheckUpdate.cpp modplatform/flame/FlameCheckUpdate.h + modplatform/flame/FlameInstanceCreationTask.h + modplatform/flame/FlameInstanceCreationTask.cpp ) set(MODRINTH_SOURCES diff --git a/launcher/InstanceCreationTask.h b/launcher/InstanceCreationTask.h index af854713..68c5de59 100644 --- a/launcher/InstanceCreationTask.h +++ b/launcher/InstanceCreationTask.h @@ -31,6 +31,8 @@ class InstanceCreationTask : public InstanceTask { */ virtual bool createInstance() { return false; }; + QString getError() const { return m_error_message; } + protected: void setError(QString message) { m_error_message = message; }; diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 4bdf9cd2..72c2496f 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -50,10 +50,9 @@ #include "Json.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" -#include "modplatform/flame/FileResolvingTask.h" -#include "modplatform/flame/PackManifest.h" #include "modplatform/technic/TechnicPackProcessor.h" #include "modplatform/modrinth/ModrinthInstanceCreationTask.h" +#include "modplatform/flame/FlameInstanceCreationTask.h" #include "Application.h" #include "icons/IconList.h" @@ -262,287 +261,24 @@ void InstanceImportTask::extractAborted() void InstanceImportTask::processFlame() { - const static QMap forgemap = { - {"1.2.5", "3.4.9.171"}, - {"1.4.2", "6.0.1.355"}, - {"1.4.7", "6.6.2.534"}, - {"1.5.2", "7.8.1.737"} - }; - Flame::Manifest pack; - try - { - QString configPath = FS::PathCombine(m_stagingPath, "manifest.json"); - Flame::loadManifest(pack, configPath); - QFile::remove(configPath); - } - catch (const JSONValidationError &e) - { - emitFailed(tr("Could not understand pack manifest:\n") + e.cause()); - return; - } - if(!pack.overrides.isEmpty()) - { - QString overridePath = FS::PathCombine(m_stagingPath, pack.overrides); - if (QFile::exists(overridePath)) - { - QString mcPath = FS::PathCombine(m_stagingPath, "minecraft"); - if (!QFile::rename(overridePath, mcPath)) - { - emitFailed(tr("Could not rename the overrides folder:\n") + pack.overrides); - return; - } - } - else - { - logWarning(tr("The specified overrides folder (%1) is missing. Maybe the modpack was already used before?").arg(pack.overrides)); - } - } + auto* inst_creation_task = new FlameCreationTask(m_stagingPath, m_globalSettings, m_parent); - QString forgeVersion; - QString fabricVersion; - // TODO: is Quilt relevant here? - for(auto &loader: pack.minecraft.modLoaders) - { - auto id = loader.id; - if(id.startsWith("forge-")) - { - id.remove("forge-"); - forgeVersion = id; - continue; - } - if(id.startsWith("fabric-")) - { - id.remove("fabric-"); - fabricVersion = id; - continue; - } - logWarning(tr("Unknown mod loader in manifest: %1").arg(id)); - } - - QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); - auto instanceSettings = std::make_shared(configPath); - MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); - auto mcVersion = pack.minecraft.version; - // Hack to correct some 'special sauce'... - if(mcVersion.endsWith('.')) - { - mcVersion.remove(QRegularExpression("[.]+$")); - logWarning(tr("Mysterious trailing dots removed from Minecraft version while importing pack.")); - } - auto components = instance.getPackProfile(); - components->buildingFromScratch(); - components->setComponentVersion("net.minecraft", mcVersion, true); - if(!forgeVersion.isEmpty()) - { - // FIXME: dirty, nasty, hack. Proper solution requires dependency resolution and knowledge of the metadata. - if(forgeVersion == "recommended") - { - if(forgemap.contains(mcVersion)) - { - forgeVersion = forgemap[mcVersion]; - } - else - { - logWarning(tr("Could not map recommended Forge version for Minecraft %1").arg(mcVersion)); - } - } - components->setComponentVersion("net.minecraftforge", forgeVersion); - } - if(!fabricVersion.isEmpty()) - { - components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion); - } - if (m_instIcon != "default") - { - instance.setIconKey(m_instIcon); - } - else - { - if(pack.name.contains("Direwolf20")) - { - instance.setIconKey("steve"); - } - else if(pack.name.contains("FTB") || pack.name.contains("Feed The Beast")) - { - instance.setIconKey("ftb_logo"); - } - else - { - // default to something other than the MultiMC default to distinguish these - instance.setIconKey("flame"); - } - } - QString jarmodsPath = FS::PathCombine(m_stagingPath, "minecraft", "jarmods"); - QFileInfo jarmodsInfo(jarmodsPath); - if(jarmodsInfo.isDir()) - { - // install all the jar mods - qDebug() << "Found jarmods:"; - QDir jarmodsDir(jarmodsPath); - QStringList jarMods; - for (auto info: jarmodsDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) - { - qDebug() << info.fileName(); - jarMods.push_back(info.absoluteFilePath()); - } - auto profile = instance.getPackProfile(); - profile->installJarMods(jarMods); - // nuke the original files - FS::deletePath(jarmodsPath); - } - instance.setName(m_instName); - m_modIdResolver = new Flame::FileResolvingTask(APPLICATION->network(), pack); - connect(m_modIdResolver.get(), &Flame::FileResolvingTask::succeeded, [&]() - { - auto results = m_modIdResolver->getResults(); - //first check for blocked mods - QString text; - QList urls; - auto anyBlocked = false; - for(const auto& result: results.files.values()) { - if (!result.resolved || result.url.isEmpty()) { - text += QString("%1:
%2
").arg(result.fileName, result.websiteUrl); - urls.append(QUrl(result.websiteUrl)); - anyBlocked = true; - } - } - if(anyBlocked) { - 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.
" - "You will need to manually download them and add them to the modpack"), - text, - urls); - message_dialog->setModal(true); - - if (message_dialog->exec()) { - m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network()); - for (const auto &result: m_modIdResolver->getResults().files) { - QString filename = result.fileName; - if (!result.required) { - filename += ".disabled"; - } - - auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename); - auto path = FS::PathCombine(m_stagingPath, relpath); - - switch (result.type) { - case Flame::File::Type::Folder: { - logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath)); - // fall-through intentional, we treat these as plain old mods and dump them wherever. - } - case Flame::File::Type::SingleFile: - case Flame::File::Type::Mod: { - if (!result.url.isEmpty()) { - qDebug() << "Will download" << result.url << "to" << path; - auto dl = Net::Download::makeFile(result.url, path); - m_filesNetJob->addNetAction(dl); - } - break; - } - case Flame::File::Type::Modpack: - logWarning( - tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg( - relpath)); - break; - case Flame::File::Type::Cmod2: - case Flame::File::Type::Ctoc: - case Flame::File::Type::Unknown: - logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath)); - break; - } - } - m_modIdResolver.reset(); - connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() { - m_filesNetJob.reset(); - emitSucceeded(); - } - ); - connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason) { - m_filesNetJob.reset(); - emitFailed(reason); - }); - connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) { - setProgress(current, total); - }); - setStatus(tr("Downloading mods...")); - m_filesNetJob->start(); - } else { - m_modIdResolver.reset(); - emitFailed("Canceled"); - } - } else { - //TODO extract to function ? - m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network()); - for (const auto &result: m_modIdResolver->getResults().files) { - QString filename = result.fileName; - if (!result.required) { - filename += ".disabled"; - } - - auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename); - auto path = FS::PathCombine(m_stagingPath, relpath); - - switch (result.type) { - case Flame::File::Type::Folder: { - logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath)); - // fall-through intentional, we treat these as plain old mods and dump them wherever. - } - case Flame::File::Type::SingleFile: - case Flame::File::Type::Mod: { - if (!result.url.isEmpty()) { - qDebug() << "Will download" << result.url << "to" << path; - auto dl = Net::Download::makeFile(result.url, path); - m_filesNetJob->addNetAction(dl); - } - break; - } - case Flame::File::Type::Modpack: - logWarning( - tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg( - relpath)); - break; - case Flame::File::Type::Cmod2: - case Flame::File::Type::Ctoc: - case Flame::File::Type::Unknown: - logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath)); - break; - } - } - m_modIdResolver.reset(); - connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() { - m_filesNetJob.reset(); - emitSucceeded(); - } - ); - connect(m_filesNetJob.get(), &NetJob::failed, [&](QString reason) { - m_filesNetJob.reset(); - emitFailed(reason); - }); - connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) { - setProgress(current, total); - }); - setStatus(tr("Downloading mods...")); - m_filesNetJob->start(); - } - } - ); - connect(m_modIdResolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason) - { - m_modIdResolver.reset(); - emitFailed(tr("Unable to resolve mod IDs:\n") + reason); + inst_creation_task->setName(m_instName); + inst_creation_task->setIcon(m_instIcon); + inst_creation_task->setGroup(m_instGroup); + + connect(inst_creation_task, &Task::succeeded, this, [this, inst_creation_task] { + setOverride(inst_creation_task->shouldOverride()); + emitSucceeded(); }); - connect(m_modIdResolver.get(), &Flame::FileResolvingTask::progress, [&](qint64 current, qint64 total) - { - setProgress(current, total); - }); - connect(m_modIdResolver.get(), &Flame::FileResolvingTask::status, [&](QString status) - { - setStatus(status); - }); - m_modIdResolver->start(); + connect(inst_creation_task, &Task::failed, this, &InstanceImportTask::emitFailed); + connect(inst_creation_task, &Task::progress, this, &InstanceImportTask::setProgress); + connect(inst_creation_task, &Task::status, this, &InstanceImportTask::setStatus); + connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater); + + connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort); + + inst_creation_task->start(); } void InstanceImportTask::processTechnic() diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp new file mode 100644 index 00000000..431c8b3d --- /dev/null +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -0,0 +1,284 @@ +#include "FlameInstanceCreationTask.h" + +#include "modplatform/flame/PackManifest.h" + +#include "Application.h" +#include "FileSystem.h" +#include "Json.h" + +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" + +#include "settings/INISettingsObject.h" + +#include "ui/dialogs/BlockedModsDialog.h" + +bool FlameCreationTask::abort() +{ + if (m_files_job) + m_files_job->abort(); + if (m_mod_id_resolver) + m_mod_id_resolver->abort(); + + return true; +} + +const static QMap forgemap = { { "1.2.5", "3.4.9.171" }, + { "1.4.2", "6.0.1.355" }, + { "1.4.7", "6.6.2.534" }, + { "1.5.2", "7.8.1.737" } }; + +bool FlameCreationTask::createInstance() +{ + QEventLoop loop; + + Flame::Manifest pack; + try { + QString configPath = FS::PathCombine(m_stagingPath, "manifest.json"); + Flame::loadManifest(pack, configPath); + QFile::remove(configPath); + } catch (const JSONValidationError& e) { + setError(tr("Could not understand pack manifest:\n") + e.cause()); + return false; + } + + if (!pack.overrides.isEmpty()) { + QString overridePath = FS::PathCombine(m_stagingPath, pack.overrides); + if (QFile::exists(overridePath)) { + QString mcPath = FS::PathCombine(m_stagingPath, "minecraft"); + if (!QFile::rename(overridePath, mcPath)) { + setError(tr("Could not rename the overrides folder:\n") + pack.overrides); + return false; + } + } else { + logWarning( + tr("The specified overrides folder (%1) is missing. Maybe the modpack was already used before?").arg(pack.overrides)); + } + } + + QString forgeVersion; + QString fabricVersion; + // TODO: is Quilt relevant here? + for (auto& loader : pack.minecraft.modLoaders) { + auto id = loader.id; + if (id.startsWith("forge-")) { + id.remove("forge-"); + forgeVersion = id; + continue; + } + if (id.startsWith("fabric-")) { + id.remove("fabric-"); + fabricVersion = id; + continue; + } + logWarning(tr("Unknown mod loader in manifest: %1").arg(id)); + } + + QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); + auto instanceSettings = std::make_shared(configPath); + MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); + auto mcVersion = pack.minecraft.version; + + // Hack to correct some 'special sauce'... + if (mcVersion.endsWith('.')) { + mcVersion.remove(QRegularExpression("[.]+$")); + logWarning(tr("Mysterious trailing dots removed from Minecraft version while importing pack.")); + } + + auto components = instance.getPackProfile(); + components->buildingFromScratch(); + components->setComponentVersion("net.minecraft", mcVersion, true); + if (!forgeVersion.isEmpty()) { + // FIXME: dirty, nasty, hack. Proper solution requires dependency resolution and knowledge of the metadata. + if (forgeVersion == "recommended") { + if (forgemap.contains(mcVersion)) { + forgeVersion = forgemap[mcVersion]; + } else { + logWarning(tr("Could not map recommended Forge version for Minecraft %1").arg(mcVersion)); + } + } + components->setComponentVersion("net.minecraftforge", forgeVersion); + } + if (!fabricVersion.isEmpty()) + components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion); + + if (m_instIcon != "default") { + instance.setIconKey(m_instIcon); + } else { + if (pack.name.contains("Direwolf20")) { + instance.setIconKey("steve"); + } else if (pack.name.contains("FTB") || pack.name.contains("Feed The Beast")) { + instance.setIconKey("ftb_logo"); + } else { + instance.setIconKey("flame"); + } + } + + QString jarmodsPath = FS::PathCombine(m_stagingPath, "minecraft", "jarmods"); + QFileInfo jarmodsInfo(jarmodsPath); + if (jarmodsInfo.isDir()) { + // install all the jar mods + qDebug() << "Found jarmods:"; + QDir jarmodsDir(jarmodsPath); + QStringList jarMods; + for (auto info : jarmodsDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) { + qDebug() << info.fileName(); + jarMods.push_back(info.absoluteFilePath()); + } + auto profile = instance.getPackProfile(); + profile->installJarMods(jarMods); + // nuke the original files + FS::deletePath(jarmodsPath); + } + + instance.setName(m_instName); + + m_mod_id_resolver = new Flame::FileResolvingTask(APPLICATION->network(), pack); + connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::succeeded, this, [this, &loop]{ + idResolverSucceeded(loop); + }); + connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason) { + m_mod_id_resolver.reset(); + setError(tr("Unable to resolve mod IDs:\n") + reason); + }); + connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::progress, this, &FlameCreationTask::setProgress); + connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::status, this, &FlameCreationTask::setStatus); + + m_mod_id_resolver->start(); + + loop.exec(); + + return getError().isEmpty(); +} + +void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) +{ + auto results = m_mod_id_resolver->getResults(); + // first check for blocked mods + QString text; + QList urls; + auto anyBlocked = false; + for (const auto& result : results.files.values()) { + if (!result.resolved || result.url.isEmpty()) { + text += QString("%1: %2
").arg(result.fileName, result.websiteUrl); + urls.append(QUrl(result.websiteUrl)); + anyBlocked = true; + } + } + if (anyBlocked) { + 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.
" + "You will need to manually download them and add them to the modpack"), + text, + urls); + message_dialog->setModal(true); + + if (message_dialog->exec()) { + m_files_job = new NetJob(tr("Mod download"), APPLICATION->network()); + for (const auto& result : m_mod_id_resolver->getResults().files) { + QString filename = result.fileName; + if (!result.required) { + filename += ".disabled"; + } + + auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename); + auto path = FS::PathCombine(m_stagingPath, relpath); + + switch (result.type) { + case Flame::File::Type::Folder: { + logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath)); + // fall-through intentional, we treat these as plain old mods and dump them wherever. + } + case Flame::File::Type::SingleFile: + case Flame::File::Type::Mod: { + if (!result.url.isEmpty()) { + qDebug() << "Will download" << result.url << "to" << path; + auto dl = Net::Download::makeFile(result.url, path); + m_files_job->addNetAction(dl); + } + break; + } + case Flame::File::Type::Modpack: + logWarning(tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(relpath)); + break; + case Flame::File::Type::Cmod2: + case Flame::File::Type::Ctoc: + case Flame::File::Type::Unknown: + logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath)); + break; + } + } + + m_mod_id_resolver.reset(); + connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { + m_files_job.reset(); + emitSucceeded(); + }); + connect(m_files_job.get(), &NetJob::failed, [&](QString reason) { + 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::finished, &loop, &QEventLoop::quit); + + setStatus(tr("Downloading mods...")); + m_files_job->start(); + } else { + m_mod_id_resolver.reset(); + setError("Canceled"); + } + } else { + // TODO extract to function ? + m_files_job = new NetJob(tr("Mod download"), APPLICATION->network()); + for (const auto& result : m_mod_id_resolver->getResults().files) { + QString filename = result.fileName; + if (!result.required) { + filename += ".disabled"; + } + + auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename); + auto path = FS::PathCombine(m_stagingPath, relpath); + + switch (result.type) { + case Flame::File::Type::Folder: { + logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath)); + // fall-through intentional, we treat these as plain old mods and dump them wherever. + } + case Flame::File::Type::SingleFile: + case Flame::File::Type::Mod: { + if (!result.url.isEmpty()) { + qDebug() << "Will download" << result.url << "to" << path; + auto dl = Net::Download::makeFile(result.url, path); + m_files_job->addNetAction(dl); + } + break; + } + case Flame::File::Type::Modpack: + logWarning(tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(relpath)); + break; + case Flame::File::Type::Cmod2: + case Flame::File::Type::Ctoc: + case Flame::File::Type::Unknown: + logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath)); + break; + } + } + + m_mod_id_resolver.reset(); + connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { + m_files_job.reset(); + emitSucceeded(); + }); + connect(m_files_job.get(), &NetJob::failed, [&](QString reason) { + 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::finished, &loop, &QEventLoop::quit); + + setStatus(tr("Downloading mods...")); + m_files_job->start(); + } +} diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.h b/launcher/modplatform/flame/FlameInstanceCreationTask.h new file mode 100644 index 00000000..efb099d9 --- /dev/null +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.h @@ -0,0 +1,32 @@ +#pragma once + +#include "InstanceCreationTask.h" + +#include "modplatform/flame/FileResolvingTask.h" + +#include "net/NetJob.h" + +class FlameCreationTask final : public InstanceCreationTask { + Q_OBJECT + + public: + FlameCreationTask(QString staging_path, SettingsObjectPtr global_settings, QWidget* parent) + : InstanceCreationTask(), m_parent(parent) + { + setStagingPath(staging_path); + setParentSettings(global_settings); + } + + bool abort() override; + + bool createInstance() override; + + private slots: + void idResolverSucceeded(QEventLoop&); + + private: + QWidget* m_parent = nullptr; + + shared_qobject_ptr m_mod_id_resolver; + NetJob::Ptr m_files_job; +}; From ebd46705d503a8627e759327e93c5a8432d4e47f Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 21 Jul 2022 16:43:08 -0300 Subject: [PATCH 206/273] refactor: move creation of CF file download task to a separate function Signed-off-by: flow --- .../flame/FlameInstanceCreationTask.cpp | 155 +++++++----------- .../flame/FlameInstanceCreationTask.h | 1 + 2 files changed, 57 insertions(+), 99 deletions(-) diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 431c8b3d..6ec1060c 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -154,6 +154,7 @@ bool FlameCreationTask::createInstance() void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) { auto results = m_mod_id_resolver->getResults(); + // first check for blocked mods QString text; QList urls; @@ -176,109 +177,65 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) message_dialog->setModal(true); if (message_dialog->exec()) { - m_files_job = new NetJob(tr("Mod download"), APPLICATION->network()); - for (const auto& result : m_mod_id_resolver->getResults().files) { - QString filename = result.fileName; - if (!result.required) { - filename += ".disabled"; - } - - auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename); - auto path = FS::PathCombine(m_stagingPath, relpath); - - switch (result.type) { - case Flame::File::Type::Folder: { - logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath)); - // fall-through intentional, we treat these as plain old mods and dump them wherever. - } - case Flame::File::Type::SingleFile: - case Flame::File::Type::Mod: { - if (!result.url.isEmpty()) { - qDebug() << "Will download" << result.url << "to" << path; - auto dl = Net::Download::makeFile(result.url, path); - m_files_job->addNetAction(dl); - } - break; - } - case Flame::File::Type::Modpack: - logWarning(tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(relpath)); - break; - case Flame::File::Type::Cmod2: - case Flame::File::Type::Ctoc: - case Flame::File::Type::Unknown: - logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath)); - break; - } - } - - m_mod_id_resolver.reset(); - connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { - m_files_job.reset(); - emitSucceeded(); - }); - connect(m_files_job.get(), &NetJob::failed, [&](QString reason) { - 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::finished, &loop, &QEventLoop::quit); - - setStatus(tr("Downloading mods...")); - m_files_job->start(); + setupDownloadJob(loop); } else { m_mod_id_resolver.reset(); setError("Canceled"); } } else { - // TODO extract to function ? - m_files_job = new NetJob(tr("Mod download"), APPLICATION->network()); - for (const auto& result : m_mod_id_resolver->getResults().files) { - QString filename = result.fileName; - if (!result.required) { - filename += ".disabled"; - } - - auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename); - auto path = FS::PathCombine(m_stagingPath, relpath); - - switch (result.type) { - case Flame::File::Type::Folder: { - logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath)); - // fall-through intentional, we treat these as plain old mods and dump them wherever. - } - case Flame::File::Type::SingleFile: - case Flame::File::Type::Mod: { - if (!result.url.isEmpty()) { - qDebug() << "Will download" << result.url << "to" << path; - auto dl = Net::Download::makeFile(result.url, path); - m_files_job->addNetAction(dl); - } - break; - } - case Flame::File::Type::Modpack: - logWarning(tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(relpath)); - break; - case Flame::File::Type::Cmod2: - case Flame::File::Type::Ctoc: - case Flame::File::Type::Unknown: - logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath)); - break; - } - } - - m_mod_id_resolver.reset(); - connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { - m_files_job.reset(); - emitSucceeded(); - }); - connect(m_files_job.get(), &NetJob::failed, [&](QString reason) { - 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::finished, &loop, &QEventLoop::quit); - - setStatus(tr("Downloading mods...")); - m_files_job->start(); + setupDownloadJob(loop); } } + +void FlameCreationTask::setupDownloadJob(QEventLoop& loop) +{ + m_files_job = new NetJob(tr("Mod download"), APPLICATION->network()); + for (const auto& result : m_mod_id_resolver->getResults().files) { + QString filename = result.fileName; + if (!result.required) { + filename += ".disabled"; + } + + auto relpath = FS::PathCombine("minecraft", result.targetFolder, filename); + auto path = FS::PathCombine(m_stagingPath, relpath); + + switch (result.type) { + case Flame::File::Type::Folder: { + logWarning(tr("This 'Folder' may need extracting: %1").arg(relpath)); + // fall-through intentional, we treat these as plain old mods and dump them wherever. + } + case Flame::File::Type::SingleFile: + case Flame::File::Type::Mod: { + if (!result.url.isEmpty()) { + qDebug() << "Will download" << result.url << "to" << path; + auto dl = Net::Download::makeFile(result.url, path); + m_files_job->addNetAction(dl); + } + break; + } + case Flame::File::Type::Modpack: + logWarning(tr("Nesting modpacks in modpacks is not implemented, nothing was downloaded: %1").arg(relpath)); + break; + case Flame::File::Type::Cmod2: + case Flame::File::Type::Ctoc: + case Flame::File::Type::Unknown: + logWarning(tr("Unrecognized/unhandled PackageType for: %1").arg(relpath)); + break; + } + } + + m_mod_id_resolver.reset(); + connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { + m_files_job.reset(); + emitSucceeded(); + }); + connect(m_files_job.get(), &NetJob::failed, [&](QString reason) { + 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::finished, &loop, &QEventLoop::quit); + + setStatus(tr("Downloading mods...")); + m_files_job->start(); +} diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.h b/launcher/modplatform/flame/FlameInstanceCreationTask.h index efb099d9..be11f805 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.h +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.h @@ -23,6 +23,7 @@ class FlameCreationTask final : public InstanceCreationTask { private slots: void idResolverSucceeded(QEventLoop&); + void setupDownloadJob(QEventLoop&); private: QWidget* m_parent = nullptr; From 795d6f35eed689677186a26ba54ce9e77613859d Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 14 Jul 2022 16:41:49 -0300 Subject: [PATCH 207/273] feat: add curseforge modpack updating Signed-off-by: flow --- .../flame/FlameInstanceCreationTask.cpp | 201 ++++++++++++++++-- .../flame/FlameInstanceCreationTask.h | 7 +- launcher/modplatform/flame/PackManifest.cpp | 24 ++- launcher/modplatform/flame/PackManifest.h | 10 +- 4 files changed, 206 insertions(+), 36 deletions(-) diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 6ec1060c..f0f02bc8 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -1,5 +1,6 @@ #include "FlameInstanceCreationTask.h" +#include "modplatform/flame/FlameAPI.h" #include "modplatform/flame/PackManifest.h" #include "Application.h" @@ -11,10 +12,23 @@ #include "settings/INISettingsObject.h" +#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/BlockedModsDialog.h" +// NOTE: Because of CF's ToS, I don't know if it counts as caching data, so it'll be disabled for now +#define DO_DIFF_UPDATE 0 + +const static QMap forgemap = { { "1.2.5", "3.4.9.171" }, + { "1.4.2", "6.0.1.355" }, + { "1.4.7", "6.6.2.534" }, + { "1.5.2", "7.8.1.737" } }; + +static const FlameAPI api; + bool FlameCreationTask::abort() { + if (m_process_update_file_info_job) + m_process_update_file_info_job->abort(); if (m_files_job) m_files_job->abort(); if (m_mod_id_resolver) @@ -23,43 +37,186 @@ bool FlameCreationTask::abort() return true; } -const static QMap forgemap = { { "1.2.5", "3.4.9.171" }, - { "1.4.2", "6.0.1.355" }, - { "1.4.7", "6.6.2.534" }, - { "1.5.2", "7.8.1.737" } }; - -bool FlameCreationTask::createInstance() +bool FlameCreationTask::updateInstance() { - QEventLoop loop; + auto instance_list = APPLICATION->instances(); + + // FIXME: How to handle situations when there's more than one install already for a given modpack? + auto inst = instance_list->getInstanceByManagedName(originalName()); + + if (!inst) { + inst = instance_list->getInstanceById(originalName()); + + if (!inst) + return false; + } + + QString index_path(FS::PathCombine(m_stagingPath, "manifest.json")); - Flame::Manifest pack; try { - QString configPath = FS::PathCombine(m_stagingPath, "manifest.json"); - Flame::loadManifest(pack, configPath); - QFile::remove(configPath); + Flame::loadManifest(m_pack, index_path); } catch (const JSONValidationError& e) { setError(tr("Could not understand pack manifest:\n") + e.cause()); return false; } - if (!pack.overrides.isEmpty()) { - QString overridePath = FS::PathCombine(m_stagingPath, pack.overrides); + auto version_id = inst->getManagedPackVersionName(); + auto version_str = !version_id.isEmpty() ? tr(" (version %1)").arg(version_id) : ""; + + auto info = CustomMessageBox::selectable(m_parent, tr("Similar modpack was found!"), + tr("One or more of your instances are from this same modpack%1. Do you want to create a " + "separate instance, or update the existing one?") + .arg(version_str), + QMessageBox::Information, QMessageBox::Ok | QMessageBox::Abort); + info->setButtonText(QMessageBox::Ok, tr("Update existing instance")); + info->setButtonText(QMessageBox::Abort, tr("Create new instance")); + + if (info->exec() && info->clickedButton() == info->button(QMessageBox::Abort)) + return false; + + QDir old_inst_dir(inst->instanceRoot()); + + QString old_index_path(FS::PathCombine(old_inst_dir.absolutePath(), "flame", "manifest.json")); + QFileInfo old_index_file(old_index_path); + if (old_index_file.exists()) { + Flame::Manifest old_pack; + Flame::loadManifest(old_pack, old_index_path); + + auto& old_files = old_pack.files; + +#if DO_DIFF_UPDATE + // Remove repeated files, we don't need to download them! + auto& files = m_pack.files; + + // Let's remove all duplicated, identical resources! + auto files_iterator = files.begin(); + while (files_iterator != files.end()) { + auto const& file = files_iterator; + + auto old_file = old_files.find(file.key()); + if (old_file != old_files.end()) { + // We found a match, but is it a different version? + if (old_file->fileId == file->fileId) { + qDebug() << "Removed file at" << file->targetFolder << "with id" << file->fileId << "from list of downloads"; + + old_files.remove(file.key()); + files_iterator = files.erase(files_iterator); + } + } + + files_iterator++; + } +#endif + // Remove remaining old files (we need to do an API request to know which ids are which files...) + QStringList fileIds; + + for (auto& file : old_files) { + fileIds.append(QString::number(file.fileId)); + } + + auto* raw_response = new QByteArray; + auto job = api.getFiles(fileIds, raw_response); + + QEventLoop loop; + + connect(job, &NetJob::succeeded, this, [raw_response, fileIds, old_inst_dir, &old_files] { + // Parse the API response + QJsonParseError parse_error{}; + auto doc = QJsonDocument::fromJson(*raw_response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from Flame files task at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *raw_response; + return; + } + + try { + QJsonArray entries; + if (fileIds.size() == 1) + entries = { Json::requireObject(Json::requireObject(doc), "data") }; + else + entries = Json::requireArray(Json::requireObject(doc), "data"); + + for (auto entry : entries) { + auto entry_obj = Json::requireObject(entry); + + Flame::File file; + // We don't care about blocked mods, we just need local data to delete the file + file.parseFromObject(entry_obj, false); + + auto id = Json::requireInteger(entry_obj, "id"); + old_files.insert(id, file); + } + } catch (Json::JsonException& e) { + qCritical() << e.cause() << e.what(); + } + + // Delete the files + for (auto& file : old_files) { + if (file.fileName.isEmpty() || file.targetFolder.isEmpty()) + continue; + + qDebug() << "Removing" << file.fileName << "at" << file.targetFolder; + QString path(FS::PathCombine(old_inst_dir.absolutePath(), "minecraft", file.targetFolder, file.fileName)); + if (!QFile::remove(path)) + qDebug() << "Failed to remove file at" << path; + } + }); + connect(job, &NetJob::finished, &loop, &QEventLoop::quit); + + m_process_update_file_info_job = job; + job->start(); + + loop.exec(); + + m_process_update_file_info_job = nullptr; + } + // TODO: Currently 'overrides' will always override the stuff on update. How do we preserve unchanged overrides? + + setOverride(true); + qDebug() << "Will override instance!"; + + // We let it go through the createInstance() stage, just with a couple modifications for updating + return false; +} + +bool FlameCreationTask::createInstance() +{ + QEventLoop loop; + + try { + QString index_path(FS::PathCombine(m_stagingPath, "manifest.json")); + if (!m_pack.is_loaded) + Flame::loadManifest(m_pack, index_path); + + // Keep index file in case we need it some other time (like when changing versions) + QString new_index_place(FS::PathCombine(m_stagingPath, "flame", "manifest.json")); + FS::ensureFilePathExists(new_index_place); + QFile::rename(index_path, new_index_place); + + } catch (const JSONValidationError& e) { + setError(tr("Could not understand pack manifest:\n") + e.cause()); + return false; + } + + if (!m_pack.overrides.isEmpty()) { + QString overridePath = FS::PathCombine(m_stagingPath, m_pack.overrides); if (QFile::exists(overridePath)) { QString mcPath = FS::PathCombine(m_stagingPath, "minecraft"); if (!QFile::rename(overridePath, mcPath)) { - setError(tr("Could not rename the overrides folder:\n") + pack.overrides); + setError(tr("Could not rename the overrides folder:\n") + m_pack.overrides); return false; } } else { logWarning( - tr("The specified overrides folder (%1) is missing. Maybe the modpack was already used before?").arg(pack.overrides)); + tr("The specified overrides folder (%1) is missing. Maybe the modpack was already used before?").arg(m_pack.overrides)); } } QString forgeVersion; QString fabricVersion; // TODO: is Quilt relevant here? - for (auto& loader : pack.minecraft.modLoaders) { + for (auto& loader : m_pack.minecraft.modLoaders) { auto id = loader.id; if (id.startsWith("forge-")) { id.remove("forge-"); @@ -77,7 +234,7 @@ bool FlameCreationTask::createInstance() QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); auto instanceSettings = std::make_shared(configPath); MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); - auto mcVersion = pack.minecraft.version; + auto mcVersion = m_pack.minecraft.version; // Hack to correct some 'special sauce'... if (mcVersion.endsWith('.')) { @@ -105,9 +262,9 @@ bool FlameCreationTask::createInstance() if (m_instIcon != "default") { instance.setIconKey(m_instIcon); } else { - if (pack.name.contains("Direwolf20")) { + if (m_pack.name.contains("Direwolf20")) { instance.setIconKey("steve"); - } else if (pack.name.contains("FTB") || pack.name.contains("Feed The Beast")) { + } else if (m_pack.name.contains("FTB") || m_pack.name.contains("Feed The Beast")) { instance.setIconKey("ftb_logo"); } else { instance.setIconKey("flame"); @@ -133,10 +290,8 @@ bool FlameCreationTask::createInstance() instance.setName(m_instName); - m_mod_id_resolver = new Flame::FileResolvingTask(APPLICATION->network(), pack); - connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::succeeded, this, [this, &loop]{ - idResolverSucceeded(loop); - }); + m_mod_id_resolver = new Flame::FileResolvingTask(APPLICATION->network(), m_pack); + connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::succeeded, this, [this, &loop] { idResolverSucceeded(loop); }); connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::failed, [&](QString reason) { m_mod_id_resolver.reset(); setError(tr("Unable to resolve mod IDs:\n") + reason); diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.h b/launcher/modplatform/flame/FlameInstanceCreationTask.h index be11f805..99822d93 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.h +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.h @@ -19,6 +19,7 @@ class FlameCreationTask final : public InstanceCreationTask { bool abort() override; + bool updateInstance() override; bool createInstance() override; private slots: @@ -29,5 +30,9 @@ class FlameCreationTask final : public InstanceCreationTask { QWidget* m_parent = nullptr; shared_qobject_ptr m_mod_id_resolver; - NetJob::Ptr m_files_job; + Flame::Manifest m_pack; + + // Handle to allow aborting + NetJob* m_process_update_file_info_job = nullptr; + NetJob::Ptr m_files_job = nullptr; }; diff --git a/launcher/modplatform/flame/PackManifest.cpp b/launcher/modplatform/flame/PackManifest.cpp index 81395fcd..22008297 100644 --- a/launcher/modplatform/flame/PackManifest.cpp +++ b/launcher/modplatform/flame/PackManifest.cpp @@ -29,21 +29,29 @@ static void loadMinecraftV1(Flame::Minecraft& m, QJsonObject& minecraft) } } -static void loadManifestV1(Flame::Manifest& m, QJsonObject& manifest) +static void loadManifestV1(Flame::Manifest& pack, QJsonObject& manifest) { auto mc = Json::requireObject(manifest, "minecraft"); - loadMinecraftV1(m.minecraft, mc); - m.name = Json::ensureString(manifest, QString("name"), "Unnamed"); - m.version = Json::ensureString(manifest, QString("version"), QString()); - m.author = Json::ensureString(manifest, QString("author"), "Anonymous"); + + loadMinecraftV1(pack.minecraft, mc); + + pack.name = Json::ensureString(manifest, QString("name"), "Unnamed"); + pack.version = Json::ensureString(manifest, QString("version"), QString()); + pack.author = Json::ensureString(manifest, QString("author"), "Anonymous"); + auto arr = Json::ensureArray(manifest, "files", QJsonArray()); - for (QJsonValueRef item : arr) { + for (auto item : arr) { auto obj = Json::requireObject(item); + Flame::File file; loadFileV1(file, obj); - m.files.insert(file.fileId,file); + + pack.files.insert(file.fileId,file); } - m.overrides = Json::ensureString(manifest, "overrides", "overrides"); + + pack.overrides = Json::ensureString(manifest, "overrides", "overrides"); + + pack.is_loaded = true; } void Flame::loadManifest(Flame::Manifest& m, const QString& filepath) diff --git a/launcher/modplatform/flame/PackManifest.h b/launcher/modplatform/flame/PackManifest.h index a69e1321..0b7461d8 100644 --- a/launcher/modplatform/flame/PackManifest.h +++ b/launcher/modplatform/flame/PackManifest.h @@ -35,11 +35,11 @@ #pragma once -#include -#include -#include -#include #include +#include +#include +#include +#include namespace Flame { @@ -97,6 +97,8 @@ struct Manifest //File id -> File QMap files; QString overrides; + + bool is_loaded = false; }; void loadManifest(Flame::Manifest & m, const QString &filepath); From eed73c90785ec977ee975d403270f9138aa6960c Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 8 Jul 2022 19:44:44 -0300 Subject: [PATCH 208/273] refactor: clean up InstanceImportTask a bit Also removes a divide by two in the download progress (why was it there???) Signed-off-by: flow --- launcher/InstanceImportTask.cpp | 44 ++++++++++++++------------------- 1 file changed, 18 insertions(+), 26 deletions(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 72c2496f..da57ddeb 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -35,34 +35,26 @@ */ #include "InstanceImportTask.h" -#include + #include "Application.h" -#include "BaseInstance.h" #include "FileSystem.h" #include "MMCZip.h" #include "NullInstance.h" + #include "icons/IconList.h" #include "icons/IconUtils.h" -#include "settings/INISettingsObject.h" -// FIXME: this does not belong here, it's Minecraft/Flame specific -#include -#include "Json.h" -#include "minecraft/MinecraftInstance.h" -#include "minecraft/PackProfile.h" #include "modplatform/technic/TechnicPackProcessor.h" #include "modplatform/modrinth/ModrinthInstanceCreationTask.h" #include "modplatform/flame/FlameInstanceCreationTask.h" -#include "Application.h" -#include "icons/IconList.h" -#include "net/ChecksumValidator.h" - -#include "ui/dialogs/CustomMessageBox.h" -#include "ui/dialogs/BlockedModsDialog.h" +#include "settings/INISettingsObject.h" +#include #include +#include + InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent) { m_sourceUrl = sourceUrl; @@ -80,26 +72,26 @@ bool InstanceImportTask::abort() void InstanceImportTask::executeTask() { - if (m_sourceUrl.isLocalFile()) - { + if (m_sourceUrl.isLocalFile()) { m_archivePath = m_sourceUrl.toLocalFile(); processZipPack(); - } - else - { + } else { setStatus(tr("Downloading modpack:\n%1").arg(m_sourceUrl.toString())); m_downloadRequired = true; - const QString path = m_sourceUrl.host() + '/' + m_sourceUrl.path(); + const QString path(m_sourceUrl.host() + '/' + m_sourceUrl.path()); + auto entry = APPLICATION->metacache()->resolveEntry("general", path); entry->setStale(true); + m_archivePath = entry->getFullPath(); + m_filesNetJob = new NetJob(tr("Modpack download"), APPLICATION->network()); m_filesNetJob->addNetAction(Net::Download::makeCached(m_sourceUrl, entry)); - m_archivePath = entry->getFullPath(); - auto job = m_filesNetJob.get(); - connect(job, &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded); - connect(job, &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged); - connect(job, &NetJob::failed, this, &InstanceImportTask::downloadFailed); + + connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded); + connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged); + connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed); + m_filesNetJob->start(); } } @@ -118,7 +110,7 @@ void InstanceImportTask::downloadFailed(QString reason) void InstanceImportTask::downloadProgressChanged(qint64 current, qint64 total) { - setProgress(current / 2, total); + setProgress(current, total); } void InstanceImportTask::processZipPack() From 6131346e2f80c5ce4377fcc608be4f3929f43f91 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 14 Jul 2022 16:13:23 -0300 Subject: [PATCH 209/273] refactor: change the way instance names are handled While working on pack updating, instance naming always gets in the way, since we need both way of respecting the user's name choice, and a standarized way of getting the original pack name / version. This tries to circunvent such problems by abstracting away the naming schema into it's own struct, holding both the original name / version, and the user-defined name, so that everyone can be happy and world peace can be achieved! (at least that's what i'd hope :c). Signed-off-by: flow --- launcher/InstanceCopyTask.cpp | 2 +- launcher/InstanceImportTask.cpp | 8 +-- launcher/InstanceList.cpp | 32 ++++----- launcher/InstanceList.h | 6 +- launcher/InstanceTask.cpp | 29 +++++++- launcher/InstanceTask.h | 70 +++++++++---------- .../minecraft/VanillaInstanceCreationTask.cpp | 4 +- .../atlauncher/ATLPackInstallTask.cpp | 2 +- .../flame/FlameInstanceCreationTask.cpp | 4 +- .../legacy_ftb/PackInstallTask.cpp | 2 +- .../modpacksch/FTBPackInstallTask.cpp | 2 +- .../modrinth/ModrinthInstanceCreationTask.cpp | 10 ++- .../technic/SingleZipPackInstallTask.cpp | 2 +- .../technic/SolderPackInstallTask.cpp | 2 +- launcher/ui/dialogs/NewInstanceDialog.cpp | 31 ++++++-- launcher/ui/dialogs/NewInstanceDialog.h | 6 +- .../pages/modplatform/atlauncher/AtlPage.cpp | 2 +- launcher/ui/pages/modplatform/ftb/FtbPage.cpp | 2 +- .../ui/pages/modplatform/legacy_ftb/Page.cpp | 2 +- .../modplatform/modrinth/ModrinthPage.cpp | 2 +- .../pages/modplatform/technic/TechnicPage.cpp | 4 +- 21 files changed, 131 insertions(+), 93 deletions(-) diff --git a/launcher/InstanceCopyTask.cpp b/launcher/InstanceCopyTask.cpp index c2bfe839..b1e33884 100644 --- a/launcher/InstanceCopyTask.cpp +++ b/launcher/InstanceCopyTask.cpp @@ -44,7 +44,7 @@ void InstanceCopyTask::copyFinished() auto instanceSettings = std::make_shared(FS::PathCombine(m_stagingPath, "instance.cfg")); InstancePtr inst(new NullInstance(m_globalSettings, instanceSettings, m_stagingPath)); - inst->setName(m_instName); + inst->setName(name()); inst->setIconKey(m_instIcon); if(!m_keepPlaytime) { inst->resetTimePlayed(); diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index da57ddeb..09e65064 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -255,7 +255,7 @@ void InstanceImportTask::processFlame() { auto* inst_creation_task = new FlameCreationTask(m_stagingPath, m_globalSettings, m_parent); - inst_creation_task->setName(m_instName); + inst_creation_task->setName(*this); inst_creation_task->setIcon(m_instIcon); inst_creation_task->setGroup(m_instGroup); @@ -278,7 +278,7 @@ void InstanceImportTask::processTechnic() shared_qobject_ptr packProcessor = new Technic::TechnicPackProcessor(); connect(packProcessor.get(), &Technic::TechnicPackProcessor::succeeded, this, &InstanceImportTask::emitSucceeded); connect(packProcessor.get(), &Technic::TechnicPackProcessor::failed, this, &InstanceImportTask::emitFailed); - packProcessor->run(m_globalSettings, m_instName, m_instIcon, m_stagingPath); + packProcessor->run(m_globalSettings, name(), m_instIcon, m_stagingPath); } void InstanceImportTask::processMultiMC() @@ -292,7 +292,7 @@ void InstanceImportTask::processMultiMC() instance.resetTimePlayed(); // set a new nice name - instance.setName(m_instName); + instance.setName(name()); // if the icon was specified by user, use that. otherwise pull icon from the pack if (m_instIcon != "default") { @@ -317,7 +317,7 @@ void InstanceImportTask::processModrinth() { auto* inst_creation_task = new ModrinthCreationTask(m_stagingPath, m_globalSettings, m_parent, m_sourceUrl.toString()); - inst_creation_task->setName(m_instName); + inst_creation_task->setName(*this); inst_creation_task->setIcon(m_instIcon); inst_creation_task->setGroup(m_instGroup); diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 698aa24e..0e59150e 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -778,19 +778,14 @@ class InstanceStaging : public Task { const unsigned minBackoff = 1; const unsigned maxBackoff = 16; public: - InstanceStaging(InstanceList* parent, InstanceTask* child, const QString& stagingPath, const QString& instanceName, const QString& groupName) - : backoff(minBackoff, maxBackoff) + InstanceStaging(InstanceList* parent, InstanceTask* child, QString stagingPath, InstanceName const& instanceName, QString groupName) + : m_parent(parent), backoff(minBackoff, maxBackoff), m_stagingPath(std::move(stagingPath)), m_instance_name(std::move(instanceName)), m_groupName(std::move(groupName)) { - m_parent = parent; m_child.reset(child); connect(child, &Task::succeeded, this, &InstanceStaging::childSucceded); connect(child, &Task::failed, this, &InstanceStaging::childFailed); connect(child, &Task::status, this, &InstanceStaging::setStatus); connect(child, &Task::progress, this, &InstanceStaging::setProgress); - m_instanceName = instanceName; - m_groupName = groupName; - m_stagingPath = stagingPath; - m_backoffTimer.setSingleShot(true); connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded); } @@ -820,7 +815,7 @@ class InstanceStaging : public Task { void childSucceded() { unsigned sleepTime = backoff(); - if (m_parent->commitStagedInstance(m_stagingPath, m_instanceName, m_groupName, m_child->shouldOverride())) + if (m_parent->commitStagedInstance(m_stagingPath, m_instance_name, m_groupName, m_child->shouldOverride())) { emitSucceeded(); return; @@ -830,7 +825,7 @@ class InstanceStaging : public Task { emitFailed(tr("Failed to commit instance, even after multiple retries. It is being blocked by something.")); return; } - qDebug() << "Failed to commit instance" << m_instanceName << "Initiating backoff:" << sleepTime; + qDebug() << "Failed to commit instance" << m_instance_name.name() << "Initiating backoff:" << sleepTime; m_backoffTimer.start(sleepTime * 500); } void childFailed(const QString& reason) @@ -840,6 +835,7 @@ class InstanceStaging : public Task { } private: + InstanceList * m_parent; /* * WHY: the whole reason why this uses an exponential backoff retry scheme is antivirus on Windows. * Basically, it starts messing things up while the launcher is extracting/creating instances @@ -847,9 +843,8 @@ class InstanceStaging : public Task { */ ExponentialSeries backoff; QString m_stagingPath; - InstanceList * m_parent; unique_qobject_ptr m_child; - QString m_instanceName; + InstanceName m_instance_name; QString m_groupName; QTimer m_backoffTimer; }; @@ -859,7 +854,7 @@ Task* InstanceList::wrapInstanceTask(InstanceTask* task) auto stagingPath = getStagedInstancePath(); task->setStagingPath(stagingPath); task->setParentSettings(m_globalSettings); - return new InstanceStaging(this, task, stagingPath, task->name(), task->group()); + return new InstanceStaging(this, task, stagingPath, *task, task->group()); } QString InstanceList::getStagedInstancePath() @@ -879,22 +874,23 @@ QString InstanceList::getStagedInstancePath() return path; } -bool InstanceList::commitStagedInstance(const QString& path, const QString& instanceName, const QString& groupName, bool should_override) +bool InstanceList::commitStagedInstance(QString path, InstanceName const& instanceName, QString groupName, bool should_override) { QDir dir; QString instID; InstancePtr inst; - QString raw_inst_name = instanceName.section(' ', 0, -2); if (should_override) { // This is to avoid problems when the instance folder gets manually renamed - if ((inst = getInstanceByManagedName(raw_inst_name))) { + if ((inst = getInstanceByManagedName(instanceName.originalName()))) { + instID = QFileInfo(inst->instanceRoot()).fileName(); + } else if ((inst = getInstanceByManagedName(instanceName.modifiedName()))) { instID = QFileInfo(inst->instanceRoot()).fileName(); } else { - instID = FS::RemoveInvalidFilenameChars(raw_inst_name, '-'); + instID = FS::RemoveInvalidFilenameChars(instanceName.modifiedName(), '-'); } } else { - instID = FS::DirNameFromString(raw_inst_name, m_instDir); + instID = FS::DirNameFromString(instanceName.modifiedName(), m_instDir); } { @@ -910,7 +906,7 @@ bool InstanceList::commitStagedInstance(const QString& path, const QString& inst if (!inst) inst = getInstanceById(instID); if (inst) - inst->setName(instanceName); + inst->setName(instanceName.name()); } else { if (!dir.rename(path, destination)) { qWarning() << "Failed to move" << path << "to" << destination; diff --git a/launcher/InstanceList.h b/launcher/InstanceList.h index 6b4dcfa4..df11b55a 100644 --- a/launcher/InstanceList.h +++ b/launcher/InstanceList.h @@ -24,10 +24,10 @@ #include "BaseInstance.h" -#include "QObjectPtr.h" - class QFileSystemWatcher; class InstanceTask; +struct InstanceName; + using InstanceId = QString; using GroupId = QString; using InstanceLocator = std::pair; @@ -133,7 +133,7 @@ public: * should_override is used when another similar instance already exists, and we want to override it * - for instance, when updating it. */ - bool commitStagedInstance(const QString & keyPath, const QString& instanceName, const QString & groupName, bool should_override); + bool commitStagedInstance(QString keyPath, const InstanceName& instanceName, QString groupName, bool should_override); /** * Destroy a previously created staging area given by @keyPath - used when creation fails. diff --git a/launcher/InstanceTask.cpp b/launcher/InstanceTask.cpp index dd132877..43a0b947 100644 --- a/launcher/InstanceTask.cpp +++ b/launcher/InstanceTask.cpp @@ -1,9 +1,34 @@ #include "InstanceTask.h" -InstanceTask::InstanceTask() +QString InstanceName::name() const { + if (!m_modified_name.isEmpty()) + return modifiedName(); + return QString("%1 %2").arg(m_original_name, m_original_version); } -InstanceTask::~InstanceTask() +QString InstanceName::originalName() const { + return m_original_name; } + +QString InstanceName::modifiedName() const +{ + if (!m_modified_name.isEmpty()) + return m_modified_name; + return m_original_name; +} + +QString InstanceName::version() const +{ + return m_original_version; +} + +void InstanceName::setName(InstanceName& other) +{ + m_original_name = other.m_original_name; + m_original_version = other.m_original_version; + m_modified_name = other.m_modified_name; +} + +InstanceTask::InstanceTask() : Task(), InstanceName() {} diff --git a/launcher/InstanceTask.h b/launcher/InstanceTask.h index 02810a52..5d67a2f0 100644 --- a/launcher/InstanceTask.h +++ b/launcher/InstanceTask.h @@ -1,56 +1,50 @@ #pragma once -#include "tasks/Task.h" #include "settings/SettingsObject.h" +#include "tasks/Task.h" -class InstanceTask : public Task -{ +struct InstanceName { + public: + InstanceName() = default; + InstanceName(QString name, QString version) : m_original_name(std::move(name)), m_original_version(std::move(version)) {} + + [[nodiscard]] QString modifiedName() const; + [[nodiscard]] QString originalName() const; + [[nodiscard]] QString name() const; + [[nodiscard]] QString version() const; + + void setName(QString name) { m_modified_name = name; } + void setName(InstanceName& other); + + protected: + QString m_original_name; + QString m_original_version; + + QString m_modified_name; +}; + +class InstanceTask : public Task, public InstanceName { Q_OBJECT -public: - explicit InstanceTask(); - virtual ~InstanceTask(); + public: + InstanceTask(); + ~InstanceTask() override = default; - void setParentSettings(SettingsObjectPtr settings) - { - m_globalSettings = settings; - } + void setParentSettings(SettingsObjectPtr settings) { m_globalSettings = settings; } - void setStagingPath(const QString &stagingPath) - { - m_stagingPath = stagingPath; - } + void setStagingPath(const QString& stagingPath) { m_stagingPath = stagingPath; } - void setName(const QString &name) - { - m_instName = name; - } - QString name() const - { - return m_instName; - } + void setIcon(const QString& icon) { m_instIcon = icon; } - void setIcon(const QString &icon) - { - m_instIcon = icon; - } - - void setGroup(const QString &group) - { - m_instGroup = group; - } - QString group() const - { - return m_instGroup; - } + void setGroup(const QString& group) { m_instGroup = group; } + QString group() const { return m_instGroup; } bool shouldOverride() const { return m_override_existing; } -protected: + protected: void setOverride(bool override) { m_override_existing = override; } -protected: /* data */ + protected: /* data */ SettingsObjectPtr m_globalSettings; - QString m_instName; QString m_instIcon; QString m_instGroup; QString m_stagingPath; diff --git a/launcher/minecraft/VanillaInstanceCreationTask.cpp b/launcher/minecraft/VanillaInstanceCreationTask.cpp index 2d1593b6..fb11379c 100644 --- a/launcher/minecraft/VanillaInstanceCreationTask.cpp +++ b/launcher/minecraft/VanillaInstanceCreationTask.cpp @@ -1,9 +1,9 @@ #include "VanillaInstanceCreationTask.h" #include "FileSystem.h" -#include "settings/INISettingsObject.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" +#include "settings/INISettingsObject.h" VanillaCreationTask::VanillaCreationTask(BaseVersionPtr version, QString loader, BaseVersionPtr loader_version) : InstanceCreationTask(), m_version(version), m_using_loader(true), m_loader(loader), m_loader_version(loader_version) @@ -23,7 +23,7 @@ bool VanillaCreationTask::createInstance() if(m_using_loader) components->setComponentVersion(m_loader, m_loader_version->descriptor()); - inst.setName(m_instName); + inst.setName(name()); inst.setIconKey(m_instIcon); } instance_settings->resumeSave(); diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 70a35395..13cab256 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -1005,7 +1005,7 @@ void PackInstallTask::install() components->saveNow(); - instance.setName(m_instName); + instance.setName(name()); instance.setIconKey(m_instIcon); instance.setManagedPack("atlauncher", m_pack_safe_name, m_pack_name, m_version_name, m_version_name); instanceSettings->resumeSave(); diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index f0f02bc8..202caa28 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -5,6 +5,7 @@ #include "Application.h" #include "FileSystem.h" +#include "InstanceList.h" #include "Json.h" #include "minecraft/MinecraftInstance.h" @@ -288,7 +289,8 @@ bool FlameCreationTask::createInstance() FS::deletePath(jarmodsPath); } - instance.setName(m_instName); + instance.setManagedPack("flame", {}, m_pack.name, {}, m_pack.version); + instance.setName(name()); m_mod_id_resolver = new Flame::FileResolvingTask(APPLICATION->network(), m_pack); connect(m_mod_id_resolver.get(), &Flame::FileResolvingTask::succeeded, this, [this, &loop] { idResolverSucceeded(loop); }); diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp index 83e14969..e2e61a7c 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp @@ -228,7 +228,7 @@ void PackInstallTask::install() progress(4, 4); - instance.setName(m_instName); + instance.setName(name()); if(m_instIcon == "default") { m_instIcon = "ftb_logo"; diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp index 3c15667c..f17a311a 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp @@ -335,7 +335,7 @@ void PackInstallTask::install() components->saveNow(); - instance.setName(m_instName); + instance.setName(name()); instance.setIconKey(m_instIcon); instance.setManagedPack("modpacksch", QString::number(m_pack.id), m_pack.name, QString::number(m_version.id), m_version.name); instanceSettings->resumeSave(); diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index efb8c99b..06ac29c3 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -8,8 +8,6 @@ #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" -#include "modplatform/ModIndex.h" - #include "net/ChecksumValidator.h" #include "settings/INISettingsObject.h" @@ -30,11 +28,10 @@ bool ModrinthCreationTask::updateInstance() auto instance_list = APPLICATION->instances(); // FIXME: How to handle situations when there's more than one install already for a given modpack? - // Based on the way we create the instance name (name + " " + version). Is there a better way? - auto inst = instance_list->getInstanceByManagedName(m_instName.section(' ', 0, -2)); + auto inst = instance_list->getInstanceByManagedName(originalName()); if (!inst) { - inst = instance_list->getInstanceById(m_instName); + inst = instance_list->getInstanceById(originalName()); if (!inst) return false; @@ -163,8 +160,9 @@ bool ModrinthCreationTask::createInstance() } else { instance.setIconKey("modrinth"); } - instance.setName(m_instName); + instance.setManagedPack("modrinth", getManagedPackID(), m_managed_name, m_managed_id, {}); + instance.setName(name()); instance.saveNow(); m_files_job = new NetJob(tr("Mod download"), APPLICATION->network()); diff --git a/launcher/modplatform/technic/SingleZipPackInstallTask.cpp b/launcher/modplatform/technic/SingleZipPackInstallTask.cpp index 9093b245..6438d9ef 100644 --- a/launcher/modplatform/technic/SingleZipPackInstallTask.cpp +++ b/launcher/modplatform/technic/SingleZipPackInstallTask.cpp @@ -133,7 +133,7 @@ void Technic::SingleZipPackInstallTask::extractFinished() shared_qobject_ptr packProcessor = new Technic::TechnicPackProcessor(); connect(packProcessor.get(), &Technic::TechnicPackProcessor::succeeded, this, &Technic::SingleZipPackInstallTask::emitSucceeded); connect(packProcessor.get(), &Technic::TechnicPackProcessor::failed, this, &Technic::SingleZipPackInstallTask::emitFailed); - packProcessor->run(m_globalSettings, m_instName, m_instIcon, m_stagingPath, m_minecraftVersion); + packProcessor->run(m_globalSettings, name(), m_instIcon, m_stagingPath, m_minecraftVersion); } void Technic::SingleZipPackInstallTask::extractAborted() diff --git a/launcher/modplatform/technic/SolderPackInstallTask.cpp b/launcher/modplatform/technic/SolderPackInstallTask.cpp index 89dbf4ca..65b6ca51 100644 --- a/launcher/modplatform/technic/SolderPackInstallTask.cpp +++ b/launcher/modplatform/technic/SolderPackInstallTask.cpp @@ -214,7 +214,7 @@ void Technic::SolderPackInstallTask::extractFinished() shared_qobject_ptr packProcessor = new Technic::TechnicPackProcessor(); connect(packProcessor.get(), &Technic::TechnicPackProcessor::succeeded, this, &Technic::SolderPackInstallTask::emitSucceeded); connect(packProcessor.get(), &Technic::TechnicPackProcessor::failed, this, &Technic::SolderPackInstallTask::emitFailed); - packProcessor->run(m_globalSettings, m_instName, m_instIcon, m_stagingPath, m_minecraftVersion, true); + packProcessor->run(m_globalSettings, name(), m_instIcon, m_stagingPath, m_minecraftVersion, true); } void Technic::SolderPackInstallTask::extractAborted() diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp index 675f8b15..04fecbde 100644 --- a/launcher/ui/dialogs/NewInstanceDialog.cpp +++ b/launcher/ui/dialogs/NewInstanceDialog.cpp @@ -177,13 +177,30 @@ NewInstanceDialog::~NewInstanceDialog() delete ui; } -void NewInstanceDialog::setSuggestedPack(const QString& name, InstanceTask* task) +void NewInstanceDialog::setSuggestedPack(QString name, InstanceTask* task) { creationTask.reset(task); - ui->instNameTextBox->setPlaceholderText(name); - if(!task) - { + ui->instNameTextBox->setPlaceholderText(name); + importVersion.clear(); + + if (!task) { + ui->iconButton->setIcon(APPLICATION->icons()->getIcon("default")); + importIcon = false; + } + + auto allowOK = task && !instName().isEmpty(); + m_buttons->button(QDialogButtonBox::Ok)->setEnabled(allowOK); +} + +void NewInstanceDialog::setSuggestedPack(QString name, QString version, InstanceTask* task) +{ + creationTask.reset(task); + + ui->instNameTextBox->setPlaceholderText(name); + importVersion = version; + + if (!task) { ui->iconButton->setIcon(APPLICATION->icons()->getIcon("default")); importIcon = false; } @@ -214,7 +231,11 @@ InstanceTask * NewInstanceDialog::extractTask() { InstanceTask * extracted = creationTask.get(); creationTask.release(); - extracted->setName(instName()); + + InstanceName inst_name(ui->instNameTextBox->placeholderText().trimmed(), importVersion); + inst_name.setName(ui->instNameTextBox->text().trimmed()); + extracted->setName(inst_name); + extracted->setGroup(instGroup()); extracted->setIcon(iconKey()); return extracted; diff --git a/launcher/ui/dialogs/NewInstanceDialog.h b/launcher/ui/dialogs/NewInstanceDialog.h index a3c8cd1c..185ab9e6 100644 --- a/launcher/ui/dialogs/NewInstanceDialog.h +++ b/launcher/ui/dialogs/NewInstanceDialog.h @@ -37,7 +37,6 @@ #include -#include "BaseVersion.h" #include "ui/pages/BasePageProvider.h" #include "InstanceTask.h" @@ -61,7 +60,8 @@ public: void updateDialogState(); - void setSuggestedPack(const QString & name = QString(), InstanceTask * task = nullptr); + void setSuggestedPack(QString name = QString(), InstanceTask * task = nullptr); + void setSuggestedPack(QString name, QString version, InstanceTask * task = nullptr); void setSuggestedIconFromFile(const QString &path, const QString &name); void setSuggestedIcon(const QString &key); @@ -95,5 +95,7 @@ private: QString importIconPath; QString importIconName; + QString importVersion; + void importIconNow(); }; diff --git a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp index 7901b90b..87544445 100644 --- a/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp +++ b/launcher/ui/pages/modplatform/atlauncher/AtlPage.cpp @@ -117,7 +117,7 @@ void AtlPage::suggestCurrent() } auto uiSupport = new AtlUserInteractionSupportImpl(this); - dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ATLauncher::PackInstallTask(uiSupport, selected.name, selectedVersion)); + dialog->setSuggestedPack(selected.name, selectedVersion, new ATLauncher::PackInstallTask(uiSupport, selected.name, selectedVersion)); auto editedLogoName = selected.safeName; auto url = QString(BuildConfig.ATL_DOWNLOAD_SERVER_URL + "launcher/images/%1.png").arg(selected.safeName.toLower()); diff --git a/launcher/ui/pages/modplatform/ftb/FtbPage.cpp b/launcher/ui/pages/modplatform/ftb/FtbPage.cpp index 504d7f7b..8975d74e 100644 --- a/launcher/ui/pages/modplatform/ftb/FtbPage.cpp +++ b/launcher/ui/pages/modplatform/ftb/FtbPage.cpp @@ -127,7 +127,7 @@ void FtbPage::suggestCurrent() return; } - dialog->setSuggestedPack(selected.name + " " + selectedVersion, new ModpacksCH::PackInstallTask(selected, selectedVersion, this)); + dialog->setSuggestedPack(selected.name, selectedVersion, new ModpacksCH::PackInstallTask(selected, selectedVersion, this)); for(auto art : selected.art) { if(art.type == "square") { QString editedLogoName; diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp index 6ffbd312..0208b36b 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp @@ -176,7 +176,7 @@ void Page::suggestCurrent() return; } - dialog->setSuggestedPack(selected.name + " " + selectedVersion, new PackInstallTask(APPLICATION->network(), selected, selectedVersion)); + dialog->setSuggestedPack(selected.name, selectedVersion, new PackInstallTask(APPLICATION->network(), selected, selectedVersion)); QString editedLogoName; if(selected.logo.toLower().startsWith("ftb")) { diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index df29c0c3..16fec82c 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -294,7 +294,7 @@ void ModrinthPage::suggestCurrent() for (auto& ver : current.versions) { if (ver.id == selectedVersion) { - dialog->setSuggestedPack(current.name + " " + ver.version, new InstanceImportTask(ver.download_url, this)); + dialog->setSuggestedPack(current.name, ver.version, new InstanceImportTask(ver.download_url, this)); auto iconName = current.iconName; m_model->getLogo(iconName, current.iconUrl.toString(), [this, iconName](QString logo) { dialog->setSuggestedIconFromFile(logo, iconName); }); diff --git a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp index b8c1e00a..b15af244 100644 --- a/launcher/ui/pages/modplatform/technic/TechnicPage.cpp +++ b/launcher/ui/pages/modplatform/technic/TechnicPage.cpp @@ -271,11 +271,11 @@ void TechnicPage::selectVersion() { if (!current.isSolder) { - dialog->setSuggestedPack(current.name + " " + selectedVersion, new Technic::SingleZipPackInstallTask(current.url, current.minecraftVersion)); + dialog->setSuggestedPack(current.name, selectedVersion, new Technic::SingleZipPackInstallTask(current.url, current.minecraftVersion)); } else { - dialog->setSuggestedPack(current.name + " " + selectedVersion, new Technic::SolderPackInstallTask(APPLICATION->network(), current.url, current.slug, selectedVersion, current.minecraftVersion)); + dialog->setSuggestedPack(current.name, selectedVersion, new Technic::SolderPackInstallTask(APPLICATION->network(), current.url, current.slug, selectedVersion, current.minecraftVersion)); } } From 7024acac06885f9d9e24125afef04231aebdd113 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 28 Jul 2022 22:34:24 -0300 Subject: [PATCH 210/273] feat: add override helper functions These help us keep track of relevant metadata information about overrides, so that we know what they are when we update a pack. Signed-off-by: flow --- launcher/CMakeLists.txt | 2 + .../modplatform/helpers/OverrideUtils.cpp | 59 +++++++++++++++++++ launcher/modplatform/helpers/OverrideUtils.h | 20 +++++++ 3 files changed, 81 insertions(+) create mode 100644 launcher/modplatform/helpers/OverrideUtils.cpp create mode 100644 launcher/modplatform/helpers/OverrideUtils.h diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 7bd92fac..2ff700ad 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -461,6 +461,8 @@ set(API_SOURCES modplatform/helpers/NetworkModAPI.cpp modplatform/helpers/HashUtils.h modplatform/helpers/HashUtils.cpp + modplatform/helpers/OverrideUtils.h + modplatform/helpers/OverrideUtils.cpp ) set(FTB_SOURCES diff --git a/launcher/modplatform/helpers/OverrideUtils.cpp b/launcher/modplatform/helpers/OverrideUtils.cpp new file mode 100644 index 00000000..3496a286 --- /dev/null +++ b/launcher/modplatform/helpers/OverrideUtils.cpp @@ -0,0 +1,59 @@ +#include "OverrideUtils.h" + +#include + +#include "FileSystem.h" + +namespace Override { + +void createOverrides(QString name, QString parent_folder, QString override_path) +{ + QString file_path(FS::PathCombine(parent_folder, name + ".txt")); + if (QFile::exists(file_path)) + QFile::remove(file_path); + + FS::ensureFilePathExists(file_path); + + QFile file(file_path); + file.open(QFile::WriteOnly); + + QDirIterator override_iterator(override_path, QDirIterator::Subdirectories); + while (override_iterator.hasNext()) { + auto override_file_path = override_iterator.next(); + QFileInfo info(override_file_path); + if (info.isFile()) { + // Absolute path with temp directory -> relative path + override_file_path = override_file_path.split(name).last().remove(0, 1); + + file.write(override_file_path.toUtf8()); + file.write("\n"); + } + } + + file.close(); +} + +QStringList readOverrides(QString name, QString parent_folder) +{ + QString file_path(FS::PathCombine(parent_folder, name + ".txt")); + + QFile file(file_path); + if (!file.exists()) + return {}; + + QStringList previous_overrides; + + file.open(QFile::ReadOnly); + + QString entry; + do { + entry = file.readLine(); + previous_overrides.append(entry.trimmed()); + } while (!entry.isEmpty()); + + file.close(); + + return previous_overrides; +} + +} // namespace Override diff --git a/launcher/modplatform/helpers/OverrideUtils.h b/launcher/modplatform/helpers/OverrideUtils.h new file mode 100644 index 00000000..73f059d6 --- /dev/null +++ b/launcher/modplatform/helpers/OverrideUtils.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +namespace Override { + +/** This creates a file in `parent_folder` that holds information about which + * overrides are in `override_path`. + * + * If there's already an existing such file, it will be ovewritten. + */ +void createOverrides(QString name, QString parent_folder, QString override_path); + +/** This reads an existing overrides archive, returning a list of overrides. + * + * If there's no such file in `parent_folder`, it will return an empty list. + */ +QStringList readOverrides(QString name, QString parent_folder); + +} // namespace Override From 3a9d58e31c70159f574b5cc24a104f81c9d981ed Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 28 Jul 2022 22:35:40 -0300 Subject: [PATCH 211/273] feat: add override handling in modrinth pack update Signed-off-by: flow --- .../modrinth/ModrinthInstanceCreationTask.cpp | 42 +++++++++++++++---- 1 file changed, 35 insertions(+), 7 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index 06ac29c3..212b3447 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -8,6 +8,8 @@ #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" +#include "modplatform/helpers/OverrideUtils.h" + #include "net/ChecksumValidator.h" #include "settings/INISettingsObject.h" @@ -58,7 +60,9 @@ bool ModrinthCreationTask::updateInstance() // Remove repeated files, we don't need to download them! QDir old_inst_dir(inst->instanceRoot()); - QString old_index_path(FS::PathCombine(old_inst_dir.absolutePath(), "mrpack", "modrinth.index.json")); + QString old_index_folder(FS::PathCombine(old_inst_dir.absolutePath(), "mrpack")); + + QString old_index_path(FS::PathCombine(old_index_folder, "modrinth.index.json")); QFileInfo old_index_file(old_index_path); if (old_index_file.exists()) { std::vector old_files; @@ -66,7 +70,7 @@ bool ModrinthCreationTask::updateInstance() // Let's remove all duplicated, identical resources! auto files_iterator = m_files.begin(); -begin: + begin: while (files_iterator != m_files.end()) { auto const& file = *files_iterator; @@ -78,7 +82,7 @@ begin: qDebug() << "Removed file at" << file.path << "from list of downloads"; files_iterator = m_files.erase(files_iterator); old_files_iterator = old_files.erase(old_files_iterator); - goto begin; // Sorry :c + goto begin; // Sorry :c } old_files_iterator++; @@ -87,18 +91,32 @@ begin: files_iterator++; } + QDir old_minecraft_dir(inst->gameRoot()); // Some files were removed from the old version, and some will be downloaded in an updated version, // so we're fine removing them! if (!old_files.empty()) { - QDir old_minecraft_dir(inst->gameRoot()); for (auto const& file : old_files) { - qWarning() << "Removing" << file.path; + qDebug() << "Removing" << file.path; old_minecraft_dir.remove(file.path); } } + + // We will remove all the previous overrides, to prevent duplicate files! + // TODO: Currently 'overrides' will always override the stuff on update. How do we preserve unchanged overrides? + // FIXME: We may want to do something about disabled mods. + auto old_overrides = Override::readOverrides("overrides", old_index_folder); + for (auto entry : old_overrides) { + qDebug() << "Removing" << entry; + old_minecraft_dir.remove(entry); + } + + auto old_client_overrides = Override::readOverrides("client-overrides", old_index_folder); + for (auto entry : old_overrides) { + qDebug() << "Removing" << entry; + old_minecraft_dir.remove(entry); + } } - // TODO: Currently 'overrides' will always override the stuff on update. How do we preserve unchanged overrides? setOverride(true); qDebug() << "Will override instance!"; @@ -112,12 +130,14 @@ bool ModrinthCreationTask::createInstance() { QEventLoop loop; + QString parent_folder(FS::PathCombine(m_stagingPath, "mrpack")); + QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json"); if (m_files.empty() && !parseManifest(index_path, m_files)) return false; // Keep index file in case we need it some other time (like when changing versions) - QString new_index_place(FS::PathCombine(m_stagingPath, "mrpack", "modrinth.index.json")); + QString new_index_place(FS::PathCombine(parent_folder, "modrinth.index.json")); FS::ensureFilePathExists(new_index_place); QFile::rename(index_path, new_index_place); @@ -125,6 +145,10 @@ bool ModrinthCreationTask::createInstance() auto override_path = FS::PathCombine(m_stagingPath, "overrides"); if (QFile::exists(override_path)) { + // Create a list of overrides in "overrides.txt" inside mrpack/ + Override::createOverrides("overrides", parent_folder, override_path); + + // Apply the overrides if (!QFile::rename(override_path, mcPath)) { setError(tr("Could not rename the overrides folder:\n") + "overrides"); return false; @@ -134,6 +158,10 @@ bool ModrinthCreationTask::createInstance() // Do client overrides auto client_override_path = FS::PathCombine(m_stagingPath, "client-overrides"); if (QFile::exists(client_override_path)) { + // Create a list of overrides in "client-overrides.txt" inside mrpack/ + Override::createOverrides("client-overrides", parent_folder, client_override_path); + + // Apply the overrides if (!FS::overrideFolder(mcPath, client_override_path)) { setError(tr("Could not rename the client overrides folder:\n") + "client overrides"); return false; From be769d07f1498a212599234a3ad14c6b9431d206 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 31 Jul 2022 19:50:09 -0300 Subject: [PATCH 212/273] fix: correctly set all managed pack fields in Modrinth pack Signed-off-by: flow --- .../modrinth/ModrinthInstanceCreationTask.cpp | 28 +++++++++++++------ .../modrinth/ModrinthInstanceCreationTask.h | 10 +++++-- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index 212b3447..b5140f34 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -5,7 +5,6 @@ #include "InstanceList.h" #include "Json.h" -#include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" #include "modplatform/helpers/OverrideUtils.h" @@ -43,8 +42,8 @@ bool ModrinthCreationTask::updateInstance() if (!parseManifest(index_path, m_files)) return false; - auto version_id = inst->getManagedPackVersionID(); - auto version_str = !version_id.isEmpty() ? tr(" (version %1)").arg(version_id) : ""; + auto version_name = inst->getManagedPackVersionName(); + auto version_str = !version_name.isEmpty() ? tr(" (version %1)").arg(version_name) : ""; auto info = CustomMessageBox::selectable(m_parent, tr("Similar modpack was found!"), tr("One or more of your instances are from this same modpack%1. Do you want to create a " @@ -66,7 +65,7 @@ bool ModrinthCreationTask::updateInstance() QFileInfo old_index_file(old_index_path); if (old_index_file.exists()) { std::vector old_files; - parseManifest(old_index_path, old_files); + parseManifest(old_index_path, old_files, false); // Let's remove all duplicated, identical resources! auto files_iterator = m_files.begin(); @@ -121,6 +120,8 @@ bool ModrinthCreationTask::updateInstance() setOverride(true); qDebug() << "Will override instance!"; + m_instance = inst; + // We let it go through the createInstance() stage, just with a couple modifications for updating return false; } @@ -189,7 +190,7 @@ bool ModrinthCreationTask::createInstance() instance.setIconKey("modrinth"); } - instance.setManagedPack("modrinth", getManagedPackID(), m_managed_name, m_managed_id, {}); + instance.setManagedPack("modrinth", getManagedPackID(), m_managed_name, m_managed_version_id, version()); instance.setName(name()); instance.saveNow(); @@ -229,10 +230,17 @@ bool ModrinthCreationTask::createInstance() loop.exec(); + if (m_instance) { + auto inst = m_instance.value(); + + inst->copyManagedPack(instance); + inst->setName(instance.name()); + } + return ended_well; } -bool ModrinthCreationTask::parseManifest(QString index_path, std::vector& files) +bool ModrinthCreationTask::parseManifest(QString index_path, std::vector& files, bool set_managed_info) { try { auto doc = Json::requireDocument(index_path); @@ -244,8 +252,10 @@ bool ModrinthCreationTask::parseManifest(QString index_path, std::vector(obj, "files", "modrinth.index.json"); bool had_optional = false; @@ -348,7 +358,7 @@ QString ModrinthCreationTask::getManagedPackID() const { if (!m_source_url.isEmpty()) { QRegularExpression regex(R"(data\/(.*)\/versions)"); - return regex.match(m_source_url).captured(0); + return regex.match(m_source_url).captured(1); } return {}; diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h index 4e804e58..bcf80682 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h @@ -2,6 +2,10 @@ #include "InstanceCreationTask.h" +#include + +#include "minecraft/MinecraftInstance.h" + #include "modplatform/modrinth/ModrinthPackManifest.h" #include "net/NetJob.h" @@ -11,7 +15,7 @@ class ModrinthCreationTask final : public InstanceCreationTask { public: ModrinthCreationTask(QString staging_path, SettingsObjectPtr global_settings, QWidget* parent, QString source_url = {}) - : InstanceCreationTask(), m_parent(parent) + : InstanceCreationTask(), m_parent(parent), m_source_url(std::move(source_url)) { setStagingPath(staging_path); setParentSettings(global_settings); @@ -24,7 +28,7 @@ class ModrinthCreationTask final : public InstanceCreationTask { bool createInstance() override; private: - bool parseManifest(QString, std::vector&); + bool parseManifest(QString, std::vector&, bool set_managed_info = true); QString getManagedPackID() const; private: @@ -36,4 +40,6 @@ class ModrinthCreationTask final : public InstanceCreationTask { std::vector m_files; NetJob::Ptr m_files_job; + + std::optional m_instance; }; From 8c0816c1669c6acedd798b4d0b49c7a567cdcf1a Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 31 Jul 2022 16:45:01 -0300 Subject: [PATCH 213/273] feat: add override awareness to CF modpack updating Signed-off-by: flow --- .../flame/FlameInstanceCreationTask.cpp | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 202caa28..4d70e223 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -11,14 +11,13 @@ #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" +#include "modplatform/helpers/OverrideUtils.h" + #include "settings/INISettingsObject.h" #include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/BlockedModsDialog.h" -// NOTE: Because of CF's ToS, I don't know if it counts as caching data, so it'll be disabled for now -#define DO_DIFF_UPDATE 0 - const static QMap forgemap = { { "1.2.5", "3.4.9.171" }, { "1.4.2", "6.0.1.355" }, { "1.4.7", "6.6.2.534" }, @@ -77,7 +76,9 @@ bool FlameCreationTask::updateInstance() QDir old_inst_dir(inst->instanceRoot()); - QString old_index_path(FS::PathCombine(old_inst_dir.absolutePath(), "flame", "manifest.json")); + QString old_index_folder(FS::PathCombine(old_inst_dir.absolutePath(), "flame")); + QString old_index_path(FS::PathCombine(old_index_folder, "manifest.json")); + QFileInfo old_index_file(old_index_path); if (old_index_file.exists()) { Flame::Manifest old_pack; @@ -85,11 +86,9 @@ bool FlameCreationTask::updateInstance() auto& old_files = old_pack.files; -#if DO_DIFF_UPDATE - // Remove repeated files, we don't need to download them! auto& files = m_pack.files; - // Let's remove all duplicated, identical resources! + // Remove repeated files, we don't need to download them! auto files_iterator = files.begin(); while (files_iterator != files.end()) { auto const& file = files_iterator; @@ -107,7 +106,18 @@ bool FlameCreationTask::updateInstance() files_iterator++; } -#endif + + QString old_minecraft_dir(inst->gameRoot()); + + // We will remove all the previous overrides, to prevent duplicate files! + // TODO: Currently 'overrides' will always override the stuff on update. How do we preserve unchanged overrides? + // FIXME: We may want to do something about disabled mods. + auto old_overrides = Override::readOverrides("overrides", old_index_folder); + for (auto entry : old_overrides) { + qDebug() << "Removing" << entry; + old_minecraft_dir.remove(entry); + } + // Remove remaining old files (we need to do an API request to know which ids are which files...) QStringList fileIds; @@ -120,7 +130,7 @@ bool FlameCreationTask::updateInstance() QEventLoop loop; - connect(job, &NetJob::succeeded, this, [raw_response, fileIds, old_inst_dir, &old_files] { + connect(job, &NetJob::succeeded, this, [raw_response, fileIds, old_inst_dir, &old_files, old_minecraft_dir] { // Parse the API response QJsonParseError parse_error{}; auto doc = QJsonDocument::fromJson(*raw_response, &parse_error); @@ -158,7 +168,7 @@ bool FlameCreationTask::updateInstance() continue; qDebug() << "Removing" << file.fileName << "at" << file.targetFolder; - QString path(FS::PathCombine(old_inst_dir.absolutePath(), "minecraft", file.targetFolder, file.fileName)); + QString path(FS::PathCombine(old_minecraft_dir, file.targetFolder, file.fileName)); if (!QFile::remove(path)) qDebug() << "Failed to remove file at" << path; } @@ -172,7 +182,6 @@ bool FlameCreationTask::updateInstance() m_process_update_file_info_job = nullptr; } - // TODO: Currently 'overrides' will always override the stuff on update. How do we preserve unchanged overrides? setOverride(true); qDebug() << "Will override instance!"; @@ -185,13 +194,15 @@ bool FlameCreationTask::createInstance() { QEventLoop loop; + QString parent_folder(FS::PathCombine(m_stagingPath, "flame")); + try { QString index_path(FS::PathCombine(m_stagingPath, "manifest.json")); if (!m_pack.is_loaded) Flame::loadManifest(m_pack, index_path); // Keep index file in case we need it some other time (like when changing versions) - QString new_index_place(FS::PathCombine(m_stagingPath, "flame", "manifest.json")); + QString new_index_place(FS::PathCombine(parent_folder, "manifest.json")); FS::ensureFilePathExists(new_index_place); QFile::rename(index_path, new_index_place); @@ -203,6 +214,9 @@ bool FlameCreationTask::createInstance() if (!m_pack.overrides.isEmpty()) { QString overridePath = FS::PathCombine(m_stagingPath, m_pack.overrides); if (QFile::exists(overridePath)) { + // Create a list of overrides in "overrides.txt" inside flame/ + Override::createOverrides("overrides", parent_folder, overridePath); + QString mcPath = FS::PathCombine(m_stagingPath, "minecraft"); if (!QFile::rename(overridePath, mcPath)) { setError(tr("Could not rename the overrides folder:\n") + m_pack.overrides); @@ -338,6 +352,7 @@ void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) } else { m_mod_id_resolver.reset(); setError("Canceled"); + loop.quit(); } } else { setupDownloadJob(loop); From 4b0ceea8941826134c949b1c2fb80e05c174e5ec Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 31 Jul 2022 19:56:14 -0300 Subject: [PATCH 214/273] fix: correctly set managed pack fields in CF pack Signed-off-by: flow --- launcher/modplatform/flame/FlameInstanceCreationTask.cpp | 9 +++++++++ launcher/modplatform/flame/FlameInstanceCreationTask.h | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 4d70e223..76ac11af 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -186,6 +186,8 @@ bool FlameCreationTask::updateInstance() setOverride(true); qDebug() << "Will override instance!"; + m_instance = inst; + // We let it go through the createInstance() stage, just with a couple modifications for updating return false; } @@ -319,6 +321,13 @@ bool FlameCreationTask::createInstance() loop.exec(); + if (m_instance) { + auto inst = m_instance.value(); + + inst->copyManagedPack(instance); + inst->setName(instance.name()); + } + return getError().isEmpty(); } diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.h b/launcher/modplatform/flame/FlameInstanceCreationTask.h index 99822d93..ccb5f827 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.h +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.h @@ -2,6 +2,10 @@ #include "InstanceCreationTask.h" +#include + +#include "minecraft/MinecraftInstance.h" + #include "modplatform/flame/FileResolvingTask.h" #include "net/NetJob.h" @@ -35,4 +39,6 @@ class FlameCreationTask final : public InstanceCreationTask { // Handle to allow aborting NetJob* m_process_update_file_info_job = nullptr; NetJob::Ptr m_files_job = nullptr; + + std::optional m_instance; }; From 654157096985d0cec0d3b7bb53f39a5c0157206e Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 28 Jul 2022 15:58:04 -0300 Subject: [PATCH 215/273] fix: simplify abort handling and add missing emits Signed-off-by: flow --- launcher/InstanceImportTask.cpp | 11 ++++++++--- launcher/InstanceImportTask.h | 1 + launcher/InstanceList.cpp | 17 ++++++++++------- .../atlauncher/ATLPackInstallTask.cpp | 18 ++++++++++++++++++ .../atlauncher/ATLPackInstallTask.h | 1 + .../modplatform/legacy_ftb/PackFetchTask.cpp | 14 ++++++++++++++ .../modplatform/legacy_ftb/PackFetchTask.h | 2 ++ .../modplatform/legacy_ftb/PackInstallTask.cpp | 6 ++++++ .../modplatform/legacy_ftb/PackInstallTask.h | 1 + .../modpacksch/FTBPackInstallTask.cpp | 3 +-- .../technic/SolderPackInstallTask.cpp | 8 ++++++++ .../technic/SolderPackInstallTask.h | 1 + launcher/ui/MainWindow.cpp | 4 ++++ launcher/ui/dialogs/ProgressDialog.cpp | 5 ++--- .../ui/pages/modplatform/legacy_ftb/Page.cpp | 8 +++++++- .../ui/pages/modplatform/legacy_ftb/Page.h | 1 + 16 files changed, 85 insertions(+), 16 deletions(-) diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 09e65064..56aabb2d 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -91,6 +91,7 @@ void InstanceImportTask::executeTask() connect(m_filesNetJob.get(), &NetJob::succeeded, this, &InstanceImportTask::downloadSucceeded); connect(m_filesNetJob.get(), &NetJob::progress, this, &InstanceImportTask::downloadProgressChanged); connect(m_filesNetJob.get(), &NetJob::failed, this, &InstanceImportTask::downloadFailed); + connect(m_filesNetJob.get(), &NetJob::aborted, this, &InstanceImportTask::downloadAborted); m_filesNetJob->start(); } @@ -113,6 +114,12 @@ void InstanceImportTask::downloadProgressChanged(qint64 current, qint64 total) setProgress(current, total); } +void InstanceImportTask::downloadAborted() +{ + emitAborted(); + m_filesNetJob.reset(); +} + void InstanceImportTask::processZipPack() { setStatus(tr("Extracting modpack")); @@ -246,9 +253,7 @@ void InstanceImportTask::extractFinished() void InstanceImportTask::extractAborted() { - emitFailed(tr("Instance import has been aborted.")); - emit aborted(); - return; + emitAborted(); } void InstanceImportTask::processFlame() diff --git a/launcher/InstanceImportTask.h b/launcher/InstanceImportTask.h index 48ba2161..acb1f9d6 100644 --- a/launcher/InstanceImportTask.h +++ b/launcher/InstanceImportTask.h @@ -80,6 +80,7 @@ private slots: void downloadSucceeded(); void downloadFailed(QString reason); void downloadProgressChanged(qint64 current, qint64 total); + void downloadAborted(); void extractFinished(); void extractAborted(); diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 0e59150e..5f604f9a 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -784,6 +784,7 @@ class InstanceStaging : public Task { m_child.reset(child); connect(child, &Task::succeeded, this, &InstanceStaging::childSucceded); connect(child, &Task::failed, this, &InstanceStaging::childFailed); + connect(child, &Task::aborted, this, &InstanceStaging::childAborted); connect(child, &Task::status, this, &InstanceStaging::setStatus); connect(child, &Task::progress, this, &InstanceStaging::setProgress); connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded); @@ -794,17 +795,14 @@ class InstanceStaging : public Task { // FIXME/TODO: add ability to abort during instance commit retries bool abort() override { - if (m_child && m_child->canAbort()) { + if (m_child && m_child->canAbort()) return m_child->abort(); - } + return false; } bool canAbort() const override { - if (m_child && m_child->canAbort()) { - return true; - } - return false; + return (m_child && m_child->canAbort()); } protected: @@ -834,7 +832,12 @@ class InstanceStaging : public Task { emitFailed(reason); } - private: + void childAborted() + { + emitAborted(); + } + +private: InstanceList * m_parent; /* * WHY: the whole reason why this uses an exponential backoff retry scheme is antivirus on Windows. diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp index 13cab256..a553eafd 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.cpp @@ -90,6 +90,7 @@ void PackInstallTask::executeTask() QObject::connect(netJob, &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded); QObject::connect(netJob, &NetJob::failed, this, &PackInstallTask::onDownloadFailed); + QObject::connect(netJob, &NetJob::aborted, this, &PackInstallTask::onDownloadAborted); } void PackInstallTask::onDownloadSucceeded() @@ -169,6 +170,12 @@ void PackInstallTask::onDownloadFailed(QString reason) emitFailed(reason); } +void PackInstallTask::onDownloadAborted() +{ + jobPtr.reset(); + emitAborted(); +} + void PackInstallTask::deleteExistingFiles() { setStatus(tr("Deleting existing files...")); @@ -675,6 +682,11 @@ void PackInstallTask::installConfigs() abortable = true; setProgress(current, total); }); + connect(jobPtr.get(), &NetJob::aborted, [&]{ + abortable = false; + jobPtr.reset(); + emitAborted(); + }); jobPtr->start(); } @@ -831,6 +843,12 @@ void PackInstallTask::downloadMods() abortable = true; setProgress(current, total); }); + connect(jobPtr.get(), &NetJob::aborted, [&] + { + abortable = false; + jobPtr.reset(); + emitAborted(); + }); jobPtr->start(); } diff --git a/launcher/modplatform/atlauncher/ATLPackInstallTask.h b/launcher/modplatform/atlauncher/ATLPackInstallTask.h index a7124d59..ed4436f0 100644 --- a/launcher/modplatform/atlauncher/ATLPackInstallTask.h +++ b/launcher/modplatform/atlauncher/ATLPackInstallTask.h @@ -93,6 +93,7 @@ protected: private slots: void onDownloadSucceeded(); void onDownloadFailed(QString reason); + void onDownloadAborted(); void onModsDownloaded(); void onModsExtracted(); diff --git a/launcher/modplatform/legacy_ftb/PackFetchTask.cpp b/launcher/modplatform/legacy_ftb/PackFetchTask.cpp index 4da6a866..36aa60c7 100644 --- a/launcher/modplatform/legacy_ftb/PackFetchTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackFetchTask.cpp @@ -59,6 +59,7 @@ void PackFetchTask::fetch() QObject::connect(jobPtr.get(), &NetJob::succeeded, this, &PackFetchTask::fileDownloadFinished); QObject::connect(jobPtr.get(), &NetJob::failed, this, &PackFetchTask::fileDownloadFailed); + QObject::connect(jobPtr.get(), &NetJob::aborted, this, &PackFetchTask::fileDownloadAborted); jobPtr->start(); } @@ -98,6 +99,14 @@ void PackFetchTask::fetchPrivate(const QStringList & toFetch) delete data; }); + QObject::connect(job, &NetJob::aborted, this, [this, job, data]{ + emit aborted(); + job->deleteLater(); + + data->clear(); + delete data; + }); + job->start(); } } @@ -204,4 +213,9 @@ void PackFetchTask::fileDownloadFailed(QString reason) emit failed(reason); } +void PackFetchTask::fileDownloadAborted() +{ + emit aborted(); +} + } diff --git a/launcher/modplatform/legacy_ftb/PackFetchTask.h b/launcher/modplatform/legacy_ftb/PackFetchTask.h index f1667e90..8f3c4f3b 100644 --- a/launcher/modplatform/legacy_ftb/PackFetchTask.h +++ b/launcher/modplatform/legacy_ftb/PackFetchTask.h @@ -33,10 +33,12 @@ private: protected slots: void fileDownloadFinished(); void fileDownloadFailed(QString reason); + void fileDownloadAborted(); signals: void finished(ModpackList publicPacks, ModpackList thirdPartyPacks); void failed(QString reason); + void aborted(); void privateFileDownloadFinished(Modpack modpack); void privateFileDownloadFailed(QString reason, QString packCode); diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp index e2e61a7c..209ad884 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.cpp +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.cpp @@ -86,6 +86,7 @@ void PackInstallTask::downloadPack() connect(netJobContainer.get(), &NetJob::succeeded, this, &PackInstallTask::onDownloadSucceeded); connect(netJobContainer.get(), &NetJob::failed, this, &PackInstallTask::onDownloadFailed); connect(netJobContainer.get(), &NetJob::progress, this, &PackInstallTask::onDownloadProgress); + connect(netJobContainer.get(), &NetJob::aborted, this, &PackInstallTask::onDownloadAborted); netJobContainer->start(); progress(1, 4); @@ -110,6 +111,11 @@ void PackInstallTask::onDownloadProgress(qint64 current, qint64 total) setStatus(tr("Downloading zip for %1 (%2%)").arg(m_pack.name).arg(current / 10)); } +void PackInstallTask::onDownloadAborted() +{ + emitAborted(); +} + void PackInstallTask::unzip() { progress(2, 4); diff --git a/launcher/modplatform/legacy_ftb/PackInstallTask.h b/launcher/modplatform/legacy_ftb/PackInstallTask.h index da4c0da5..da791e06 100644 --- a/launcher/modplatform/legacy_ftb/PackInstallTask.h +++ b/launcher/modplatform/legacy_ftb/PackInstallTask.h @@ -38,6 +38,7 @@ private slots: void onDownloadSucceeded(); void onDownloadFailed(QString reason); void onDownloadProgress(qint64 current, qint64 total); + void onDownloadAborted(); void onUnzipFinished(); void onUnzipCanceled(); diff --git a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp index f17a311a..97ce1dc6 100644 --- a/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp +++ b/launcher/modplatform/modpacksch/FTBPackInstallTask.cpp @@ -65,9 +65,8 @@ bool PackInstallTask::abort() if (m_mod_id_resolver_task) aborted &= m_mod_id_resolver_task->abort(); - // FIXME: This should be 'emitAborted()', but InstanceStaging doesn't connect to the abort signal yet... if (aborted) - emitFailed(tr("Aborted")); + emitAborted(); return aborted; } diff --git a/launcher/modplatform/technic/SolderPackInstallTask.cpp b/launcher/modplatform/technic/SolderPackInstallTask.cpp index 65b6ca51..19731b38 100644 --- a/launcher/modplatform/technic/SolderPackInstallTask.cpp +++ b/launcher/modplatform/technic/SolderPackInstallTask.cpp @@ -77,6 +77,7 @@ void Technic::SolderPackInstallTask::executeTask() auto job = m_filesNetJob.get(); connect(job, &NetJob::succeeded, this, &Technic::SolderPackInstallTask::fileListSucceeded); connect(job, &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed); + connect(job, &NetJob::aborted, this, &Technic::SolderPackInstallTask::downloadAborted); m_filesNetJob->start(); } @@ -127,6 +128,7 @@ void Technic::SolderPackInstallTask::fileListSucceeded() connect(m_filesNetJob.get(), &NetJob::succeeded, this, &Technic::SolderPackInstallTask::downloadSucceeded); connect(m_filesNetJob.get(), &NetJob::progress, this, &Technic::SolderPackInstallTask::downloadProgressChanged); connect(m_filesNetJob.get(), &NetJob::failed, this, &Technic::SolderPackInstallTask::downloadFailed); + connect(m_filesNetJob.get(), &NetJob::aborted, this, &Technic::SolderPackInstallTask::downloadAborted); m_filesNetJob->start(); } @@ -171,6 +173,12 @@ void Technic::SolderPackInstallTask::downloadProgressChanged(qint64 current, qin setProgress(current / 2, total); } +void Technic::SolderPackInstallTask::downloadAborted() +{ + emitAborted(); + m_filesNetJob.reset(); +} + void Technic::SolderPackInstallTask::extractFinished() { if (!m_extractFuture.result()) diff --git a/launcher/modplatform/technic/SolderPackInstallTask.h b/launcher/modplatform/technic/SolderPackInstallTask.h index 117a7bd6..aa14ce88 100644 --- a/launcher/modplatform/technic/SolderPackInstallTask.h +++ b/launcher/modplatform/technic/SolderPackInstallTask.h @@ -61,6 +61,7 @@ namespace Technic void downloadSucceeded(); void downloadFailed(QString reason); void downloadProgressChanged(qint64 current, qint64 total); + void downloadAborted(); void extractFinished(); void extractAborted(); diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 58b1ae80..5729b44d 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1656,6 +1656,10 @@ void MainWindow::runModalTask(Task *task) CustomMessageBox::selectable(this, tr("Warnings"), warnings.join('\n'), QMessageBox::Warning)->show(); } }); + connect(task, &Task::aborted, [this] + { + CustomMessageBox::selectable(this, tr("Task aborted"), tr("The task has been aborted by the user."), QMessageBox::Information)->show(); + }); ProgressDialog loadDialog(this); loadDialog.setSkipButton(true, tr("Abort")); loadDialog.execWithTask(task); diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index 3c7f53d3..6f9cc8e0 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -43,8 +43,7 @@ void ProgressDialog::setSkipButton(bool present, QString label) void ProgressDialog::on_skipButton_clicked(bool checked) { Q_UNUSED(checked); - if (task->abort()) - QDialog::reject(); + task->abort(); } ProgressDialog::~ProgressDialog() @@ -81,7 +80,7 @@ int ProgressDialog::execWithTask(Task* task) connect(task, &Task::stepStatus, this, &ProgressDialog::changeStatus); connect(task, &Task::progress, this, &ProgressDialog::changeProgress); - connect(task, &Task::aborted, [this] { onTaskFailed(tr("Aborted by user")); }); + connect(task, &Task::aborted, [this] { QDialog::reject(); }); m_is_multi_step = task->isMultiStep(); if (!m_is_multi_step) { diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp index 0208b36b..98ab8799 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp +++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.cpp @@ -146,6 +146,7 @@ void Page::openedImpl() { connect(ftbFetchTask.get(), &PackFetchTask::finished, this, &Page::ftbPackDataDownloadSuccessfully); connect(ftbFetchTask.get(), &PackFetchTask::failed, this, &Page::ftbPackDataDownloadFailed); + connect(ftbFetchTask.get(), &PackFetchTask::aborted, this, &Page::ftbPackDataDownloadAborted); connect(ftbFetchTask.get(), &PackFetchTask::privateFileDownloadFinished, this, &Page::ftbPrivatePackDataDownloadSuccessfully); connect(ftbFetchTask.get(), &PackFetchTask::privateFileDownloadFailed, this, &Page::ftbPrivatePackDataDownloadFailed); @@ -220,7 +221,12 @@ void Page::ftbPackDataDownloadSuccessfully(ModpackList publicPacks, ModpackList void Page::ftbPackDataDownloadFailed(QString reason) { - //TODO: Display the error + CustomMessageBox::selectable(this, tr("Error"), reason, QMessageBox::Critical)->show(); +} + +void Page::ftbPackDataDownloadAborted() +{ + CustomMessageBox::selectable(this, tr("Task aborted"), tr("The task has been aborted by the user."), QMessageBox::Information)->show(); } void Page::ftbPrivatePackDataDownloadSuccessfully(Modpack pack) diff --git a/launcher/ui/pages/modplatform/legacy_ftb/Page.h b/launcher/ui/pages/modplatform/legacy_ftb/Page.h index 52db7d91..1de8b40a 100644 --- a/launcher/ui/pages/modplatform/legacy_ftb/Page.h +++ b/launcher/ui/pages/modplatform/legacy_ftb/Page.h @@ -95,6 +95,7 @@ private: private slots: void ftbPackDataDownloadSuccessfully(ModpackList publicPacks, ModpackList thirdPartyPacks); void ftbPackDataDownloadFailed(QString reason); + void ftbPackDataDownloadAborted(); void ftbPrivatePackDataDownloadSuccessfully(Modpack pack); void ftbPrivatePackDataDownloadFailed(QString reason, QString packCode); From 6a50fa35ec0dcb8b91ffc0ce70d4d8b9ee5d9fb8 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 31 Jul 2022 17:56:51 -0300 Subject: [PATCH 216/273] feat: add canAbort() status change in Task By now, it's a recurring pattern of wanting to restrict aborting in certain situations. This avoids further code duplication, and adds a signal that external users can hook up to to respond to such change. Signed-off-by: flow --- launcher/tasks/Task.h | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index 2baf0188..f2872643 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -68,7 +68,7 @@ class Task : public QObject, public QRunnable { virtual QStringList warnings() const; - virtual bool canAbort() const { return false; } + virtual bool canAbort() const { return m_can_abort; } auto getState() const -> State { return m_state; } @@ -96,6 +96,10 @@ class Task : public QObject, public QRunnable { void status(QString status); void stepStatus(QString status); + /** Emitted when the canAbort() status has changed. + */ + void abortStatusChanged(bool can_abort); + public slots: // QRunnable's interface void run() override { start(); } @@ -103,6 +107,8 @@ class Task : public QObject, public QRunnable { virtual void start(); virtual bool abort() { if(canAbort()) emitAborted(); return canAbort(); }; + void setAbortStatus(bool can_abort) { m_can_abort = can_abort; emit abortStatusChanged(can_abort); } + protected: virtual void executeTask() = 0; @@ -125,4 +131,8 @@ class Task : public QObject, public QRunnable { // TODO: Nuke in favor of QLoggingCategory bool m_show_debug = true; + + private: + // Change using setAbortStatus + bool m_can_abort = false; }; From 87002fb8f84b104a221de07ba99ba0eb5cc6bbb6 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 31 Jul 2022 18:21:59 -0300 Subject: [PATCH 217/273] fix: hook up setAbortStatus in instance import tasks Signed-off-by: flow --- launcher/InstanceCreationTask.cpp | 15 +++++++++++++-- launcher/InstanceImportTask.cpp | 9 ++++++++- launcher/InstanceImportTask.h | 1 - launcher/InstanceList.cpp | 1 + .../flame/FlameInstanceCreationTask.cpp | 5 ++++- .../modrinth/ModrinthInstanceCreationTask.cpp | 5 ++++- .../modrinth/ModrinthInstanceCreationTask.h | 1 - 7 files changed, 30 insertions(+), 7 deletions(-) diff --git a/launcher/InstanceCreationTask.cpp b/launcher/InstanceCreationTask.cpp index c8c91997..d7663c2d 100644 --- a/launcher/InstanceCreationTask.cpp +++ b/launcher/InstanceCreationTask.cpp @@ -2,11 +2,22 @@ #include -InstanceCreationTask::InstanceCreationTask() {} +InstanceCreationTask::InstanceCreationTask() = default; void InstanceCreationTask::executeTask() { - if (updateInstance() || createInstance()) { + if (updateInstance()) { + emitSucceeded(); + return; + } + + // If this is set, it means we're updating an instance. Since the previous step likely + // removed some old files, we'd better not let the user abort the next task, since it'd + // put the instance in an invalid state. + // TODO: Figure out an unexpensive way of making such file removal a recoverable transaction. + setAbortStatus(!shouldOverride()); + + if (createInstance()) { emitSucceeded(); return; } diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 56aabb2d..4819a6ff 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -63,15 +63,20 @@ InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent) bool InstanceImportTask::abort() { + if (!canAbort()) + return false; + if (m_filesNetJob) m_filesNetJob->abort(); m_extractFuture.cancel(); - return false; + return Task::abort(); } void InstanceImportTask::executeTask() { + setAbortStatus(true); + if (m_sourceUrl.isLocalFile()) { m_archivePath = m_sourceUrl.toLocalFile(); processZipPack(); @@ -274,6 +279,7 @@ void InstanceImportTask::processFlame() connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater); connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort); + connect(inst_creation_task, &Task::abortStatusChanged, this, &Task::setAbortStatus); inst_creation_task->start(); } @@ -336,6 +342,7 @@ void InstanceImportTask::processModrinth() connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater); connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort); + connect(inst_creation_task, &Task::abortStatusChanged, this, &Task::setAbortStatus); inst_creation_task->start(); } diff --git a/launcher/InstanceImportTask.h b/launcher/InstanceImportTask.h index acb1f9d6..ef70c819 100644 --- a/launcher/InstanceImportTask.h +++ b/launcher/InstanceImportTask.h @@ -58,7 +58,6 @@ class InstanceImportTask : public InstanceTask public: explicit InstanceImportTask(const QUrl sourceUrl, QWidget* parent = nullptr); - bool canAbort() const override { return true; } bool abort() override; const QVector &getBlockedFiles() const { diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 5f604f9a..bf25d2d0 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -785,6 +785,7 @@ class InstanceStaging : public Task { connect(child, &Task::succeeded, this, &InstanceStaging::childSucceded); connect(child, &Task::failed, this, &InstanceStaging::childFailed); connect(child, &Task::aborted, this, &InstanceStaging::childAborted); + connect(child, &Task::abortStatusChanged, this, &InstanceStaging::setAbortStatus); connect(child, &Task::status, this, &InstanceStaging::setStatus); connect(child, &Task::progress, this, &InstanceStaging::setProgress); connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded); diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 76ac11af..c8b2e297 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -27,6 +27,9 @@ static const FlameAPI api; bool FlameCreationTask::abort() { + if (!canAbort()) + return false; + if (m_process_update_file_info_job) m_process_update_file_info_job->abort(); if (m_files_job) @@ -34,7 +37,7 @@ bool FlameCreationTask::abort() if (m_mod_id_resolver) m_mod_id_resolver->abort(); - return true; + return Task::abort(); } bool FlameCreationTask::updateInstance() diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index b5140f34..3234d92b 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -19,9 +19,12 @@ bool ModrinthCreationTask::abort() { + if (!canAbort()) + return false; + if (m_files_job) return m_files_job->abort(); - return true; + return Task::abort(); } bool ModrinthCreationTask::updateInstance() diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h index bcf80682..e87e4fb9 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h @@ -22,7 +22,6 @@ class ModrinthCreationTask final : public InstanceCreationTask { } bool abort() override; - bool canAbort() const override { return true; } bool updateInstance() override; bool createInstance() override; From 68facd6b93d1a08a7c2417aa0d0ec614b0feecbe Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 31 Jul 2022 18:28:56 -0300 Subject: [PATCH 218/273] fix(ui): hook up abort status signal in ProgressDialog Now we have a visual indication on when tasks are abortable! Signed-off-by: flow --- launcher/ui/dialogs/ProgressDialog.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/ui/dialogs/ProgressDialog.cpp b/launcher/ui/dialogs/ProgressDialog.cpp index 6f9cc8e0..258a32e4 100644 --- a/launcher/ui/dialogs/ProgressDialog.cpp +++ b/launcher/ui/dialogs/ProgressDialog.cpp @@ -81,6 +81,7 @@ int ProgressDialog::execWithTask(Task* task) connect(task, &Task::progress, this, &ProgressDialog::changeProgress); connect(task, &Task::aborted, [this] { QDialog::reject(); }); + connect(task, &Task::abortStatusChanged, ui->skipButton, &QPushButton::setEnabled); m_is_multi_step = task->isMultiStep(); if (!m_is_multi_step) { From eda6cf11ef53c11ed2691399ec1adbc83ca0a4d6 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 31 Jul 2022 20:29:12 -0300 Subject: [PATCH 219/273] feat(ui): improve info dialog before updating an instance Adds a 'Cancel' option, and add a note about doing a backup before updating. Signed-off-by: flow --- launcher/InstanceCreationTask.cpp | 6 ++++ launcher/InstanceCreationTask.h | 3 ++ launcher/InstanceImportTask.cpp | 2 ++ .../flame/FlameInstanceCreationTask.cpp | 21 +++++++---- .../modrinth/ModrinthInstanceCreationTask.cpp | 36 ++++++++++++------- .../modrinth/ModrinthInstanceCreationTask.h | 2 +- 6 files changed, 50 insertions(+), 20 deletions(-) diff --git a/launcher/InstanceCreationTask.cpp b/launcher/InstanceCreationTask.cpp index d7663c2d..3908bb9a 100644 --- a/launcher/InstanceCreationTask.cpp +++ b/launcher/InstanceCreationTask.cpp @@ -11,6 +11,12 @@ void InstanceCreationTask::executeTask() return; } + // When the user aborted in the update stage. + if (m_abort) { + emitAborted(); + return; + } + // If this is set, it means we're updating an instance. Since the previous step likely // removed some old files, we'd better not let the user abort the next task, since it'd // put the instance in an invalid state. diff --git a/launcher/InstanceCreationTask.h b/launcher/InstanceCreationTask.h index 68c5de59..9b51c448 100644 --- a/launcher/InstanceCreationTask.h +++ b/launcher/InstanceCreationTask.h @@ -36,6 +36,9 @@ class InstanceCreationTask : public InstanceTask { protected: void setError(QString message) { m_error_message = message; }; + protected: + bool m_abort = false; + private: QString m_error_message; }; diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index 4819a6ff..e35913da 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -279,6 +279,7 @@ void InstanceImportTask::processFlame() connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater); connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort); + connect(inst_creation_task, &Task::aborted, this, &Task::abort); connect(inst_creation_task, &Task::abortStatusChanged, this, &Task::setAbortStatus); inst_creation_task->start(); @@ -342,6 +343,7 @@ void InstanceImportTask::processModrinth() connect(inst_creation_task, &Task::finished, inst_creation_task, &InstanceCreationTask::deleteLater); connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort); + connect(inst_creation_task, &Task::aborted, this, &Task::abort); connect(inst_creation_task, &Task::abortStatusChanged, this, &Task::setAbortStatus); inst_creation_task->start(); diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index c8b2e297..22ee0998 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -66,17 +66,26 @@ bool FlameCreationTask::updateInstance() auto version_id = inst->getManagedPackVersionName(); auto version_str = !version_id.isEmpty() ? tr(" (version %1)").arg(version_id) : ""; - auto info = CustomMessageBox::selectable(m_parent, tr("Similar modpack was found!"), - tr("One or more of your instances are from this same modpack%1. Do you want to create a " - "separate instance, or update the existing one?") - .arg(version_str), - QMessageBox::Information, QMessageBox::Ok | QMessageBox::Abort); + auto info = CustomMessageBox::selectable( + m_parent, tr("Similar modpack was found!"), + tr("One or more of your instances are from this same modpack%1. Do you want to create a " + "separate instance, or update the existing one?\n\nNOTE: Make sure you made a backup of your important instance data before " + "updating, as worlds can be corrupted and some configuration may be lost (due to pack overrides).") + .arg(version_str), QMessageBox::Information, QMessageBox::Ok | QMessageBox::Reset | QMessageBox::Abort); info->setButtonText(QMessageBox::Ok, tr("Update existing instance")); info->setButtonText(QMessageBox::Abort, tr("Create new instance")); + info->setButtonText(QMessageBox::Reset, tr("Cancel")); - if (info->exec() && info->clickedButton() == info->button(QMessageBox::Abort)) + info->exec(); + + if (info->clickedButton() == info->button(QMessageBox::Abort)) return false; + if (info->clickedButton() == info->button(QMessageBox::Reset)) { + m_abort = true; + return false; + } + QDir old_inst_dir(inst->instanceRoot()); QString old_index_folder(FS::PathCombine(old_inst_dir.absolutePath(), "flame")); diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index 3234d92b..449d7387 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -42,23 +42,33 @@ bool ModrinthCreationTask::updateInstance() } QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json"); - if (!parseManifest(index_path, m_files)) + if (!parseManifest(index_path, m_files, true, false)) return false; auto version_name = inst->getManagedPackVersionName(); auto version_str = !version_name.isEmpty() ? tr(" (version %1)").arg(version_name) : ""; - auto info = CustomMessageBox::selectable(m_parent, tr("Similar modpack was found!"), - tr("One or more of your instances are from this same modpack%1. Do you want to create a " - "separate instance, or update the existing one?") - .arg(version_str), - QMessageBox::Information, QMessageBox::Ok | QMessageBox::Abort); - info->setButtonText(QMessageBox::Ok, tr("Update existing instance")); - info->setButtonText(QMessageBox::Abort, tr("Create new instance")); + auto info = CustomMessageBox::selectable( + m_parent, tr("Similar modpack was found!"), + tr("One or more of your instances are from this same modpack%1. Do you want to create a " + "separate instance, or update the existing one?\n\nNOTE: Make sure you made a backup of your important instance data before " + "updating, as worlds can be corrupted and some configuration may be lost (due to pack overrides).") + .arg(version_str), + QMessageBox::Information, QMessageBox::Ok | QMessageBox::Reset | QMessageBox::Abort); + info->setButtonText(QMessageBox::Ok, tr("Create new instance")); + info->setButtonText(QMessageBox::Abort, tr("Update existing instance")); + info->setButtonText(QMessageBox::Reset, tr("Cancel")); - if (info->exec() && info->clickedButton() == info->button(QMessageBox::Abort)) + info->exec(); + + if (info->clickedButton() == info->button(QMessageBox::Ok)) return false; + if (info->clickedButton() == info->button(QMessageBox::Reset)) { + m_abort = true; + return false; + } + // Remove repeated files, we don't need to download them! QDir old_inst_dir(inst->instanceRoot()); @@ -68,7 +78,7 @@ bool ModrinthCreationTask::updateInstance() QFileInfo old_index_file(old_index_path); if (old_index_file.exists()) { std::vector old_files; - parseManifest(old_index_path, old_files, false); + parseManifest(old_index_path, old_files, false, false); // Let's remove all duplicated, identical resources! auto files_iterator = m_files.begin(); @@ -137,7 +147,7 @@ bool ModrinthCreationTask::createInstance() QString parent_folder(FS::PathCombine(m_stagingPath, "mrpack")); QString index_path = FS::PathCombine(m_stagingPath, "modrinth.index.json"); - if (m_files.empty() && !parseManifest(index_path, m_files)) + if (m_files.empty() && !parseManifest(index_path, m_files, true, true)) return false; // Keep index file in case we need it some other time (like when changing versions) @@ -243,7 +253,7 @@ bool ModrinthCreationTask::createInstance() return ended_well; } -bool ModrinthCreationTask::parseManifest(QString index_path, std::vector& files, bool set_managed_info) +bool ModrinthCreationTask::parseManifest(QString index_path, std::vector& files, bool set_managed_info, bool show_optional_dialog) { try { auto doc = Json::requireDocument(index_path); @@ -274,7 +284,7 @@ bool ModrinthCreationTask::parseManifest(QString index_path, std::vector&, bool set_managed_info = true); + bool parseManifest(QString, std::vector&, bool set_managed_info = true, bool show_optional_dialog = true); QString getManagedPackID() const; private: From 2dd372600c5e744067fda51f8ffae16fb1caa16c Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 5 Aug 2022 21:25:21 -0300 Subject: [PATCH 220/273] fix: some abort-related issues Signed-off-by: flow --- launcher/InstanceCreationTask.cpp | 2 ++ launcher/InstanceList.cpp | 8 +++++--- launcher/modplatform/flame/FlameInstanceCreationTask.cpp | 1 + .../modplatform/modrinth/ModrinthInstanceCreationTask.cpp | 3 ++- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/launcher/InstanceCreationTask.cpp b/launcher/InstanceCreationTask.cpp index 3908bb9a..a82bdfe8 100644 --- a/launcher/InstanceCreationTask.cpp +++ b/launcher/InstanceCreationTask.cpp @@ -6,6 +6,8 @@ InstanceCreationTask::InstanceCreationTask() = default; void InstanceCreationTask::executeTask() { + setAbortStatus(true); + if (updateInstance()) { emitSucceeded(); return; diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index bf25d2d0..a4b8d8aa 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -796,10 +796,12 @@ class InstanceStaging : public Task { // FIXME/TODO: add ability to abort during instance commit retries bool abort() override { - if (m_child && m_child->canAbort()) - return m_child->abort(); + if (!canAbort()) + return false; - return false; + m_child->abort(); + + return Task::abort(); } bool canAbort() const override { diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 22ee0998..5acb6300 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -30,6 +30,7 @@ bool FlameCreationTask::abort() if (!canAbort()) return false; + m_abort = true; if (m_process_update_file_info_job) m_process_update_file_info_job->abort(); if (m_files_job) diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index 449d7387..a0c67876 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -22,8 +22,9 @@ bool ModrinthCreationTask::abort() if (!canAbort()) return false; + m_abort = true; if (m_files_job) - return m_files_job->abort(); + m_files_job->abort(); return Task::abort(); } From d2fdbec41dd664fb6cfc66ec6c79ee9f78eb8c7b Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 5 Aug 2022 21:26:02 -0300 Subject: [PATCH 221/273] fix: move file deletion to the end of the instance update This makes it harder for problems in the updating process to affect the current instance. Network issues, for instance, will no longer put the instance in an invalid state. Still, a possible improvement to this would be passing that logic to InstanceStaging instead, to be handled with the instance commiting directly. However, as it is now, the code would become very spaguetti-y, and given that the override operation in the commiting could also put the instance into an invalid state, it seems to me that, in order to fully error-proof this, we would need to do a copy operation on the whole instance, in order to modify the copy, and only in the end override everything an once with a rename. That also has the possibility of corrupting the instance if done without super care, however, so I think we may need to instead create an automatic backup system, with an undo command of sorts, or something like that. This doesn't seem very trivial though, so it'll probably need to wait until another PR. In the meantime, the user is advised to always backup their instances before doing this kind of action, as always. What a long commit message o.O Signed-off-by: flow --- launcher/InstanceCreationTask.cpp | 41 ++++++++++++++----- launcher/InstanceCreationTask.h | 2 + .../flame/FlameInstanceCreationTask.cpp | 25 ++++++----- .../modrinth/ModrinthInstanceCreationTask.cpp | 22 ++++++---- 4 files changed, 61 insertions(+), 29 deletions(-) diff --git a/launcher/InstanceCreationTask.cpp b/launcher/InstanceCreationTask.cpp index a82bdfe8..1b1a8c15 100644 --- a/launcher/InstanceCreationTask.cpp +++ b/launcher/InstanceCreationTask.cpp @@ -1,6 +1,7 @@ #include "InstanceCreationTask.h" #include +#include InstanceCreationTask::InstanceCreationTask() = default; @@ -19,19 +20,37 @@ void InstanceCreationTask::executeTask() return; } - // If this is set, it means we're updating an instance. Since the previous step likely - // removed some old files, we'd better not let the user abort the next task, since it'd - // put the instance in an invalid state. - // TODO: Figure out an unexpensive way of making such file removal a recoverable transaction. - setAbortStatus(!shouldOverride()); + if (!createInstance()) { + if (m_abort) + return; - if (createInstance()) { - emitSucceeded(); + qWarning() << "Instance creation failed!"; + if (!m_error_message.isEmpty()) + qWarning() << "Reason: " << m_error_message; + emitFailed(tr("Error while creating new instance.")); return; } - qWarning() << "Instance creation failed!"; - if (!m_error_message.isEmpty()) - qWarning() << "Reason: " << m_error_message; - emitFailed(tr("Error while creating new instance.")); + // If this is set, it means we're updating an instance. So, we now need to remove the + // files scheduled to, and we'd better not let the user abort in the middle of it, since it'd + // put the instance in an invalid state. + if (shouldOverride()) { + setAbortStatus(false); + setStatus(tr("Removing old conflicting files...")); + qDebug() << "Removing old files"; + + for (auto path : m_files_to_remove) { + if (!QFile::exists(path)) + continue; + qDebug() << "Removing" << path; + if (!QFile::remove(path)) { + qCritical() << "Couldn't remove the old conflicting files."; + emitFailed(tr("Failed to remove old conflicting files.")); + return; + } + } + } + + emitSucceeded(); + return; } diff --git a/launcher/InstanceCreationTask.h b/launcher/InstanceCreationTask.h index 9b51c448..03ee1a7a 100644 --- a/launcher/InstanceCreationTask.h +++ b/launcher/InstanceCreationTask.h @@ -39,6 +39,8 @@ class InstanceCreationTask : public InstanceTask { protected: bool m_abort = false; + QStringList m_files_to_remove; + private: QString m_error_message; }; diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 5acb6300..e3521a38 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -120,15 +120,17 @@ bool FlameCreationTask::updateInstance() files_iterator++; } - QString old_minecraft_dir(inst->gameRoot()); + QDir old_minecraft_dir(inst->gameRoot()); // We will remove all the previous overrides, to prevent duplicate files! // TODO: Currently 'overrides' will always override the stuff on update. How do we preserve unchanged overrides? // FIXME: We may want to do something about disabled mods. auto old_overrides = Override::readOverrides("overrides", old_index_folder); for (auto entry : old_overrides) { - qDebug() << "Removing" << entry; - old_minecraft_dir.remove(entry); + if (entry.isEmpty()) + continue; + qDebug() << "Scheduling" << entry << "for removal"; + m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(entry)); } // Remove remaining old files (we need to do an API request to know which ids are which files...) @@ -143,7 +145,7 @@ bool FlameCreationTask::updateInstance() QEventLoop loop; - connect(job, &NetJob::succeeded, this, [raw_response, fileIds, old_inst_dir, &old_files, old_minecraft_dir] { + connect(job, &NetJob::succeeded, this, [this, raw_response, fileIds, old_inst_dir, &old_files, old_minecraft_dir] { // Parse the API response QJsonParseError parse_error{}; auto doc = QJsonDocument::fromJson(*raw_response, &parse_error); @@ -180,10 +182,9 @@ bool FlameCreationTask::updateInstance() if (file.fileName.isEmpty() || file.targetFolder.isEmpty()) continue; - qDebug() << "Removing" << file.fileName << "at" << file.targetFolder; - QString path(FS::PathCombine(old_minecraft_dir, file.targetFolder, file.fileName)); - if (!QFile::remove(path)) - qDebug() << "Failed to remove file at" << path; + QString relative_path(FS::PathCombine(file.targetFolder, file.fileName)); + qDebug() << "Scheduling" << relative_path << "for removal"; + m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(relative_path)); } }); connect(job, &NetJob::finished, &loop, &QEventLoop::quit); @@ -334,14 +335,17 @@ bool FlameCreationTask::createInstance() loop.exec(); - if (m_instance) { + bool did_succeed = getError().isEmpty(); + + if (m_instance && did_succeed) { + setAbortStatus(false); auto inst = m_instance.value(); inst->copyManagedPack(instance); inst->setName(instance.name()); } - return getError().isEmpty(); + return did_succeed; } void FlameCreationTask::idResolverSucceeded(QEventLoop& loop) @@ -421,7 +425,6 @@ void FlameCreationTask::setupDownloadJob(QEventLoop& loop) m_mod_id_resolver.reset(); connect(m_files_job.get(), &NetJob::succeeded, this, [&]() { m_files_job.reset(); - emitSucceeded(); }); connect(m_files_job.get(), &NetJob::failed, [&](QString reason) { m_files_job.reset(); diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index a0c67876..7d19639c 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -105,12 +105,15 @@ bool ModrinthCreationTask::updateInstance() } QDir old_minecraft_dir(inst->gameRoot()); + // Some files were removed from the old version, and some will be downloaded in an updated version, // so we're fine removing them! if (!old_files.empty()) { for (auto const& file : old_files) { - qDebug() << "Removing" << file.path; - old_minecraft_dir.remove(file.path); + if (file.path.isEmpty()) + continue; + qDebug() << "Scheduling" << file.path << "for removal"; + m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(file.path)); } } @@ -119,14 +122,18 @@ bool ModrinthCreationTask::updateInstance() // FIXME: We may want to do something about disabled mods. auto old_overrides = Override::readOverrides("overrides", old_index_folder); for (auto entry : old_overrides) { - qDebug() << "Removing" << entry; - old_minecraft_dir.remove(entry); + if (entry.isEmpty()) + continue; + qDebug() << "Scheduling" << entry << "for removal"; + m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(entry)); } auto old_client_overrides = Override::readOverrides("client-overrides", old_index_folder); for (auto entry : old_overrides) { - qDebug() << "Removing" << entry; - old_minecraft_dir.remove(entry); + if (entry.isEmpty()) + continue; + qDebug() << "Scheduling" << entry << "for removal"; + m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(entry)); } } @@ -244,7 +251,8 @@ bool ModrinthCreationTask::createInstance() loop.exec(); - if (m_instance) { + if (m_instance && ended_well) { + setAbortStatus(false); auto inst = m_instance.value(); inst->copyManagedPack(instance); From 9eb35ea7c8b30c4bfa02e2ba218671902d92ff21 Mon Sep 17 00:00:00 2001 From: flow Date: Sat, 20 Aug 2022 11:33:34 -0300 Subject: [PATCH 222/273] fix: don't load specific settings for managed pack info This avoids loading all settings for all instances when searching for one with a specific managed pack name. Signed-off-by: flow --- launcher/BaseInstance.cpp | 48 +++++++++++++++++++-------------------- launcher/BaseInstance.h | 12 +++++----- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/launcher/BaseInstance.cpp b/launcher/BaseInstance.cpp index 3b261678..2995df6f 100644 --- a/launcher/BaseInstance.cpp +++ b/launcher/BaseInstance.cpp @@ -114,54 +114,54 @@ QString BaseInstance::getPostExitCommand() return settings()->get("PostExitCommand").toString(); } -bool BaseInstance::isManagedPack() +bool BaseInstance::isManagedPack() const { - return settings()->get("ManagedPack").toBool(); + return m_settings->get("ManagedPack").toBool(); } -QString BaseInstance::getManagedPackType() +QString BaseInstance::getManagedPackType() const { - return settings()->get("ManagedPackType").toString(); + return m_settings->get("ManagedPackType").toString(); } -QString BaseInstance::getManagedPackID() +QString BaseInstance::getManagedPackID() const { - return settings()->get("ManagedPackID").toString(); + return m_settings->get("ManagedPackID").toString(); } -QString BaseInstance::getManagedPackName() +QString BaseInstance::getManagedPackName() const { - return settings()->get("ManagedPackName").toString(); + return m_settings->get("ManagedPackName").toString(); } -QString BaseInstance::getManagedPackVersionID() +QString BaseInstance::getManagedPackVersionID() const { - return settings()->get("ManagedPackVersionID").toString(); + return m_settings->get("ManagedPackVersionID").toString(); } -QString BaseInstance::getManagedPackVersionName() +QString BaseInstance::getManagedPackVersionName() const { - return settings()->get("ManagedPackVersionName").toString(); + return m_settings->get("ManagedPackVersionName").toString(); } void BaseInstance::setManagedPack(const QString& type, const QString& id, const QString& name, const QString& versionId, const QString& version) { - settings()->set("ManagedPack", true); - settings()->set("ManagedPackType", type); - settings()->set("ManagedPackID", id); - settings()->set("ManagedPackName", name); - settings()->set("ManagedPackVersionID", versionId); - settings()->set("ManagedPackVersionName", version); + m_settings->set("ManagedPack", true); + m_settings->set("ManagedPackType", type); + m_settings->set("ManagedPackID", id); + m_settings->set("ManagedPackName", name); + m_settings->set("ManagedPackVersionID", versionId); + m_settings->set("ManagedPackVersionName", version); } void BaseInstance::copyManagedPack(BaseInstance& other) { - settings()->set("ManagedPack", other.isManagedPack()); - settings()->set("ManagedPackType", other.getManagedPackType()); - settings()->set("ManagedPackID", other.getManagedPackID()); - settings()->set("ManagedPackName", other.getManagedPackName()); - settings()->set("ManagedPackVersionID", other.getManagedPackVersionID()); - settings()->set("ManagedPackVersionName", other.getManagedPackVersionName()); + m_settings->set("ManagedPack", other.isManagedPack()); + m_settings->set("ManagedPackType", other.getManagedPackType()); + m_settings->set("ManagedPackID", other.getManagedPackID()); + m_settings->set("ManagedPackName", other.getManagedPackName()); + m_settings->set("ManagedPackVersionID", other.getManagedPackVersionID()); + m_settings->set("ManagedPackVersionName", other.getManagedPackVersionName()); } int BaseInstance::getConsoleMaxLines() const diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 653d1378..21cc3413 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -140,12 +140,12 @@ public: QString getPostExitCommand(); QString getWrapperCommand(); - bool isManagedPack(); - QString getManagedPackType(); - QString getManagedPackID(); - QString getManagedPackName(); - QString getManagedPackVersionID(); - QString getManagedPackVersionName(); + bool isManagedPack() const; + QString getManagedPackType() const; + QString getManagedPackID() const; + QString getManagedPackName() const; + QString getManagedPackVersionID() const; + QString getManagedPackVersionName() const; void setManagedPack(const QString& type, const QString& id, const QString& name, const QString& versionId, const QString& version); void copyManagedPack(BaseInstance& other); From be8c6f218cfe3acc31335305bb40284f8f6cb582 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 21 Aug 2022 09:26:27 -0300 Subject: [PATCH 223/273] refactor: setAbortStatus -> setAbortable Signed-off-by: flow --- launcher/InstanceCreationTask.cpp | 4 ++-- launcher/InstanceImportTask.cpp | 6 +++--- launcher/InstanceList.cpp | 2 +- launcher/modplatform/flame/FlameInstanceCreationTask.cpp | 2 +- .../modplatform/modrinth/ModrinthInstanceCreationTask.cpp | 2 +- launcher/tasks/Task.h | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/launcher/InstanceCreationTask.cpp b/launcher/InstanceCreationTask.cpp index 1b1a8c15..3971effa 100644 --- a/launcher/InstanceCreationTask.cpp +++ b/launcher/InstanceCreationTask.cpp @@ -7,7 +7,7 @@ InstanceCreationTask::InstanceCreationTask() = default; void InstanceCreationTask::executeTask() { - setAbortStatus(true); + setAbortable(true); if (updateInstance()) { emitSucceeded(); @@ -35,7 +35,7 @@ void InstanceCreationTask::executeTask() // files scheduled to, and we'd better not let the user abort in the middle of it, since it'd // put the instance in an invalid state. if (shouldOverride()) { - setAbortStatus(false); + setAbortable(false); setStatus(tr("Removing old conflicting files...")); qDebug() << "Removing old files"; diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index e35913da..b490620d 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -75,7 +75,7 @@ bool InstanceImportTask::abort() void InstanceImportTask::executeTask() { - setAbortStatus(true); + setAbortable(true); if (m_sourceUrl.isLocalFile()) { m_archivePath = m_sourceUrl.toLocalFile(); @@ -280,7 +280,7 @@ void InstanceImportTask::processFlame() connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort); connect(inst_creation_task, &Task::aborted, this, &Task::abort); - connect(inst_creation_task, &Task::abortStatusChanged, this, &Task::setAbortStatus); + connect(inst_creation_task, &Task::abortStatusChanged, this, &Task::setAbortable); inst_creation_task->start(); } @@ -344,7 +344,7 @@ void InstanceImportTask::processModrinth() connect(this, &Task::aborted, inst_creation_task, &InstanceCreationTask::abort); connect(inst_creation_task, &Task::aborted, this, &Task::abort); - connect(inst_creation_task, &Task::abortStatusChanged, this, &Task::setAbortStatus); + connect(inst_creation_task, &Task::abortStatusChanged, this, &Task::setAbortable); inst_creation_task->start(); } diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index a4b8d8aa..a414e0d5 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -785,7 +785,7 @@ class InstanceStaging : public Task { connect(child, &Task::succeeded, this, &InstanceStaging::childSucceded); connect(child, &Task::failed, this, &InstanceStaging::childFailed); connect(child, &Task::aborted, this, &InstanceStaging::childAborted); - connect(child, &Task::abortStatusChanged, this, &InstanceStaging::setAbortStatus); + connect(child, &Task::abortStatusChanged, this, &InstanceStaging::setAbortable); connect(child, &Task::status, this, &InstanceStaging::setStatus); connect(child, &Task::progress, this, &InstanceStaging::setProgress); connect(&m_backoffTimer, &QTimer::timeout, this, &InstanceStaging::childSucceded); diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index e3521a38..a0eda1b0 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -338,7 +338,7 @@ bool FlameCreationTask::createInstance() bool did_succeed = getError().isEmpty(); if (m_instance && did_succeed) { - setAbortStatus(false); + setAbortable(false); auto inst = m_instance.value(); inst->copyManagedPack(instance); diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index 7d19639c..d4c37cb9 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -252,7 +252,7 @@ bool ModrinthCreationTask::createInstance() loop.exec(); if (m_instance && ended_well) { - setAbortStatus(false); + setAbortable(false); auto inst = m_instance.value(); inst->copyManagedPack(instance); diff --git a/launcher/tasks/Task.h b/launcher/tasks/Task.h index f2872643..3d607dca 100644 --- a/launcher/tasks/Task.h +++ b/launcher/tasks/Task.h @@ -107,7 +107,7 @@ class Task : public QObject, public QRunnable { virtual void start(); virtual bool abort() { if(canAbort()) emitAborted(); return canAbort(); }; - void setAbortStatus(bool can_abort) { m_can_abort = can_abort; emit abortStatusChanged(can_abort); } + void setAbortable(bool can_abort) { m_can_abort = can_abort; emit abortStatusChanged(can_abort); } protected: virtual void executeTask() = 0; From ddde885084a8eba61e691974edbc75186438ed55 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 21 Aug 2022 10:00:23 -0300 Subject: [PATCH 224/273] fix: show warning in case there's no old index when updating Signed-off-by: flow --- .../modplatform/flame/FlameInstanceCreationTask.cpp | 11 +++++++++++ .../modrinth/ModrinthInstanceCreationTask.cpp | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index a0eda1b0..1b282770 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -195,6 +195,17 @@ bool FlameCreationTask::updateInstance() loop.exec(); m_process_update_file_info_job = nullptr; + } else { + // We don't have an old index file, so we may duplicate stuff! + auto dialog = CustomMessageBox::selectable(m_parent, + tr("No index file."), + tr("We couldn't find a suitable index file for the older version. This may cause some of the files to be duplicated. Do you want to continue?"), + QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Cancel); + + if (dialog->exec() == QDialog::DialogCode::Rejected) { + m_abort = true; + return false; + } } setOverride(true); diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index d4c37cb9..c1898dd9 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -135,6 +135,17 @@ bool ModrinthCreationTask::updateInstance() qDebug() << "Scheduling" << entry << "for removal"; m_files_to_remove.append(old_minecraft_dir.absoluteFilePath(entry)); } + } else { + // We don't have an old index file, so we may duplicate stuff! + auto dialog = CustomMessageBox::selectable(m_parent, + tr("No index file."), + tr("We couldn't find a suitable index file for the older version. This may cause some of the files to be duplicated. Do you want to continue?"), + QMessageBox::Warning, QMessageBox::Ok | QMessageBox::Cancel); + + if (dialog->exec() == QDialog::DialogCode::Rejected) { + m_abort = true; + return false; + } } From 06019f01e3e7b87e752ddc15deb850e272a82d21 Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 11 Sep 2022 13:16:25 -0300 Subject: [PATCH 225/273] feat: add dialog to ask whether to chaneg instance's name This prevents custom names from being lost when updating, by only changing the name if the old instance name constains the old version, so that we can update it if the user whishes to. Signed-off-by: flow --- launcher/InstanceList.cpp | 5 ---- launcher/InstanceTask.cpp | 24 ++++++++++++++++--- launcher/InstanceTask.h | 4 ++++ .../flame/FlameInstanceCreationTask.cpp | 12 ++++++++-- .../modrinth/ModrinthInstanceCreationTask.cpp | 10 +++++++- 5 files changed, 44 insertions(+), 11 deletions(-) diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index a414e0d5..47b0e75a 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -908,11 +908,6 @@ bool InstanceList::commitStagedInstance(QString path, InstanceName const& instan qWarning() << "Failed to override" << path << "to" << destination; return false; } - - if (!inst) - inst = getInstanceById(instID); - if (inst) - inst->setName(instanceName.name()); } else { if (!dir.rename(path, destination)) { qWarning() << "Failed to move" << path << "to" << destination; diff --git a/launcher/InstanceTask.cpp b/launcher/InstanceTask.cpp index 43a0b947..da280731 100644 --- a/launcher/InstanceTask.cpp +++ b/launcher/InstanceTask.cpp @@ -1,5 +1,23 @@ #include "InstanceTask.h" +#include "ui/dialogs/CustomMessageBox.h" + +InstanceNameChange askForChangingInstanceName(QWidget* parent, QString old_name, QString new_name) +{ + auto dialog = + CustomMessageBox::selectable(parent, QObject::tr("Change instance name"), + QObject::tr("The instance's name seems to include the old version. Would you like to update it?\n\n" + "Old name: %1\n" + "New name: %2") + .arg(old_name, new_name), + QMessageBox::Question, QMessageBox::No | QMessageBox::Yes); + auto result = dialog->exec(); + + if (result == QMessageBox::Yes) + return InstanceNameChange::ShouldChange; + return InstanceNameChange::ShouldKeep; +} + QString InstanceName::name() const { if (!m_modified_name.isEmpty()) @@ -26,9 +44,9 @@ QString InstanceName::version() const void InstanceName::setName(InstanceName& other) { - m_original_name = other.m_original_name; - m_original_version = other.m_original_version; - m_modified_name = other.m_modified_name; + m_original_name = other.m_original_name; + m_original_version = other.m_original_version; + m_modified_name = other.m_modified_name; } InstanceTask::InstanceTask() : Task(), InstanceName() {} diff --git a/launcher/InstanceTask.h b/launcher/InstanceTask.h index 5d67a2f0..0987b557 100644 --- a/launcher/InstanceTask.h +++ b/launcher/InstanceTask.h @@ -3,6 +3,10 @@ #include "settings/SettingsObject.h" #include "tasks/Task.h" +/* Helpers */ +enum class InstanceNameChange { ShouldChange, ShouldKeep }; +[[nodiscard]] InstanceNameChange askForChangingInstanceName(QWidget* parent, QString old_name, QString new_name); + struct InstanceName { public: InstanceName() = default; diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 1b282770..69a41e55 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -15,8 +15,8 @@ #include "settings/INISettingsObject.h" -#include "ui/dialogs/CustomMessageBox.h" #include "ui/dialogs/BlockedModsDialog.h" +#include "ui/dialogs/CustomMessageBox.h" const static QMap forgemap = { { "1.2.5", "3.4.9.171" }, { "1.4.2", "6.0.1.355" }, @@ -348,12 +348,20 @@ bool FlameCreationTask::createInstance() bool did_succeed = getError().isEmpty(); + // Update information of the already installed instance, if any. if (m_instance && did_succeed) { setAbortable(false); auto inst = m_instance.value(); + // Only change the name if it didn't use a custom name, so that the previous custom name + // is preserved, but if we're using the original one, we update the version string. + // NOTE: This needs to come before the copyManagedPack call! + if (inst->name().contains(inst->getManagedPackVersionName())) { + if (askForChangingInstanceName(m_parent, inst->name(), instance.name()) == InstanceNameChange::ShouldChange) + inst->setName(instance.name()); + } + inst->copyManagedPack(instance); - inst->setName(instance.name()); } return did_succeed; diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index c1898dd9..2cb6e786 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -262,12 +262,20 @@ bool ModrinthCreationTask::createInstance() loop.exec(); + // Update information of the already installed instance, if any. if (m_instance && ended_well) { setAbortable(false); auto inst = m_instance.value(); + // Only change the name if it didn't use a custom name, so that the previous custom name + // is preserved, but if we're using the original one, we update the version string. + // NOTE: This needs to come before the copyManagedPack call! + if (inst->name().contains(inst->getManagedPackVersionName())) { + if (askForChangingInstanceName(m_parent, inst->name(), instance.name()) == InstanceNameChange::ShouldChange) + inst->setName(instance.name()); + } + inst->copyManagedPack(instance); - inst->setName(instance.name()); } return ended_well; From 4f6d964217f6addd4f29969168a7d40ed4729dde Mon Sep 17 00:00:00 2001 From: flow Date: Sun, 11 Sep 2022 13:24:58 -0300 Subject: [PATCH 226/273] fix: don't change groups when updating an instance Signed-off-by: flow --- launcher/InstanceList.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 47b0e75a..6b0e3a4b 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -913,9 +913,11 @@ bool InstanceList::commitStagedInstance(QString path, InstanceName const& instan qWarning() << "Failed to move" << path << "to" << destination; return false; } + + m_instanceGroupIndex[instID] = groupName; + m_groupNameCache.insert(groupName); } - m_instanceGroupIndex[instID] = groupName; - m_groupNameCache.insert(groupName); + instanceSet.insert(instID); emit instancesChanged(); From ecf5ab75e7bbac8200cd1c4f9dd446380a365583 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Tue, 20 Sep 2022 17:38:07 +0200 Subject: [PATCH 227/273] feat: support more formatting codes also fix some crashes Signed-off-by: Sefa Eyeoglu --- launcher/ui/widgets/InfoFrame.cpp | 59 +++++++++++++++++++------------ 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/launcher/ui/widgets/InfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp index f78dbe16..37c807ad 100644 --- a/launcher/ui/widgets/InfoFrame.cpp +++ b/launcher/ui/widgets/InfoFrame.cpp @@ -97,14 +97,6 @@ void InfoFrame::updateWithResource(const Resource& resource) setImage(); } -// https://www.sportskeeda.com/minecraft-wiki/color-codes -static const QMap s_value_to_color = { - {'0', "#000000"}, {'1', "#0000AA"}, {'2', "#00AA00"}, {'3', "#00AAAA"}, {'4', "#AA0000"}, - {'5', "#AA00AA"}, {'6', "#FFAA00"}, {'7', "#AAAAAA"}, {'8', "#555555"}, {'9', "#5555FF"}, - {'a', "#55FF55"}, {'b', "#55FFFF"}, {'c', "#FF5555"}, {'d', "#FF55FF"}, {'e', "#FFFF55"}, - {'f', "#FFFFFF"} -}; - QString InfoFrame::renderColorCodes(QString input) { // We have to manually set the colors for use. // @@ -113,32 +105,53 @@ QString InfoFrame::renderColorCodes(QString input) { // We traverse the description and, when one of those is found, we create // a span element with that color set. // - // TODO: Make the same logic for font formatting too. // TODO: Wrap links inside tags + // https://minecraft.fandom.com/wiki/Formatting_codes#Color_codes + const QMap color_codes_map = { + {'0', "#000000"}, {'1', "#0000AA"}, {'2', "#00AA00"}, {'3', "#00AAAA"}, {'4', "#AA0000"}, + {'5', "#AA00AA"}, {'6', "#FFAA00"}, {'7', "#AAAAAA"}, {'8', "#555555"}, {'9', "#5555FF"}, + {'a', "#55FF55"}, {'b', "#55FFFF"}, {'c', "#FF5555"}, {'d', "#FF55FF"}, {'e', "#FFFF55"}, + {'f', "#FFFFFF"} + }; + // https://minecraft.fandom.com/wiki/Formatting_codes#Formatting_codes + const QMap formatting_codes_map = { + {'l', "b"}, {'m', "s"}, {'n', "u"}, {'o', "i"} + }; + QString html(""); - bool in_div = false; + QList tags{}; auto it = input.constBegin(); while (it != input.constEnd()) { - if (*it == u'§') { - if (in_div) - html += ""; + // is current char § and is there a following char + if (*it == u'§' && (it + 1) != input.constEnd()) { + auto const& code = *(++it); // incrementing here! - auto const& num = *(++it); - html += QString("").arg(s_value_to_color.constFind(num).value()); + auto const color_entry = color_codes_map.constFind(code); + auto const tag_entry = formatting_codes_map.constFind(code); - in_div = true; - - it++; + if (color_entry != color_codes_map.constEnd()) { // color code + html += QString("").arg(color_entry.value()); + tags << "span"; + } else if (tag_entry != formatting_codes_map.constEnd()) { // formatting code + html += QString("<%1>").arg(tag_entry.value()); + tags << tag_entry.value(); + } else if (code == 'r') { // reset all formatting + while (!tags.isEmpty()) { + html += QString("").arg(tags.takeLast()); + } + } else { // pass unknown codes through + html += QString("§%1").arg(code); + } + } else { + html += *it; } - - html += *it; it++; } - - if (in_div) - html += ""; + while (!tags.isEmpty()) { + html += QString("").arg(tags.takeLast()); + } html += ""; html.replace("\n", "
"); From 777ab3416f12c620daf111fb57ac357f5fc2af46 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Tue, 20 Sep 2022 18:01:43 +0200 Subject: [PATCH 228/273] feat: also format resource/texture pack names Signed-off-by: Sefa Eyeoglu --- launcher/ui/widgets/InfoFrame.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/ui/widgets/InfoFrame.cpp b/launcher/ui/widgets/InfoFrame.cpp index 37c807ad..fdc581b4 100644 --- a/launcher/ui/widgets/InfoFrame.cpp +++ b/launcher/ui/widgets/InfoFrame.cpp @@ -160,14 +160,14 @@ QString InfoFrame::renderColorCodes(QString input) { void InfoFrame::updateWithResourcePack(ResourcePack& resource_pack) { - setName(resource_pack.name()); + setName(renderColorCodes(resource_pack.name())); setDescription(renderColorCodes(resource_pack.description())); setImage(resource_pack.image({64, 64})); } void InfoFrame::updateWithTexturePack(TexturePack& texture_pack) { - setName(texture_pack.name()); + setName(renderColorCodes(texture_pack.name())); setDescription(renderColorCodes(texture_pack.description())); setImage(texture_pack.image({64, 64})); } From 1862f3c124e090c1f27fa26babf65f14b8af8a37 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 29 Jul 2022 13:50:08 -0300 Subject: [PATCH 229/273] fix: set icon sizes correctly in ProjectItemDelegate no more dumb hacks with icons!! Signed-off-by: flow --- launcher/ui/widgets/ProjectItem.cpp | 37 ++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/launcher/ui/widgets/ProjectItem.cpp b/launcher/ui/widgets/ProjectItem.cpp index 56ae35fb..01be88d9 100644 --- a/launcher/ui/widgets/ProjectItem.cpp +++ b/launcher/ui/widgets/ProjectItem.cpp @@ -14,9 +14,7 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o QStyleOptionViewItem opt(option); initStyleOption(&opt, index); - auto& rect = opt.rect; - auto icon_width = rect.height(), icon_height = rect.height(); - auto remaining_width = rect.width() - icon_width; + auto rect = opt.rect; if (opt.state & QStyle::State_Selected) { painter->fillRect(rect, opt.palette.highlight()); @@ -25,11 +23,34 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o painter->fillRect(rect, opt.palette.window()); } - { // Icon painting - // Square-sized, occupying the left portion - opt.icon.paint(painter, rect.x(), rect.y(), icon_width, icon_height); + // The default icon size will be a square (and height is usually the lower value). + auto icon_width = rect.height(), icon_height = rect.height(); + int icon_x_margin = (rect.height() - icon_width) / 2; + int icon_y_margin = (rect.height() - icon_height) / 2; + + if (!opt.icon.isNull()) { // Icon painting + { + auto icon_size = opt.decorationSize; + icon_width = icon_size.width(); + icon_height = icon_size.height(); + + icon_x_margin = (rect.height() - icon_width) / 2; + icon_y_margin = (rect.height() - icon_height) / 2; + } + + // Centralize icon with a margin to separate from the other elements + int x = rect.x() + icon_x_margin; + int y = rect.y() + icon_y_margin; + + // Prevent 'scaling null pixmap' warnings + if (icon_width > 0 && icon_height > 0) + opt.icon.paint(painter, x, y, icon_width, icon_height); } + // Change the rect so that funther painting is easier + auto remaining_width = rect.width() - icon_width - 2 * icon_x_margin; + rect.setRect(rect.x() + icon_width + 2 * icon_x_margin, rect.y(), remaining_width, rect.height()); + { // Title painting auto title = index.data(UserDataTypes::TITLE).toString(); @@ -46,7 +67,7 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o painter->setFont(font); // On the top, aligned to the left after the icon - painter->drawText(rect.x() + icon_width, rect.y() + QFontMetrics(font).height(), title); + painter->drawText(rect.x(), rect.y() + QFontMetrics(font).height(), title); painter->restore(); } @@ -70,7 +91,7 @@ void ProjectItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& o } // On the bottom, aligned to the left after the icon, and featuring at most two lines of text (with some margin space to spare) - painter->drawText(rect.x() + icon_width, rect.y() + rect.height() - 2.2 * opt.fontMetrics.height(), remaining_width, + painter->drawText(rect.x(), rect.y() + rect.height() - 2.2 * opt.fontMetrics.height(), remaining_width, 2 * opt.fontMetrics.height(), Qt::TextWordWrap, description); } From ee4a82929365d817d64b37e0e7064bb217a3d66b Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 23 Sep 2022 16:58:25 -0300 Subject: [PATCH 230/273] fix: remove manual icon resize in ModModel THis fixes a FIXME, now that we fixed the issue :o Signed-off-by: flow --- launcher/ui/pages/modplatform/ModModel.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index 029e2be0..68dbd500 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -62,11 +62,7 @@ auto ListModel::data(const QModelIndex& index, int role) const -> QVariant } case Qt::DecorationRole: { if (m_logoMap.contains(pack.logoName)) { - auto icon = m_logoMap.value(pack.logoName); - // FIXME: This doesn't really belong here, but Qt doesn't offer a good way right now ;( - auto icon_scaled = QIcon(icon.pixmap(48, 48).scaledToWidth(48)); - - return icon_scaled; + return m_logoMap.value(pack.logoName); } QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); // un-const-ify this From 3df8594f19563cd50ac73200ee8512b7c6ceec96 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 28 Jul 2022 23:00:00 -0300 Subject: [PATCH 231/273] feat: change project item delegate for modrinth modpacks more info! \ ^-^/ Signed-off-by: flow --- .../modplatform/modrinth/ModrinthModel.cpp | 54 +++++++++++-------- .../modplatform/modrinth/ModrinthPage.cpp | 4 ++ 2 files changed, 36 insertions(+), 22 deletions(-) diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index 614be434..03b73510 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -41,6 +41,7 @@ #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" #include "ui/dialogs/ModDownloadDialog.h" +#include "ui/widgets/ProjectItem.h" #include @@ -74,31 +75,40 @@ auto ModpackListModel::data(const QModelIndex& index, int role) const -> QVarian } Modrinth::Modpack pack = modpacks.at(pos); - if (role == Qt::DisplayRole) { - return pack.name; - } else if (role == Qt::ToolTipRole) { - if (pack.description.length() > 100) { - // some magic to prevent to long tooltips and replace html linebreaks - QString edit = pack.description.left(97); - edit = edit.left(edit.lastIndexOf("
")).left(edit.lastIndexOf(" ")).append("..."); - return edit; + switch (role) { + case Qt::ToolTipRole: { + if (pack.description.length() > 100) { + // some magic to prevent to long tooltips and replace html linebreaks + QString edit = pack.description.left(97); + edit = edit.left(edit.lastIndexOf("
")).left(edit.lastIndexOf(" ")).append("..."); + return edit; + } + return pack.description; } - return pack.description; - } else if (role == Qt::DecorationRole) { - if (m_logoMap.contains(pack.iconName)) { - auto icon = m_logoMap.value(pack.iconName); - // FIXME: This doesn't really belong here, but Qt doesn't offer a good way right now ;( - auto icon_scaled = QIcon(icon.pixmap(48, 48).scaledToWidth(48)); + case Qt::DecorationRole: { + if (m_logoMap.contains(pack.iconName)) + return m_logoMap.value(pack.iconName); - return icon_scaled; + QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); + ((ModpackListModel*)this)->requestLogo(pack.iconName, pack.iconUrl.toString()); + return icon; } - QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); - ((ModpackListModel*)this)->requestLogo(pack.iconName, pack.iconUrl.toString()); - return icon; - } else if (role == Qt::UserRole) { - QVariant v; - v.setValue(pack); - return v; + case Qt::UserRole: { + QVariant v; + v.setValue(pack); + return v; + } + case Qt::SizeHintRole: + return QSize(0, 58); + // Custom data + case UserDataTypes::TITLE: + return pack.name; + case UserDataTypes::DESCRIPTION: + return pack.description; + case UserDataTypes::SELECTED: + return false; + default: + break; } return {}; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index df29c0c3..8ee9bff5 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -43,6 +43,8 @@ #include "InstanceImportTask.h" #include "Json.h" +#include "ui/widgets/ProjectItem.h" + #include #include @@ -70,6 +72,8 @@ ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthPage::onSelectionChanged); connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthPage::onVersionSelectionChanged); + + ui->packView->setItemDelegate(new ProjectItemDelegate(this)); } ModrinthPage::~ModrinthPage() From e7380e70a3a615b69da863918bec58e41b82cdd1 Mon Sep 17 00:00:00 2001 From: flow Date: Fri, 23 Sep 2022 18:05:58 -0300 Subject: [PATCH 232/273] fix: use placeholder icon when the project has no icon in MR Projects with no icon return a null icon URL in Modrinth's API. Signed-off-by: flow --- launcher/ui/pages/modplatform/ModModel.cpp | 2 +- launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/ui/pages/modplatform/ModModel.cpp b/launcher/ui/pages/modplatform/ModModel.cpp index 68dbd500..8961fadd 100644 --- a/launcher/ui/pages/modplatform/ModModel.cpp +++ b/launcher/ui/pages/modplatform/ModModel.cpp @@ -171,7 +171,7 @@ void ListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallbac void ListModel::requestLogo(QString logo, QString url) { - if (m_loadingLogos.contains(logo) || m_failedLogos.contains(logo)) { + if (m_loadingLogos.contains(logo) || m_failedLogos.contains(logo) || url.isEmpty()) { return; } diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index 03b73510..fd7a3537 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -227,7 +227,7 @@ void ModpackListModel::getLogo(const QString& logo, const QString& logoUrl, Logo void ModpackListModel::requestLogo(QString logo, QString url) { - if (m_loadingLogos.contains(logo) || m_failedLogos.contains(logo)) { + if (m_loadingLogos.contains(logo) || m_failedLogos.contains(logo) || url.isEmpty()) { return; } From 600c49f7f0482e6ca5cf977a99c225ccd97c4ae2 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sat, 24 Sep 2022 00:06:36 +0300 Subject: [PATCH 233/273] Replaced tomlc99 with tomlplusplus Signed-off-by: Trial97 --- .gitmodules | 4 + CMakeLists.txt | 2 +- COPYING.md | 30 +- launcher/CMakeLists.txt | 2 +- .../minecraft/mod/tasks/LocalModParseTask.cpp | 268 +- launcher/modplatform/packwiz/Packwiz.cpp | 156 +- libraries/README.md | 16 +- libraries/tomlc99/CMakeLists.txt | 10 - libraries/tomlc99/LICENSE | 22 - libraries/tomlc99/README.md | 197 -- libraries/tomlc99/include/toml.h | 175 -- libraries/tomlc99/src/toml.c | 2300 ----------------- libraries/tomlplusplus | 1 + 13 files changed, 194 insertions(+), 2989 deletions(-) delete mode 100644 libraries/tomlc99/CMakeLists.txt delete mode 100644 libraries/tomlc99/LICENSE delete mode 100644 libraries/tomlc99/README.md delete mode 100644 libraries/tomlc99/include/toml.h delete mode 100644 libraries/tomlc99/src/toml.c create mode 160000 libraries/tomlplusplus diff --git a/.gitmodules b/.gitmodules index 08b94c96..b29a0477 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,3 +6,7 @@ [submodule "libraries/quazip"] path = libraries/quazip url = https://github.com/stachenov/quazip.git +[submodule "libraries/tomlplusplus"] + path = libraries/tomlplusplus + url = https://github.com/marzer/tomlplusplus.git + shallow = true diff --git a/CMakeLists.txt b/CMakeLists.txt index 7100ab1b..364e16d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -312,7 +312,7 @@ endif() add_subdirectory(libraries/rainbow) # Qt extension for colors add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions add_subdirectory(libraries/classparser) # class parser library -add_subdirectory(libraries/tomlc99) # toml parser +add_subdirectory(libraries/tomlplusplus) # toml parser add_subdirectory(libraries/katabasis) # An OAuth2 library that tried to do too much add_subdirectory(libraries/gamemode) add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API diff --git a/COPYING.md b/COPYING.md index c94c51c3..11c1fc3a 100644 --- a/COPYING.md +++ b/COPYING.md @@ -315,30 +315,24 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -## tomlc99 +## tomlplusplus MIT License - Copyright (c) 2017 CK Tan - https://github.com/cktan/tomlc99 + Copyright (c) Mark Gillard - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: + Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated + documentation files (the "Software"), to deal in the Software without restriction, including without limitation the + rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. + The above copyright notice and this permission notice shall be included in all copies or substantial portions of the + Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ## O2 (Katabasis fork) diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index 848d2e51..8fdbec5b 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -965,7 +965,7 @@ target_link_libraries(Launcher_logic Launcher_murmur2 nbt++ ${ZLIB_LIBRARIES} - tomlc99 + tomlplusplus::tomlplusplus BuildConfig Katabasis Qt${QT_VERSION_MAJOR}::Widgets diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp index 8a6e54d8..ae8e95ff 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp @@ -1,17 +1,17 @@ #include "LocalModParseTask.h" -#include -#include -#include -#include -#include #include #include -#include +#include +#include +#include +#include +#include +#include +#include "FileSystem.h" #include "Json.h" #include "settings/INIFile.h" -#include "FileSystem.h" namespace { @@ -22,8 +22,7 @@ namespace { // https://github.com/MinecraftForge/FML/wiki/FML-mod-information-file/5bf6a2d05145ec79387acc0d45c958642fb049fc ModDetails ReadMCModInfo(QByteArray contents) { - auto getInfoFromArray = [&](QJsonArray arr) -> ModDetails - { + auto getInfoFromArray = [&](QJsonArray arr) -> ModDetails { if (!arr.at(0).isObject()) { return {}; } @@ -32,16 +31,14 @@ ModDetails ReadMCModInfo(QByteArray contents) details.mod_id = firstObj.value("modid").toString(); auto name = firstObj.value("name").toString(); // NOTE: ignore stupid example mods copies where the author didn't even bother to change the name - if(name != "Example Mod") { + if (name != "Example Mod") { details.name = name; } details.version = firstObj.value("version").toString(); auto homeurl = firstObj.value("url").toString().trimmed(); - if(!homeurl.isEmpty()) - { + if (!homeurl.isEmpty()) { // fix up url. - if (!homeurl.startsWith("http://") && !homeurl.startsWith("https://") && !homeurl.startsWith("ftp://")) - { + if (!homeurl.startsWith("http://") && !homeurl.startsWith("https://") && !homeurl.startsWith("ftp://")) { homeurl.prepend("http://"); } } @@ -53,8 +50,7 @@ ModDetails ReadMCModInfo(QByteArray contents) authors = firstObj.value("authors").toArray(); } - for (auto author: authors) - { + for (auto author : authors) { details.authors.append(author.toString()); } return details; @@ -62,14 +58,11 @@ ModDetails ReadMCModInfo(QByteArray contents) QJsonParseError jsonError; QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError); // this is the very old format that had just the array - if (jsonDoc.isArray()) - { + if (jsonDoc.isArray()) { return getInfoFromArray(jsonDoc.array()); - } - else if (jsonDoc.isObject()) - { + } else if (jsonDoc.isObject()) { auto val = jsonDoc.object().value("modinfoversion"); - if(val.isUndefined()) { + if (val.isUndefined()) { val = jsonDoc.object().value("modListVersion"); } @@ -79,18 +72,16 @@ ModDetails ReadMCModInfo(QByteArray contents) if (version < 0) version = Json::ensureString(val, "").toInt(); - if (version != 2) - { + if (version != 2) { qCritical() << "BAD stuff happened to mod json:"; qCritical() << contents; return {}; } auto arrVal = jsonDoc.object().value("modlist"); - if(arrVal.isUndefined()) { + if (arrVal.isUndefined()) { arrVal = jsonDoc.object().value("modList"); } - if (arrVal.isArray()) - { + if (arrVal.isArray()) { return getInfoFromArray(arrVal.toArray()); } } @@ -102,109 +93,77 @@ ModDetails ReadMCModTOML(QByteArray contents) { ModDetails details; - char errbuf[200]; - // top-level table - toml_table_t* tomlData = toml_parse(contents.data(), errbuf, sizeof(errbuf)); - - if(!tomlData) - { + // auto tomlData = toml::parse(contents.toStdString()); + toml::table tomlData; +#if TOML_EXCEPTIONS + try { + tomlData = toml::parse(contents.toStdString()); + } catch (const toml::parse_error& err) { return {}; } +#else + tomlData = toml::parse(contents.toStdString()); + if (!tomlData) { + return {}; + } +#endif // array defined by [[mods]] - toml_array_t* tomlModsArr = toml_array_in(tomlData, "mods"); - if(!tomlModsArr) - { + auto tomlModsArr = tomlData["mods"].as_array(); + if (!tomlModsArr) { qWarning() << "Corrupted mods.toml? Couldn't find [[mods]] array!"; return {}; } // we only really care about the first element, since multiple mods in one file is not supported by us at the moment - toml_table_t* tomlModsTable0 = toml_table_at(tomlModsArr, 0); - if(!tomlModsTable0) - { + auto tomlModsTable0 = tomlModsArr->get(0); + if (!tomlModsTable0) { qWarning() << "Corrupted mods.toml? [[mods]] didn't have an element at index 0!"; return {}; } + auto modsTable = tomlModsTable0->as_table(); + if (!tomlModsTable0) { + qWarning() << "Corrupted mods.toml? [[mods]] was not a table!"; + return {}; + } // mandatory properties - always in [[mods]] - toml_datum_t modIdDatum = toml_string_in(tomlModsTable0, "modId"); - if(modIdDatum.ok) - { - details.mod_id = modIdDatum.u.s; - // library says this is required for strings - free(modIdDatum.u.s); + if (auto modIdDatum = (*modsTable)["modId"].as_string()) { + details.mod_id = QString::fromStdString(modIdDatum->get()); } - toml_datum_t versionDatum = toml_string_in(tomlModsTable0, "version"); - if(versionDatum.ok) - { - details.version = versionDatum.u.s; - free(versionDatum.u.s); + if (auto versionDatum = (*modsTable)["version"].as_string()) { + details.version = QString::fromStdString(versionDatum->get()); } - toml_datum_t displayNameDatum = toml_string_in(tomlModsTable0, "displayName"); - if(displayNameDatum.ok) - { - details.name = displayNameDatum.u.s; - free(displayNameDatum.u.s); + if (auto displayNameDatum = (*modsTable)["displayName"].as_string()) { + details.name = QString::fromStdString(displayNameDatum->get()); } - toml_datum_t descriptionDatum = toml_string_in(tomlModsTable0, "description"); - if(descriptionDatum.ok) - { - details.description = descriptionDatum.u.s; - free(descriptionDatum.u.s); + if (auto descriptionDatum = (*modsTable)["description"].as_string()) { + details.description = QString::fromStdString(descriptionDatum->get()); } // optional properties - can be in the root table or [[mods]] - toml_datum_t authorsDatum = toml_string_in(tomlData, "authors"); QString authors = ""; - if(authorsDatum.ok) - { - authors = authorsDatum.u.s; - free(authorsDatum.u.s); + if (auto authorsDatum = tomlData["authors"].as_string()) { + authors = QString::fromStdString(authorsDatum->get()); + } else if (auto authorsDatum = (*modsTable)["authors"].as_string()) { + authors = QString::fromStdString(authorsDatum->get()); } - else - { - authorsDatum = toml_string_in(tomlModsTable0, "authors"); - if(authorsDatum.ok) - { - authors = authorsDatum.u.s; - free(authorsDatum.u.s); - } - } - if(!authors.isEmpty()) - { + if (!authors.isEmpty()) { details.authors.append(authors); } - toml_datum_t homeurlDatum = toml_string_in(tomlData, "displayURL"); QString homeurl = ""; - if(homeurlDatum.ok) - { - homeurl = homeurlDatum.u.s; - free(homeurlDatum.u.s); + if (auto homeurlDatum = tomlData["displayURL"].as_string()) { + homeurl = QString::fromStdString(homeurlDatum->get()); + } else if (auto homeurlDatum = (*modsTable)["displayURL"].as_string()) { + homeurl = QString::fromStdString(homeurlDatum->get()); } - else - { - homeurlDatum = toml_string_in(tomlModsTable0, "displayURL"); - if(homeurlDatum.ok) - { - homeurl = homeurlDatum.u.s; - free(homeurlDatum.u.s); - } - } - if(!homeurl.isEmpty()) - { - // fix up url. - if (!homeurl.startsWith("http://") && !homeurl.startsWith("https://") && !homeurl.startsWith("ftp://")) - { - homeurl.prepend("http://"); - } + // fix up url. + if (!homeurl.isEmpty() && !homeurl.startsWith("http://") && !homeurl.startsWith("https://") && !homeurl.startsWith("ftp://")) { + homeurl.prepend("http://"); } details.homeurl = homeurl; - // this seems to be recursive, so it should free everything - toml_free(tomlData); - return details; } @@ -224,25 +183,20 @@ ModDetails ReadFabricModInfo(QByteArray contents) details.name = object.contains("name") ? object.value("name").toString() : details.mod_id; details.description = object.value("description").toString(); - if (schemaVersion >= 1) - { + if (schemaVersion >= 1) { QJsonArray authors = object.value("authors").toArray(); - for (auto author: authors) - { - if(author.isObject()) { + for (auto author : authors) { + if (author.isObject()) { details.authors.append(author.toObject().value("name").toString()); - } - else { + } else { details.authors.append(author.toString()); } } - if (object.contains("contact")) - { + if (object.contains("contact")) { QJsonObject contact = object.value("contact").toObject(); - if (contact.contains("homepage")) - { + if (contact.contains("homepage")) { details.homeurl = contact.value("homepage").toString(); } } @@ -261,8 +215,7 @@ ModDetails ReadQuiltModInfo(QByteArray contents) ModDetails details; // https://github.com/QuiltMC/rfcs/blob/be6ba280d785395fefa90a43db48e5bfc1d15eb4/specification/0002-quilt.mod.json.md - if (schemaVersion == 1) - { + if (schemaVersion == 1) { auto modInfo = Json::requireObject(object.value("quilt_loader"), "Quilt mod info"); details.mod_id = Json::requireString(modInfo.value("id"), "Mod ID"); @@ -280,8 +233,7 @@ ModDetails ReadQuiltModInfo(QByteArray contents) auto modContact = Json::ensureObject(modMetadata.value("contact")); - if (modContact.contains("homepage")) - { + if (modContact.contains("homepage")) { details.homeurl = Json::requireString(modContact.value("homepage")); } } @@ -314,21 +266,17 @@ ModDetails ReadLiteModInfo(QByteArray contents) QJsonParseError jsonError; QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError); auto object = jsonDoc.object(); - if (object.contains("name")) - { + if (object.contains("name")) { details.mod_id = details.name = object.value("name").toString(); } - if (object.contains("version")) - { + if (object.contains("version")) { details.version = object.value("version").toString(""); - } - else - { + } else { details.version = object.value("revision").toString(""); } details.mcversion = object.value("mcversion").toString(); auto author = object.value("author").toString(); - if(!author.isEmpty()) { + if (!author.isEmpty()) { details.authors.append(author); } details.description = object.value("description").toString(); @@ -336,14 +284,10 @@ ModDetails ReadLiteModInfo(QByteArray contents) return details; } -} +} // namespace -LocalModParseTask::LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile): - Task(nullptr, false), - m_token(token), - m_type(type), - m_modFile(modFile), - m_result(new Result()) +LocalModParseTask::LocalModParseTask(int token, ResourceType type, const QFileInfo& modFile) + : Task(nullptr, false), m_token(token), m_type(type), m_modFile(modFile), m_result(new Result()) {} void LocalModParseTask::processAsZip() @@ -354,10 +298,8 @@ void LocalModParseTask::processAsZip() QuaZipFile file(&zip); - if (zip.setCurrentFile("META-INF/mods.toml")) - { - if (!file.open(QIODevice::ReadOnly)) - { + if (zip.setCurrentFile("META-INF/mods.toml")) { + if (!file.open(QIODevice::ReadOnly)) { zip.close(); return; } @@ -366,12 +308,9 @@ void LocalModParseTask::processAsZip() file.close(); // to replace ${file.jarVersion} with the actual version, as needed - if (m_result->details.version == "${file.jarVersion}") - { - if (zip.setCurrentFile("META-INF/MANIFEST.MF")) - { - if (!file.open(QIODevice::ReadOnly)) - { + if (m_result->details.version == "${file.jarVersion}") { + if (zip.setCurrentFile("META-INF/MANIFEST.MF")) { + if (!file.open(QIODevice::ReadOnly)) { zip.close(); return; } @@ -379,10 +318,8 @@ void LocalModParseTask::processAsZip() // quick and dirty line-by-line parser auto manifestLines = file.readAll().split('\n'); QString manifestVersion = ""; - for (auto &line : manifestLines) - { - if (QString(line).startsWith("Implementation-Version: ")) - { + for (auto& line : manifestLines) { + if (QString(line).startsWith("Implementation-Version: ")) { manifestVersion = QString(line).remove("Implementation-Version: "); break; } @@ -390,8 +327,7 @@ void LocalModParseTask::processAsZip() // some mods use ${projectversion} in their build.gradle, causing this mess to show up in MANIFEST.MF // also keep with forge's behavior of setting the version to "NONE" if none is found - if (manifestVersion.contains("task ':jar' property 'archiveVersion'") || manifestVersion == "") - { + if (manifestVersion.contains("task ':jar' property 'archiveVersion'") || manifestVersion == "") { manifestVersion = "NONE"; } @@ -403,11 +339,8 @@ void LocalModParseTask::processAsZip() zip.close(); return; - } - else if (zip.setCurrentFile("mcmod.info")) - { - if (!file.open(QIODevice::ReadOnly)) - { + } else if (zip.setCurrentFile("mcmod.info")) { + if (!file.open(QIODevice::ReadOnly)) { zip.close(); return; } @@ -416,11 +349,8 @@ void LocalModParseTask::processAsZip() file.close(); zip.close(); return; - } - else if (zip.setCurrentFile("quilt.mod.json")) - { - if (!file.open(QIODevice::ReadOnly)) - { + } else if (zip.setCurrentFile("quilt.mod.json")) { + if (!file.open(QIODevice::ReadOnly)) { zip.close(); return; } @@ -429,11 +359,8 @@ void LocalModParseTask::processAsZip() file.close(); zip.close(); return; - } - else if (zip.setCurrentFile("fabric.mod.json")) - { - if (!file.open(QIODevice::ReadOnly)) - { + } else if (zip.setCurrentFile("fabric.mod.json")) { + if (!file.open(QIODevice::ReadOnly)) { zip.close(); return; } @@ -442,11 +369,8 @@ void LocalModParseTask::processAsZip() file.close(); zip.close(); return; - } - else if (zip.setCurrentFile("forgeversion.properties")) - { - if (!file.open(QIODevice::ReadOnly)) - { + } else if (zip.setCurrentFile("forgeversion.properties")) { + if (!file.open(QIODevice::ReadOnly)) { zip.close(); return; } @@ -463,8 +387,7 @@ void LocalModParseTask::processAsZip() void LocalModParseTask::processAsFolder() { QFileInfo mcmod_info(FS::PathCombine(m_modFile.filePath(), "mcmod.info")); - if (mcmod_info.isFile()) - { + if (mcmod_info.isFile()) { QFile mcmod(mcmod_info.filePath()); if (!mcmod.open(QIODevice::ReadOnly)) return; @@ -483,10 +406,8 @@ void LocalModParseTask::processAsLitemod() QuaZipFile file(&zip); - if (zip.setCurrentFile("litemod.json")) - { - if (!file.open(QIODevice::ReadOnly)) - { + if (zip.setCurrentFile("litemod.json")) { + if (!file.open(QIODevice::ReadOnly)) { zip.close(); return; } @@ -505,8 +426,7 @@ bool LocalModParseTask::abort() void LocalModParseTask::executeTask() { - switch(m_type) - { + switch (m_type) { case ResourceType::ZIPFILE: processAsZip(); break; diff --git a/launcher/modplatform/packwiz/Packwiz.cpp b/launcher/modplatform/packwiz/Packwiz.cpp index c3561093..b1fe963e 100644 --- a/launcher/modplatform/packwiz/Packwiz.cpp +++ b/launcher/modplatform/packwiz/Packwiz.cpp @@ -1,20 +1,20 @@ // SPDX-License-Identifier: GPL-3.0-only /* -* PolyMC - Minecraft Launcher -* Copyright (c) 2022 flowln -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, version 3. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see . -*/ + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ #include "Packwiz.h" @@ -22,9 +22,7 @@ #include #include -#include "toml.h" -#include "FileSystem.h" - +#include #include "minecraft/mod/Mod.h" #include "modplatform/ModIndex.h" @@ -44,7 +42,7 @@ auto getRealIndexName(QDir& index_dir, QString normalized_fname, bool should_fin } } - if(should_find_match && !QString::compare(normalized_fname, real_fname, Qt::CaseSensitive)){ + if (should_find_match && !QString::compare(normalized_fname, real_fname, Qt::CaseSensitive)) { qCritical() << "Could not find a match for a valid metadata file!"; qCritical() << "File: " << normalized_fname; return {}; @@ -57,7 +55,7 @@ auto getRealIndexName(QDir& index_dir, QString normalized_fname, bool should_fin // Helpers static inline auto indexFileName(QString const& mod_slug) -> QString { - if(mod_slug.endsWith(".pw.toml")) + if (mod_slug.endsWith(".pw.toml")) return mod_slug; return QString("%1.pw.toml").arg(mod_slug); } @@ -65,32 +63,28 @@ 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_t* parent, const char* entry_name) -> QString +auto stringEntry(toml::table table, const std::string entry_name) -> QString { - toml_datum_t var = toml_string_in(parent, entry_name); - if (!var.ok) { - qCritical() << QString("Failed to read str property '%1' in mod metadata.").arg(entry_name); + auto node = table[entry_name]; + if (!node) { + qCritical() << QString::fromStdString("Failed to read str property '" + entry_name + "' in mod metadata."); return {}; } - QString tmp = var.u.s; - free(var.u.s); - - return tmp; + return QString::fromStdString(node.value_or("")); } -auto intEntry(toml_table_t* parent, const char* entry_name) -> int +auto intEntry(toml::table table, const std::string entry_name) -> int { - toml_datum_t var = toml_int_in(parent, entry_name); - if (!var.ok) { - qCritical() << QString("Failed to read int property '%1' in mod metadata.").arg(entry_name); + auto node = table[entry_name]; + if (!node) { + qCritical() << QString::fromStdString("Failed to read int property '" + entry_name + "' in mod metadata."); return {}; } - return var.u.i; + return node.value_or(0); } - auto V1::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, ModPlatform::IndexedVersion& mod_version) -> Mod { Mod mod; @@ -99,10 +93,9 @@ auto V1::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, Mo mod.name = mod_pack.name; mod.filename = mod_version.fileName; - if(mod_pack.provider == ModPlatform::Provider::FLAME){ + if (mod_pack.provider == ModPlatform::Provider::FLAME) { mod.mode = "metadata:curseforge"; - } - else { + } else { mod.mode = "url"; mod.url = mod_version.downloadUrl; } @@ -120,8 +113,8 @@ auto V1::createModFormat(QDir& index_dir, ModPlatform::IndexedPack& mod_pack, Mo auto V1::createModFormat(QDir& index_dir, ::Mod& internal_mod, QString slug) -> Mod { // Try getting metadata if it exists - Mod mod { getIndexForMod(index_dir, slug) }; - if(mod.isValid()) + Mod mod{ getIndexForMod(index_dir, slug) }; + if (mod.isValid()) return mod; qWarning() << QString("Tried to create mod metadata with a Mod without metadata!"); @@ -131,7 +124,7 @@ auto V1::createModFormat(QDir& index_dir, ::Mod& internal_mod, QString slug) -> void V1::updateModIndex(QDir& index_dir, Mod& mod) { - if(!mod.isValid()){ + if (!mod.isValid()) { qCritical() << QString("Tried to update metadata of an invalid mod!"); return; } @@ -150,7 +143,9 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod) // TODO: We should do more stuff here, as the user is likely trying to // override a file. In this case, check versions and ask the user what // they want to do! - if (index_file.exists()) { index_file.remove(); } + if (index_file.exists()) { + index_file.remove(); + } if (!index_file.open(QIODevice::ReadWrite)) { qCritical() << QString("Could not open file %1!").arg(indexFileName(mod.name)); @@ -174,15 +169,15 @@ void V1::updateModIndex(QDir& index_dir, Mod& mod) in_stream << QString("\n[update]\n"); in_stream << QString("[update.%1]\n").arg(ProviderCaps.name(mod.provider)); - switch(mod.provider){ - case(ModPlatform::Provider::FLAME): - in_stream << QString("file-id = %1\n").arg(mod.file_id.toString()); - in_stream << QString("project-id = %1\n").arg(mod.project_id.toString()); - break; - case(ModPlatform::Provider::MODRINTH): - addToStream("mod-id", mod.mod_id().toString()); - addToStream("version", mod.version().toString()); - break; + switch (mod.provider) { + case (ModPlatform::Provider::FLAME): + in_stream << QString("file-id = %1\n").arg(mod.file_id.toString()); + in_stream << QString("project-id = %1\n").arg(mod.project_id.toString()); + break; + case (ModPlatform::Provider::MODRINTH): + addToStream("mod-id", mod.mod_id().toString()); + addToStream("version", mod.version().toString()); + break; } } @@ -230,27 +225,25 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod if (real_fname.isEmpty()) return {}; - QFile index_file(index_dir.absoluteFilePath(real_fname)); - - if (!index_file.open(QIODevice::ReadOnly)) { - qWarning() << QString("Failed to open mod metadata for %1").arg(slug); + toml::table table; +#if TOML_EXCEPTIONS + try { + table = toml::parse_file(index_dir.absoluteFilePath(real_fname).toStdString()); + } catch (const toml::parse_error& err) { + qWarning() << QString("Could not open file %1!").arg(normalized_fname); + qWarning() << "Reason: " << QString(err.what()); return {}; } - - toml_table_t* table = nullptr; - - // NOLINTNEXTLINE(modernize-avoid-c-arrays) - char errbuf[200]; - auto file_bytearray = index_file.readAll(); - table = toml_parse(file_bytearray.data(), errbuf, sizeof(errbuf)); - - index_file.close(); - +#else + table = toml::parse_file(index_dir.absoluteFilePath(real_fname).toStdString()); if (!table) { qWarning() << QString("Could not open file %1!").arg(normalized_fname); - qWarning() << "Reason: " << QString(errbuf); + qWarning() << "Reason: " << QString(table.error().what()); return {}; } +#endif + + // index_file.close(); mod.slug = slug; @@ -261,45 +254,42 @@ auto V1::getIndexForMod(QDir& index_dir, QString slug) -> Mod } { // [download] info - toml_table_t* download_table = toml_table_in(table, "download"); + auto download_table = table["download"].as_table(); if (!download_table) { qCritical() << QString("No [download] section found on mod metadata!"); return {}; } - mod.mode = stringEntry(download_table, "mode"); - mod.url = stringEntry(download_table, "url"); - mod.hash_format = stringEntry(download_table, "hash-format"); - mod.hash = stringEntry(download_table, "hash"); + mod.mode = stringEntry(*download_table, "mode"); + mod.url = stringEntry(*download_table, "url"); + mod.hash_format = stringEntry(*download_table, "hash-format"); + mod.hash = stringEntry(*download_table, "hash"); } - { // [update] info + { // [update] info using Provider = ModPlatform::Provider; - toml_table_t* update_table = toml_table_in(table, "update"); - if (!update_table) { + auto update_table = table["update"]; + if (!update_table || !update_table.is_table()) { qCritical() << QString("No [update] section found on mod metadata!"); return {}; } - toml_table_t* mod_provider_table = nullptr; - if ((mod_provider_table = toml_table_in(update_table, ProviderCaps.name(Provider::FLAME)))) { + toml::table* mod_provider_table = nullptr; + if ((mod_provider_table = update_table[ProviderCaps.name(Provider::FLAME)].as_table())) { mod.provider = Provider::FLAME; - mod.file_id = intEntry(mod_provider_table, "file-id"); - mod.project_id = intEntry(mod_provider_table, "project-id"); - } else if ((mod_provider_table = toml_table_in(update_table, ProviderCaps.name(Provider::MODRINTH)))) { + mod.file_id = intEntry(*mod_provider_table, "file-id"); + mod.project_id = intEntry(*mod_provider_table, "project-id"); + } else if ((mod_provider_table = update_table[ProviderCaps.name(Provider::MODRINTH)].as_table())) { mod.provider = Provider::MODRINTH; - mod.mod_id() = stringEntry(mod_provider_table, "mod-id"); - mod.version() = stringEntry(mod_provider_table, "version"); + mod.mod_id() = stringEntry(*mod_provider_table, "mod-id"); + mod.version() = stringEntry(*mod_provider_table, "version"); } else { qCritical() << QString("No mod provider on mod metadata!"); return {}; } - } - toml_free(table); - return mod; } diff --git a/libraries/README.md b/libraries/README.md index 8e4bd61b..6297cd9b 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -44,17 +44,17 @@ Java launcher part for Minecraft. It: -* Starts a process -* Waits for a launch script on stdin -* Consumes the launch script you feed it -* Proceeds with launch when it gets the `launcher` command +- Starts a process +- Waits for a launch script on stdin +- Consumes the launch script you feed it +- Proceeds with launch when it gets the `launcher` command This means the process is essentially idle until the final command is sent. You can, for example, attach a profiler before you send it. A `legacy` and `onesix` launchers are available. -* `legacy` is intended for use with Minecraft versions < 1.6 and is deprecated. -* `onesix` can handle launching any Minecraft version, at the cost of some extra features `legacy` enables (custom window icon and title). +- `legacy` is intended for use with Minecraft versions < 1.6 and is deprecated. +- `onesix` can handle launching any Minecraft version, at the cost of some extra features `legacy` enables (custom window icon and title). Example (some parts have been censored): @@ -177,11 +177,11 @@ A PolyMC-specific library for probing system information. Apache 2.0 -## tomlc99 +## tomlplusplus A TOML language parser. Used by Forge 1.14+ to store mod metadata. -See [github repo](https://github.com/cktan/tomlc99). +See [github repo](https://github.com/marzer/tomlplusplus). Licenced under the MIT licence. diff --git a/libraries/tomlc99/CMakeLists.txt b/libraries/tomlc99/CMakeLists.txt deleted file mode 100644 index 60786923..00000000 --- a/libraries/tomlc99/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -project(tomlc99) - -set(tomlc99_SOURCES -include/toml.h -src/toml.c -) - -add_library(tomlc99 STATIC ${tomlc99_SOURCES}) - -target_include_directories(tomlc99 PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) diff --git a/libraries/tomlc99/LICENSE b/libraries/tomlc99/LICENSE deleted file mode 100644 index a3292b16..00000000 --- a/libraries/tomlc99/LICENSE +++ /dev/null @@ -1,22 +0,0 @@ -MIT License - -Copyright (c) 2017 CK Tan -https://github.com/cktan/tomlc99 - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/libraries/tomlc99/README.md b/libraries/tomlc99/README.md deleted file mode 100644 index e5fe9480..00000000 --- a/libraries/tomlc99/README.md +++ /dev/null @@ -1,197 +0,0 @@ -# tomlc99 - -TOML in c99; v1.0 compliant. - -If you are looking for a C++ library, you might try this wrapper: [https://github.com/cktan/tomlcpp](https://github.com/cktan/tomlcpp). - -* Compatible with [TOML v1.0.0](https://toml.io/en/v1.0.0). -* Tested with multiple test suites, including -[BurntSushi/toml-test](https://github.com/BurntSushi/toml-test) and -[iarna/toml-spec-tests](https://github.com/iarna/toml-spec-tests). -* Provides very simple and intuitive interface. - -## Usage - -Please see the `toml.h` file for details. What follows is a simple example that -parses this config file: - -```toml -[server] - host = "www.example.com" - port = [ 8080, 8181, 8282 ] -``` - -The steps for getting values from our file is usually : - -1. Parse the TOML file. -2. Traverse and locate a table in TOML. -3. Extract values from the table. -4. Free up allocated memory. - -Below is an example of parsing the values from the example table. - -```c -#include -#include -#include -#include -#include "toml.h" - -static void error(const char* msg, const char* msg1) -{ - fprintf(stderr, "ERROR: %s%s\n", msg, msg1?msg1:""); - exit(1); -} - - -int main() -{ - FILE* fp; - char errbuf[200]; - - // 1. Read and parse toml file - fp = fopen("sample.toml", "r"); - if (!fp) { - error("cannot open sample.toml - ", strerror(errno)); - } - - toml_table_t* conf = toml_parse_file(fp, errbuf, sizeof(errbuf)); - fclose(fp); - - if (!conf) { - error("cannot parse - ", errbuf); - } - - // 2. Traverse to a table. - toml_table_t* server = toml_table_in(conf, "server"); - if (!server) { - error("missing [server]", ""); - } - - // 3. Extract values - toml_datum_t host = toml_string_in(server, "host"); - if (!host.ok) { - error("cannot read server.host", ""); - } - - toml_array_t* portarray = toml_array_in(server, "port"); - if (!portarray) { - error("cannot read server.port", ""); - } - - printf("host: %s\n", host.u.s); - printf("port: "); - for (int i = 0; ; i++) { - toml_datum_t port = toml_int_at(portarray, i); - if (!port.ok) break; - printf("%d ", (int)port.u.i); - } - printf("\n"); - - // 4. Free memory - free(host.u.s); - toml_free(conf); - return 0; -} -``` - -### Accessing Table Content - -TOML tables are dictionaries where lookups are done using string keys. In -general, all access functions on tables are named `toml_*_in(...)`. - -In the normal case, you know the key and its content type, and retrievals can be done -using one of these functions: - -```c -toml_string_in(tab, key); -toml_bool_in(tab, key); -toml_int_in(tab, key); -toml_double_in(tab, key); -toml_timestamp_in(tab, key); -toml_table_in(tab, key); -toml_array_in(tab, key); -``` - -You can also interrogate the keys in a table using an integer index: - -```c -toml_table_t* tab = toml_parse_file(...); -for (int i = 0; ; i++) { - const char* key = toml_key_in(tab, i); - if (!key) break; - printf("key %d: %s\n", i, key); -} -``` - -### Accessing Array Content - -TOML arrays can be deref-ed using integer indices. In general, all access methods on arrays are named `toml_*_at()`. - -To obtain the size of an array: - -```c -int size = toml_array_nelem(arr); -``` - -To obtain the content of an array, use a valid index and call one of these functions: - -```c -toml_string_at(arr, idx); -toml_bool_at(arr, idx); -toml_int_at(arr, idx); -toml_double_at(arr, idx); -toml_timestamp_at(arr, idx); -toml_table_at(arr, idx); -toml_array_at(arr, idx); -``` - -### toml_datum_t - -Some `toml_*_at` and `toml_*_in` functions return a toml_datum_t -structure. The `ok` flag in the structure indicates if the function -call was successful. If so, you may proceed to read the value -corresponding to the type of the content. - -For example: - -```c -toml_datum_t host = toml_string_in(tab, "host"); -if (host.ok) { - printf("host: %s\n", host.u.s); - free(host.u.s); /* FREE applies to string and timestamp types only */ -} -``` - -**IMPORTANT: if the accessed value is a string or a timestamp, you must call `free(datum.u.s)` or `free(datum.u.ts)` respectively after usage.** - -## Building and installing - -A normal *make* suffices. You can also simply include the -`toml.c` and `toml.h` files in your project. - -Invoking `make install` will install the header and library files into -/usr/local/{include,lib}. - -Alternatively, specify `make install prefix=/a/file/path` to install into -/a/file/path/{include,lib}. - -## Testing - -To test against the standard test set provided by BurntSushi/toml-test: - -```sh -% make -% cd test1 -% bash build.sh # do this once -% bash run.sh # this will run the test suite -``` - -To test against the standard test set provided by iarna/toml: - -```sh -% make -% cd test2 -% bash build.sh # do this once -% bash run.sh # this will run the test suite -``` diff --git a/libraries/tomlc99/include/toml.h b/libraries/tomlc99/include/toml.h deleted file mode 100644 index b91ef890..00000000 --- a/libraries/tomlc99/include/toml.h +++ /dev/null @@ -1,175 +0,0 @@ -/* - MIT License - - Copyright (c) 2017 - 2019 CK Tan - https://github.com/cktan/tomlc99 - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. -*/ -#ifndef TOML_H -#define TOML_H - - -#include -#include - - -#ifdef __cplusplus -#define TOML_EXTERN extern "C" -#else -#define TOML_EXTERN extern -#endif - -typedef struct toml_timestamp_t toml_timestamp_t; -typedef struct toml_table_t toml_table_t; -typedef struct toml_array_t toml_array_t; -typedef struct toml_datum_t toml_datum_t; - -/* Parse a file. Return a table on success, or 0 otherwise. - * Caller must toml_free(the-return-value) after use. - */ -TOML_EXTERN toml_table_t* toml_parse_file(FILE* fp, - char* errbuf, - int errbufsz); - -/* Parse a string containing the full config. - * Return a table on success, or 0 otherwise. - * Caller must toml_free(the-return-value) after use. - */ -TOML_EXTERN toml_table_t* toml_parse(char* conf, /* NUL terminated, please. */ - char* errbuf, - int errbufsz); - -/* Free the table returned by toml_parse() or toml_parse_file(). Once - * this function is called, any handles accessed through this tab - * directly or indirectly are no longer valid. - */ -TOML_EXTERN void toml_free(toml_table_t* tab); - - -/* Timestamp types. The year, month, day, hour, minute, second, z - * fields may be NULL if they are not relevant. e.g. In a DATE - * type, the hour, minute, second and z fields will be NULLs. - */ -struct toml_timestamp_t { - struct { /* internal. do not use. */ - int year, month, day; - int hour, minute, second, millisec; - char z[10]; - } __buffer; - int *year, *month, *day; - int *hour, *minute, *second, *millisec; - char* z; -}; - - -/*----------------------------------------------------------------- - * Enhanced access methods - */ -struct toml_datum_t { - int ok; - union { - toml_timestamp_t* ts; /* ts must be freed after use */ - char* s; /* string value. s must be freed after use */ - int b; /* bool value */ - int64_t i; /* int value */ - double d; /* double value */ - } u; -}; - -/* on arrays: */ -/* ... retrieve size of array. */ -TOML_EXTERN int toml_array_nelem(const toml_array_t* arr); -/* ... retrieve values using index. */ -TOML_EXTERN toml_datum_t toml_string_at(const toml_array_t* arr, int idx); -TOML_EXTERN toml_datum_t toml_bool_at(const toml_array_t* arr, int idx); -TOML_EXTERN toml_datum_t toml_int_at(const toml_array_t* arr, int idx); -TOML_EXTERN toml_datum_t toml_double_at(const toml_array_t* arr, int idx); -TOML_EXTERN toml_datum_t toml_timestamp_at(const toml_array_t* arr, int idx); -/* ... retrieve array or table using index. */ -TOML_EXTERN toml_array_t* toml_array_at(const toml_array_t* arr, int idx); -TOML_EXTERN toml_table_t* toml_table_at(const toml_array_t* arr, int idx); - -/* on tables: */ -/* ... retrieve the key in table at keyidx. Return 0 if out of range. */ -TOML_EXTERN const char* toml_key_in(const toml_table_t* tab, int keyidx); -/* ... retrieve values using key. */ -TOML_EXTERN toml_datum_t toml_string_in(const toml_table_t* arr, const char* key); -TOML_EXTERN toml_datum_t toml_bool_in(const toml_table_t* arr, const char* key); -TOML_EXTERN toml_datum_t toml_int_in(const toml_table_t* arr, const char* key); -TOML_EXTERN toml_datum_t toml_double_in(const toml_table_t* arr, const char* key); -TOML_EXTERN toml_datum_t toml_timestamp_in(const toml_table_t* arr, const char* key); -/* .. retrieve array or table using key. */ -TOML_EXTERN toml_array_t* toml_array_in(const toml_table_t* tab, - const char* key); -TOML_EXTERN toml_table_t* toml_table_in(const toml_table_t* tab, - const char* key); - -/*----------------------------------------------------------------- - * lesser used - */ -/* Return the array kind: 't'able, 'a'rray, 'v'alue, 'm'ixed */ -TOML_EXTERN char toml_array_kind(const toml_array_t* arr); - -/* For array kind 'v'alue, return the type of values - i:int, d:double, b:bool, s:string, t:time, D:date, T:timestamp, 'm'ixed - 0 if unknown -*/ -TOML_EXTERN char toml_array_type(const toml_array_t* arr); - -/* Return the key of an array */ -TOML_EXTERN const char* toml_array_key(const toml_array_t* arr); - -/* Return the number of key-values in a table */ -TOML_EXTERN int toml_table_nkval(const toml_table_t* tab); - -/* Return the number of arrays in a table */ -TOML_EXTERN int toml_table_narr(const toml_table_t* tab); - -/* Return the number of sub-tables in a table */ -TOML_EXTERN int toml_table_ntab(const toml_table_t* tab); - -/* Return the key of a table*/ -TOML_EXTERN const char* toml_table_key(const toml_table_t* tab); - -/*-------------------------------------------------------------- - * misc - */ -TOML_EXTERN int toml_utf8_to_ucs(const char* orig, int len, int64_t* ret); -TOML_EXTERN int toml_ucs_to_utf8(int64_t code, char buf[6]); -TOML_EXTERN void toml_set_memutil(void* (*xxmalloc)(size_t), - void (*xxfree)(void*)); - - -/*-------------------------------------------------------------- - * deprecated - */ -/* A raw value, must be processed by toml_rto* before using. */ -typedef const char* toml_raw_t; -TOML_EXTERN toml_raw_t toml_raw_in(const toml_table_t* tab, const char* key); -TOML_EXTERN toml_raw_t toml_raw_at(const toml_array_t* arr, int idx); -TOML_EXTERN int toml_rtos(toml_raw_t s, char** ret); -TOML_EXTERN int toml_rtob(toml_raw_t s, int* ret); -TOML_EXTERN int toml_rtoi(toml_raw_t s, int64_t* ret); -TOML_EXTERN int toml_rtod(toml_raw_t s, double* ret); -TOML_EXTERN int toml_rtod_ex(toml_raw_t s, double* ret, char* buf, int buflen); -TOML_EXTERN int toml_rtots(toml_raw_t s, toml_timestamp_t* ret); - - -#endif /* TOML_H */ diff --git a/libraries/tomlc99/src/toml.c b/libraries/tomlc99/src/toml.c deleted file mode 100644 index e46e62e6..00000000 --- a/libraries/tomlc99/src/toml.c +++ /dev/null @@ -1,2300 +0,0 @@ -/* - - MIT License - - Copyright (c) 2017 - 2021 CK Tan - https://github.com/cktan/tomlc99 - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - -*/ -#define _POSIX_C_SOURCE 200809L -#include -#include -#include -#include -#include -#include -#include -#include -#include "toml.h" - - -static void* (*ppmalloc)(size_t) = malloc; -static void (*ppfree)(void*) = free; - -void toml_set_memutil(void* (*xxmalloc)(size_t), - void (*xxfree)(void*)) -{ - if (xxmalloc) ppmalloc = xxmalloc; - if (xxfree) ppfree = xxfree; -} - - -#define MALLOC(a) ppmalloc(a) -#define FREE(a) ppfree(a) - -static void* CALLOC(size_t nmemb, size_t sz) -{ - int nb = sz * nmemb; - void* p = MALLOC(nb); - if (p) { - memset(p, 0, nb); - } - return p; -} - - -static char* STRDUP(const char* s) -{ - int len = strlen(s); - char* p = MALLOC(len+1); - if (p) { - memcpy(p, s, len); - p[len] = 0; - } - return p; -} - -static char* STRNDUP(const char* s, size_t n) -{ - size_t len = strnlen(s, n); - char* p = MALLOC(len+1); - if (p) { - memcpy(p, s, len); - p[len] = 0; - } - return p; -} - - - -/** - * Convert a char in utf8 into UCS, and store it in *ret. - * Return #bytes consumed or -1 on failure. - */ -int toml_utf8_to_ucs(const char* orig, int len, int64_t* ret) -{ - const unsigned char* buf = (const unsigned char*) orig; - unsigned i = *buf++; - int64_t v; - - /* 0x00000000 - 0x0000007F: - 0xxxxxxx - */ - if (0 == (i >> 7)) { - if (len < 1) return -1; - v = i; - return *ret = v, 1; - } - /* 0x00000080 - 0x000007FF: - 110xxxxx 10xxxxxx - */ - if (0x6 == (i >> 5)) { - if (len < 2) return -1; - v = i & 0x1f; - for (int j = 0; j < 1; j++) { - i = *buf++; - if (0x2 != (i >> 6)) return -1; - v = (v << 6) | (i & 0x3f); - } - return *ret = v, (const char*) buf - orig; - } - - /* 0x00000800 - 0x0000FFFF: - 1110xxxx 10xxxxxx 10xxxxxx - */ - if (0xE == (i >> 4)) { - if (len < 3) return -1; - v = i & 0x0F; - for (int j = 0; j < 2; j++) { - i = *buf++; - if (0x2 != (i >> 6)) return -1; - v = (v << 6) | (i & 0x3f); - } - return *ret = v, (const char*) buf - orig; - } - - /* 0x00010000 - 0x001FFFFF: - 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - */ - if (0x1E == (i >> 3)) { - if (len < 4) return -1; - v = i & 0x07; - for (int j = 0; j < 3; j++) { - i = *buf++; - if (0x2 != (i >> 6)) return -1; - v = (v << 6) | (i & 0x3f); - } - return *ret = v, (const char*) buf - orig; - } - - /* 0x00200000 - 0x03FFFFFF: - 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx - */ - if (0x3E == (i >> 2)) { - if (len < 5) return -1; - v = i & 0x03; - for (int j = 0; j < 4; j++) { - i = *buf++; - if (0x2 != (i >> 6)) return -1; - v = (v << 6) | (i & 0x3f); - } - return *ret = v, (const char*) buf - orig; - } - - /* 0x04000000 - 0x7FFFFFFF: - 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx - */ - if (0x7e == (i >> 1)) { - if (len < 6) return -1; - v = i & 0x01; - for (int j = 0; j < 5; j++) { - i = *buf++; - if (0x2 != (i >> 6)) return -1; - v = (v << 6) | (i & 0x3f); - } - return *ret = v, (const char*) buf - orig; - } - return -1; -} - - -/** - * Convert a UCS char to utf8 code, and return it in buf. - * Return #bytes used in buf to encode the char, or - * -1 on error. - */ -int toml_ucs_to_utf8(int64_t code, char buf[6]) -{ - /* http://stackoverflow.com/questions/6240055/manually-converting-unicode-codepoints-into-utf-8-and-utf-16 */ - /* The UCS code values 0xd800–0xdfff (UTF-16 surrogates) as well - * as 0xfffe and 0xffff (UCS noncharacters) should not appear in - * conforming UTF-8 streams. - */ - if (0xd800 <= code && code <= 0xdfff) return -1; - if (0xfffe <= code && code <= 0xffff) return -1; - - /* 0x00000000 - 0x0000007F: - 0xxxxxxx - */ - if (code < 0) return -1; - if (code <= 0x7F) { - buf[0] = (unsigned char) code; - return 1; - } - - /* 0x00000080 - 0x000007FF: - 110xxxxx 10xxxxxx - */ - if (code <= 0x000007FF) { - buf[0] = 0xc0 | (code >> 6); - buf[1] = 0x80 | (code & 0x3f); - return 2; - } - - /* 0x00000800 - 0x0000FFFF: - 1110xxxx 10xxxxxx 10xxxxxx - */ - if (code <= 0x0000FFFF) { - buf[0] = 0xe0 | (code >> 12); - buf[1] = 0x80 | ((code >> 6) & 0x3f); - buf[2] = 0x80 | (code & 0x3f); - return 3; - } - - /* 0x00010000 - 0x001FFFFF: - 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx - */ - if (code <= 0x001FFFFF) { - buf[0] = 0xf0 | (code >> 18); - buf[1] = 0x80 | ((code >> 12) & 0x3f); - buf[2] = 0x80 | ((code >> 6) & 0x3f); - buf[3] = 0x80 | (code & 0x3f); - return 4; - } - - /* 0x00200000 - 0x03FFFFFF: - 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx - */ - if (code <= 0x03FFFFFF) { - buf[0] = 0xf8 | (code >> 24); - buf[1] = 0x80 | ((code >> 18) & 0x3f); - buf[2] = 0x80 | ((code >> 12) & 0x3f); - buf[3] = 0x80 | ((code >> 6) & 0x3f); - buf[4] = 0x80 | (code & 0x3f); - return 5; - } - - /* 0x04000000 - 0x7FFFFFFF: - 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx - */ - if (code <= 0x7FFFFFFF) { - buf[0] = 0xfc | (code >> 30); - buf[1] = 0x80 | ((code >> 24) & 0x3f); - buf[2] = 0x80 | ((code >> 18) & 0x3f); - buf[3] = 0x80 | ((code >> 12) & 0x3f); - buf[4] = 0x80 | ((code >> 6) & 0x3f); - buf[5] = 0x80 | (code & 0x3f); - return 6; - } - - return -1; -} - -/* - * TOML has 3 data structures: value, array, table. - * Each of them can have identification key. - */ -typedef struct toml_keyval_t toml_keyval_t; -struct toml_keyval_t { - const char* key; /* key to this value */ - const char* val; /* the raw value */ -}; - -typedef struct toml_arritem_t toml_arritem_t; -struct toml_arritem_t { - int valtype; /* for value kind: 'i'nt, 'd'ouble, 'b'ool, 's'tring, 't'ime, 'D'ate, 'T'imestamp */ - char* val; - toml_array_t* arr; - toml_table_t* tab; -}; - - -struct toml_array_t { - const char* key; /* key to this array */ - int kind; /* element kind: 'v'alue, 'a'rray, or 't'able, 'm'ixed */ - int type; /* for value kind: 'i'nt, 'd'ouble, 'b'ool, 's'tring, 't'ime, 'D'ate, 'T'imestamp, 'm'ixed */ - - int nitem; /* number of elements */ - toml_arritem_t* item; -}; - - -struct toml_table_t { - const char* key; /* key to this table */ - bool implicit; /* table was created implicitly */ - bool readonly; /* no more modification allowed */ - - /* key-values in the table */ - int nkval; - toml_keyval_t** kval; - - /* arrays in the table */ - int narr; - toml_array_t** arr; - - /* tables in the table */ - int ntab; - toml_table_t** tab; -}; - - -static inline void xfree(const void* x) { if (x) FREE((void*)(intptr_t)x); } - - -enum tokentype_t { - INVALID, - DOT, - COMMA, - EQUAL, - LBRACE, - RBRACE, - NEWLINE, - LBRACKET, - RBRACKET, - STRING, -}; -typedef enum tokentype_t tokentype_t; - -typedef struct token_t token_t; -struct token_t { - tokentype_t tok; - int lineno; - char* ptr; /* points into context->start */ - int len; - int eof; -}; - - -typedef struct context_t context_t; -struct context_t { - char* start; - char* stop; - char* errbuf; - int errbufsz; - - token_t tok; - toml_table_t* root; - toml_table_t* curtab; - - struct { - int top; - char* key[10]; - token_t tok[10]; - } tpath; - -}; - -#define STRINGIFY(x) #x -#define TOSTRING(x) STRINGIFY(x) -#define FLINE __FILE__ ":" TOSTRING(__LINE__) - -static int next_token(context_t* ctx, int dotisspecial); - -/* - Error reporting. Call when an error is detected. Always return -1. -*/ -static int e_outofmemory(context_t* ctx, const char* fline) -{ - snprintf(ctx->errbuf, ctx->errbufsz, "ERROR: out of memory (%s)", fline); - return -1; -} - - -static int e_internal(context_t* ctx, const char* fline) -{ - snprintf(ctx->errbuf, ctx->errbufsz, "internal error (%s)", fline); - return -1; -} - -static int e_syntax(context_t* ctx, int lineno, const char* msg) -{ - snprintf(ctx->errbuf, ctx->errbufsz, "line %d: %s", lineno, msg); - return -1; -} - -static int e_badkey(context_t* ctx, int lineno) -{ - snprintf(ctx->errbuf, ctx->errbufsz, "line %d: bad key", lineno); - return -1; -} - -static int e_keyexists(context_t* ctx, int lineno) -{ - snprintf(ctx->errbuf, ctx->errbufsz, "line %d: key exists", lineno); - return -1; -} - -static int e_forbid(context_t* ctx, int lineno, const char* msg) -{ - snprintf(ctx->errbuf, ctx->errbufsz, "line %d: %s", lineno, msg); - return -1; -} - -static void* expand(void* p, int sz, int newsz) -{ - void* s = MALLOC(newsz); - if (!s) return 0; - - memcpy(s, p, sz); - FREE(p); - return s; -} - -static void** expand_ptrarr(void** p, int n) -{ - void** s = MALLOC((n+1) * sizeof(void*)); - if (!s) return 0; - - s[n] = 0; - memcpy(s, p, n * sizeof(void*)); - FREE(p); - return s; -} - -static toml_arritem_t* expand_arritem(toml_arritem_t* p, int n) -{ - toml_arritem_t* pp = expand(p, n*sizeof(*p), (n+1)*sizeof(*p)); - if (!pp) return 0; - - memset(&pp[n], 0, sizeof(pp[n])); - return pp; -} - - -static char* norm_lit_str(const char* src, int srclen, - int multiline, - char* errbuf, int errbufsz) -{ - char* dst = 0; /* will write to dst[] and return it */ - int max = 0; /* max size of dst[] */ - int off = 0; /* cur offset in dst[] */ - const char* sp = src; - const char* sq = src + srclen; - int ch; - - /* scan forward on src */ - for (;;) { - if (off >= max - 10) { /* have some slack for misc stuff */ - int newmax = max + 50; - char* x = expand(dst, max, newmax); - if (!x) { - xfree(dst); - snprintf(errbuf, errbufsz, "out of memory"); - return 0; - } - dst = x; - max = newmax; - } - - /* finished? */ - if (sp >= sq) break; - - ch = *sp++; - /* control characters other than tab is not allowed */ - if ((0 <= ch && ch <= 0x08) - || (0x0a <= ch && ch <= 0x1f) - || (ch == 0x7f)) { - if (! (multiline && (ch == '\r' || ch == '\n'))) { - xfree(dst); - snprintf(errbuf, errbufsz, "invalid char U+%04x", ch); - return 0; - } - } - - // a plain copy suffice - dst[off++] = ch; - } - - dst[off++] = 0; - return dst; -} - - - - -/* - * Convert src to raw unescaped utf-8 string. - * Returns NULL if error with errmsg in errbuf. - */ -static char* norm_basic_str(const char* src, int srclen, - int multiline, - char* errbuf, int errbufsz) -{ - char* dst = 0; /* will write to dst[] and return it */ - int max = 0; /* max size of dst[] */ - int off = 0; /* cur offset in dst[] */ - const char* sp = src; - const char* sq = src + srclen; - int ch; - - /* scan forward on src */ - for (;;) { - if (off >= max - 10) { /* have some slack for misc stuff */ - int newmax = max + 50; - char* x = expand(dst, max, newmax); - if (!x) { - xfree(dst); - snprintf(errbuf, errbufsz, "out of memory"); - return 0; - } - dst = x; - max = newmax; - } - - /* finished? */ - if (sp >= sq) break; - - ch = *sp++; - if (ch != '\\') { - /* these chars must be escaped: U+0000 to U+0008, U+000A to U+001F, U+007F */ - if ((0 <= ch && ch <= 0x08) - || (0x0a <= ch && ch <= 0x1f) - || (ch == 0x7f)) { - if (! (multiline && (ch == '\r' || ch == '\n'))) { - xfree(dst); - snprintf(errbuf, errbufsz, "invalid char U+%04x", ch); - return 0; - } - } - - // a plain copy suffice - dst[off++] = ch; - continue; - } - - /* ch was backslash. we expect the escape char. */ - if (sp >= sq) { - snprintf(errbuf, errbufsz, "last backslash is invalid"); - xfree(dst); - return 0; - } - - /* for multi-line, we want to kill line-ending-backslash ... */ - if (multiline) { - - // if there is only whitespace after the backslash ... - if (sp[strspn(sp, " \t\r")] == '\n') { - /* skip all the following whitespaces */ - sp += strspn(sp, " \t\r\n"); - continue; - } - } - - /* get the escaped char */ - ch = *sp++; - switch (ch) { - case 'u': case 'U': - { - int64_t ucs = 0; - int nhex = (ch == 'u' ? 4 : 8); - for (int i = 0; i < nhex; i++) { - if (sp >= sq) { - snprintf(errbuf, errbufsz, "\\%c expects %d hex chars", ch, nhex); - xfree(dst); - return 0; - } - ch = *sp++; - int v = ('0' <= ch && ch <= '9') - ? ch - '0' - : (('A' <= ch && ch <= 'F') ? ch - 'A' + 10 : -1); - if (-1 == v) { - snprintf(errbuf, errbufsz, "invalid hex chars for \\u or \\U"); - xfree(dst); - return 0; - } - ucs = ucs * 16 + v; - } - int n = toml_ucs_to_utf8(ucs, &dst[off]); - if (-1 == n) { - snprintf(errbuf, errbufsz, "illegal ucs code in \\u or \\U"); - xfree(dst); - return 0; - } - off += n; - } - continue; - - case 'b': ch = '\b'; break; - case 't': ch = '\t'; break; - case 'n': ch = '\n'; break; - case 'f': ch = '\f'; break; - case 'r': ch = '\r'; break; - case '"': ch = '"'; break; - case '\\': ch = '\\'; break; - default: - snprintf(errbuf, errbufsz, "illegal escape char \\%c", ch); - xfree(dst); - return 0; - } - - dst[off++] = ch; - } - - // Cap with NUL and return it. - dst[off++] = 0; - return dst; -} - - -/* Normalize a key. Convert all special chars to raw unescaped utf-8 chars. */ -static char* normalize_key(context_t* ctx, token_t strtok) -{ - const char* sp = strtok.ptr; - const char* sq = strtok.ptr + strtok.len; - int lineno = strtok.lineno; - char* ret; - int ch = *sp; - char ebuf[80]; - - /* handle quoted string */ - if (ch == '\'' || ch == '\"') { - /* if ''' or """, take 3 chars off front and back. Else, take 1 char off. */ - int multiline = 0; - if (sp[1] == ch && sp[2] == ch) { - sp += 3, sq -= 3; - multiline = 1; - } - else - sp++, sq--; - - if (ch == '\'') { - /* for single quote, take it verbatim. */ - if (! (ret = STRNDUP(sp, sq - sp))) { - e_outofmemory(ctx, FLINE); - return 0; - } - } else { - /* for double quote, we need to normalize */ - ret = norm_basic_str(sp, sq - sp, multiline, ebuf, sizeof(ebuf)); - if (!ret) { - e_syntax(ctx, lineno, ebuf); - return 0; - } - } - - /* newlines are not allowed in keys */ - if (strchr(ret, '\n')) { - xfree(ret); - e_badkey(ctx, lineno); - return 0; - } - return ret; - } - - /* for bare-key allow only this regex: [A-Za-z0-9_-]+ */ - const char* xp; - for (xp = sp; xp != sq; xp++) { - int k = *xp; - if (isalnum(k)) continue; - if (k == '_' || k == '-') continue; - e_badkey(ctx, lineno); - return 0; - } - - /* dup and return it */ - if (! (ret = STRNDUP(sp, sq - sp))) { - e_outofmemory(ctx, FLINE); - return 0; - } - return ret; -} - - -/* - * Look up key in tab. Return 0 if not found, or - * 'v'alue, 'a'rray or 't'able depending on the element. - */ -static int check_key(toml_table_t* tab, const char* key, - toml_keyval_t** ret_val, - toml_array_t** ret_arr, - toml_table_t** ret_tab) -{ - int i; - void* dummy; - - if (!ret_tab) ret_tab = (toml_table_t**) &dummy; - if (!ret_arr) ret_arr = (toml_array_t**) &dummy; - if (!ret_val) ret_val = (toml_keyval_t**) &dummy; - - *ret_tab = 0; *ret_arr = 0; *ret_val = 0; - - for (i = 0; i < tab->nkval; i++) { - if (0 == strcmp(key, tab->kval[i]->key)) { - *ret_val = tab->kval[i]; - return 'v'; - } - } - for (i = 0; i < tab->narr; i++) { - if (0 == strcmp(key, tab->arr[i]->key)) { - *ret_arr = tab->arr[i]; - return 'a'; - } - } - for (i = 0; i < tab->ntab; i++) { - if (0 == strcmp(key, tab->tab[i]->key)) { - *ret_tab = tab->tab[i]; - return 't'; - } - } - return 0; -} - - -static int key_kind(toml_table_t* tab, const char* key) -{ - return check_key(tab, key, 0, 0, 0); -} - -/* Create a keyval in the table. - */ -static toml_keyval_t* create_keyval_in_table(context_t* ctx, toml_table_t* tab, token_t keytok) -{ - /* first, normalize the key to be used for lookup. - * remember to free it if we error out. - */ - char* newkey = normalize_key(ctx, keytok); - if (!newkey) return 0; - - /* if key exists: error out. */ - toml_keyval_t* dest = 0; - if (key_kind(tab, newkey)) { - xfree(newkey); - e_keyexists(ctx, keytok.lineno); - return 0; - } - - /* make a new entry */ - int n = tab->nkval; - toml_keyval_t** base; - if (0 == (base = (toml_keyval_t**) expand_ptrarr((void**)tab->kval, n))) { - xfree(newkey); - e_outofmemory(ctx, FLINE); - return 0; - } - tab->kval = base; - - if (0 == (base[n] = (toml_keyval_t*) CALLOC(1, sizeof(*base[n])))) { - xfree(newkey); - e_outofmemory(ctx, FLINE); - return 0; - } - dest = tab->kval[tab->nkval++]; - - /* save the key in the new value struct */ - dest->key = newkey; - return dest; -} - - -/* Create a table in the table. - */ -static toml_table_t* create_keytable_in_table(context_t* ctx, toml_table_t* tab, token_t keytok) -{ - /* first, normalize the key to be used for lookup. - * remember to free it if we error out. - */ - char* newkey = normalize_key(ctx, keytok); - if (!newkey) return 0; - - /* if key exists: error out */ - toml_table_t* dest = 0; - if (check_key(tab, newkey, 0, 0, &dest)) { - xfree(newkey); /* don't need this anymore */ - - /* special case: if table exists, but was created implicitly ... */ - if (dest && dest->implicit) { - /* we make it explicit now, and simply return it. */ - dest->implicit = false; - return dest; - } - e_keyexists(ctx, keytok.lineno); - return 0; - } - - /* create a new table entry */ - int n = tab->ntab; - toml_table_t** base; - if (0 == (base = (toml_table_t**) expand_ptrarr((void**)tab->tab, n))) { - xfree(newkey); - e_outofmemory(ctx, FLINE); - return 0; - } - tab->tab = base; - - if (0 == (base[n] = (toml_table_t*) CALLOC(1, sizeof(*base[n])))) { - xfree(newkey); - e_outofmemory(ctx, FLINE); - return 0; - } - dest = tab->tab[tab->ntab++]; - - /* save the key in the new table struct */ - dest->key = newkey; - return dest; -} - - -/* Create an array in the table. - */ -static toml_array_t* create_keyarray_in_table(context_t* ctx, - toml_table_t* tab, - token_t keytok, - char kind) -{ - /* first, normalize the key to be used for lookup. - * remember to free it if we error out. - */ - char* newkey = normalize_key(ctx, keytok); - if (!newkey) return 0; - - /* if key exists: error out */ - if (key_kind(tab, newkey)) { - xfree(newkey); /* don't need this anymore */ - e_keyexists(ctx, keytok.lineno); - return 0; - } - - /* make a new array entry */ - int n = tab->narr; - toml_array_t** base; - if (0 == (base = (toml_array_t**) expand_ptrarr((void**)tab->arr, n))) { - xfree(newkey); - e_outofmemory(ctx, FLINE); - return 0; - } - tab->arr = base; - - if (0 == (base[n] = (toml_array_t*) CALLOC(1, sizeof(*base[n])))) { - xfree(newkey); - e_outofmemory(ctx, FLINE); - return 0; - } - toml_array_t* dest = tab->arr[tab->narr++]; - - /* save the key in the new array struct */ - dest->key = newkey; - dest->kind = kind; - return dest; -} - - -static toml_arritem_t* create_value_in_array(context_t* ctx, - toml_array_t* parent) -{ - const int n = parent->nitem; - toml_arritem_t* base = expand_arritem(parent->item, n); - if (!base) { - e_outofmemory(ctx, FLINE); - return 0; - } - parent->item = base; - parent->nitem++; - return &parent->item[n]; -} - -/* Create an array in an array - */ -static toml_array_t* create_array_in_array(context_t* ctx, - toml_array_t* parent) -{ - const int n = parent->nitem; - toml_arritem_t* base = expand_arritem(parent->item, n); - if (!base) { - e_outofmemory(ctx, FLINE); - return 0; - } - toml_array_t* ret = (toml_array_t*) CALLOC(1, sizeof(toml_array_t)); - if (!ret) { - e_outofmemory(ctx, FLINE); - return 0; - } - base[n].arr = ret; - parent->item = base; - parent->nitem++; - return ret; -} - -/* Create a table in an array - */ -static toml_table_t* create_table_in_array(context_t* ctx, - toml_array_t* parent) -{ - int n = parent->nitem; - toml_arritem_t* base = expand_arritem(parent->item, n); - if (!base) { - e_outofmemory(ctx, FLINE); - return 0; - } - toml_table_t* ret = (toml_table_t*) CALLOC(1, sizeof(toml_table_t)); - if (!ret) { - e_outofmemory(ctx, FLINE); - return 0; - } - base[n].tab = ret; - parent->item = base; - parent->nitem++; - return ret; -} - - -static int skip_newlines(context_t* ctx, int isdotspecial) -{ - while (ctx->tok.tok == NEWLINE) { - if (next_token(ctx, isdotspecial)) return -1; - if (ctx->tok.eof) break; - } - return 0; -} - - -static int parse_keyval(context_t* ctx, toml_table_t* tab); - -static inline int eat_token(context_t* ctx, tokentype_t typ, int isdotspecial, const char* fline) -{ - if (ctx->tok.tok != typ) - return e_internal(ctx, fline); - - if (next_token(ctx, isdotspecial)) - return -1; - - return 0; -} - - - -/* We are at '{ ... }'. - * Parse the table. - */ -static int parse_inline_table(context_t* ctx, toml_table_t* tab) -{ - if (eat_token(ctx, LBRACE, 1, FLINE)) - return -1; - - for (;;) { - if (ctx->tok.tok == NEWLINE) - return e_syntax(ctx, ctx->tok.lineno, "newline not allowed in inline table"); - - /* until } */ - if (ctx->tok.tok == RBRACE) - break; - - if (ctx->tok.tok != STRING) - return e_syntax(ctx, ctx->tok.lineno, "expect a string"); - - if (parse_keyval(ctx, tab)) - return -1; - - if (ctx->tok.tok == NEWLINE) - return e_syntax(ctx, ctx->tok.lineno, "newline not allowed in inline table"); - - /* on comma, continue to scan for next keyval */ - if (ctx->tok.tok == COMMA) { - if (eat_token(ctx, COMMA, 1, FLINE)) - return -1; - continue; - } - break; - } - - if (eat_token(ctx, RBRACE, 1, FLINE)) - return -1; - - tab->readonly = 1; - - return 0; -} - -static int valtype(const char* val) -{ - toml_timestamp_t ts; - if (*val == '\'' || *val == '"') return 's'; - if (0 == toml_rtob(val, 0)) return 'b'; - if (0 == toml_rtoi(val, 0)) return 'i'; - if (0 == toml_rtod(val, 0)) return 'd'; - if (0 == toml_rtots(val, &ts)) { - if (ts.year && ts.hour) return 'T'; /* timestamp */ - if (ts.year) return 'D'; /* date */ - return 't'; /* time */ - } - return 'u'; /* unknown */ -} - - -/* We are at '[...]' */ -static int parse_array(context_t* ctx, toml_array_t* arr) -{ - if (eat_token(ctx, LBRACKET, 0, FLINE)) return -1; - - for (;;) { - if (skip_newlines(ctx, 0)) return -1; - - /* until ] */ - if (ctx->tok.tok == RBRACKET) break; - - switch (ctx->tok.tok) { - case STRING: - { - /* set array kind if this will be the first entry */ - if (arr->kind == 0) - arr->kind = 'v'; - else if (arr->kind != 'v') - arr->kind = 'm'; - - char* val = ctx->tok.ptr; - int vlen = ctx->tok.len; - - /* make a new value in array */ - toml_arritem_t* newval = create_value_in_array(ctx, arr); - if (!newval) - return e_outofmemory(ctx, FLINE); - - if (! (newval->val = STRNDUP(val, vlen))) - return e_outofmemory(ctx, FLINE); - - newval->valtype = valtype(newval->val); - - /* set array type if this is the first entry */ - if (arr->nitem == 1) - arr->type = newval->valtype; - else if (arr->type != newval->valtype) - arr->type = 'm'; /* mixed */ - - if (eat_token(ctx, STRING, 0, FLINE)) return -1; - break; - } - - case LBRACKET: - { /* [ [array], [array] ... ] */ - /* set the array kind if this will be the first entry */ - if (arr->kind == 0) - arr->kind = 'a'; - else if (arr->kind != 'a') - arr->kind = 'm'; - - toml_array_t* subarr = create_array_in_array(ctx, arr); - if (!subarr) return -1; - if (parse_array(ctx, subarr)) return -1; - break; - } - - case LBRACE: - { /* [ {table}, {table} ... ] */ - /* set the array kind if this will be the first entry */ - if (arr->kind == 0) - arr->kind = 't'; - else if (arr->kind != 't') - arr->kind = 'm'; - - toml_table_t* subtab = create_table_in_array(ctx, arr); - if (!subtab) return -1; - if (parse_inline_table(ctx, subtab)) return -1; - break; - } - - default: - return e_syntax(ctx, ctx->tok.lineno, "syntax error"); - } - - if (skip_newlines(ctx, 0)) return -1; - - /* on comma, continue to scan for next element */ - if (ctx->tok.tok == COMMA) { - if (eat_token(ctx, COMMA, 0, FLINE)) return -1; - continue; - } - break; - } - - if (eat_token(ctx, RBRACKET, 1, FLINE)) return -1; - return 0; -} - - -/* handle lines like these: - key = "value" - key = [ array ] - key = { table } -*/ -static int parse_keyval(context_t* ctx, toml_table_t* tab) -{ - if (tab->readonly) { - return e_forbid(ctx, ctx->tok.lineno, "cannot insert new entry into existing table"); - } - - token_t key = ctx->tok; - if (eat_token(ctx, STRING, 1, FLINE)) return -1; - - if (ctx->tok.tok == DOT) { - /* handle inline dotted key. - e.g. - physical.color = "orange" - physical.shape = "round" - */ - toml_table_t* subtab = 0; - { - char* subtabstr = normalize_key(ctx, key); - if (!subtabstr) return -1; - - subtab = toml_table_in(tab, subtabstr); - xfree(subtabstr); - } - if (!subtab) { - subtab = create_keytable_in_table(ctx, tab, key); - if (!subtab) return -1; - } - if (next_token(ctx, 1)) return -1; - if (parse_keyval(ctx, subtab)) return -1; - return 0; - } - - if (ctx->tok.tok != EQUAL) { - return e_syntax(ctx, ctx->tok.lineno, "missing ="); - } - - if (next_token(ctx, 0)) return -1; - - switch (ctx->tok.tok) { - case STRING: - { /* key = "value" */ - toml_keyval_t* keyval = create_keyval_in_table(ctx, tab, key); - if (!keyval) return -1; - token_t val = ctx->tok; - - assert(keyval->val == 0); - if (! (keyval->val = STRNDUP(val.ptr, val.len))) - return e_outofmemory(ctx, FLINE); - - if (next_token(ctx, 1)) return -1; - - return 0; - } - - case LBRACKET: - { /* key = [ array ] */ - toml_array_t* arr = create_keyarray_in_table(ctx, tab, key, 0); - if (!arr) return -1; - if (parse_array(ctx, arr)) return -1; - return 0; - } - - case LBRACE: - { /* key = { table } */ - toml_table_t* nxttab = create_keytable_in_table(ctx, tab, key); - if (!nxttab) return -1; - if (parse_inline_table(ctx, nxttab)) return -1; - return 0; - } - - default: - return e_syntax(ctx, ctx->tok.lineno, "syntax error"); - } - return 0; -} - - -typedef struct tabpath_t tabpath_t; -struct tabpath_t { - int cnt; - token_t key[10]; -}; - -/* at [x.y.z] or [[x.y.z]] - * Scan forward and fill tabpath until it enters ] or ]] - * There will be at least one entry on return. - */ -static int fill_tabpath(context_t* ctx) -{ - int lineno = ctx->tok.lineno; - int i; - - /* clear tpath */ - for (i = 0; i < ctx->tpath.top; i++) { - char** p = &ctx->tpath.key[i]; - xfree(*p); - *p = 0; - } - ctx->tpath.top = 0; - - for (;;) { - if (ctx->tpath.top >= 10) - return e_syntax(ctx, lineno, "table path is too deep; max allowed is 10."); - - if (ctx->tok.tok != STRING) - return e_syntax(ctx, lineno, "invalid or missing key"); - - char* key = normalize_key(ctx, ctx->tok); - if (!key) return -1; - ctx->tpath.tok[ctx->tpath.top] = ctx->tok; - ctx->tpath.key[ctx->tpath.top] = key; - ctx->tpath.top++; - - if (next_token(ctx, 1)) return -1; - - if (ctx->tok.tok == RBRACKET) break; - - if (ctx->tok.tok != DOT) - return e_syntax(ctx, lineno, "invalid key"); - - if (next_token(ctx, 1)) return -1; - } - - if (ctx->tpath.top <= 0) - return e_syntax(ctx, lineno, "empty table selector"); - - return 0; -} - - -/* Walk tabpath from the root, and create new tables on the way. - * Sets ctx->curtab to the final table. - */ -static int walk_tabpath(context_t* ctx) -{ - /* start from root */ - toml_table_t* curtab = ctx->root; - - for (int i = 0; i < ctx->tpath.top; i++) { - const char* key = ctx->tpath.key[i]; - - toml_keyval_t* nextval = 0; - toml_array_t* nextarr = 0; - toml_table_t* nexttab = 0; - switch (check_key(curtab, key, &nextval, &nextarr, &nexttab)) { - case 't': - /* found a table. nexttab is where we will go next. */ - break; - - case 'a': - /* found an array. nexttab is the last table in the array. */ - if (nextarr->kind != 't') - return e_internal(ctx, FLINE); - - if (nextarr->nitem == 0) - return e_internal(ctx, FLINE); - - nexttab = nextarr->item[nextarr->nitem-1].tab; - break; - - case 'v': - return e_keyexists(ctx, ctx->tpath.tok[i].lineno); - - default: - { /* Not found. Let's create an implicit table. */ - int n = curtab->ntab; - toml_table_t** base = (toml_table_t**) expand_ptrarr((void**)curtab->tab, n); - if (0 == base) - return e_outofmemory(ctx, FLINE); - - curtab->tab = base; - - if (0 == (base[n] = (toml_table_t*) CALLOC(1, sizeof(*base[n])))) - return e_outofmemory(ctx, FLINE); - - if (0 == (base[n]->key = STRDUP(key))) - return e_outofmemory(ctx, FLINE); - - nexttab = curtab->tab[curtab->ntab++]; - - /* tabs created by walk_tabpath are considered implicit */ - nexttab->implicit = true; - } - break; - } - - /* switch to next tab */ - curtab = nexttab; - } - - /* save it */ - ctx->curtab = curtab; - - return 0; -} - - -/* handle lines like [x.y.z] or [[x.y.z]] */ -static int parse_select(context_t* ctx) -{ - assert(ctx->tok.tok == LBRACKET); - - /* true if [[ */ - int llb = (ctx->tok.ptr + 1 < ctx->stop && ctx->tok.ptr[1] == '['); - /* need to detect '[[' on our own because next_token() will skip whitespace, - and '[ [' would be taken as '[[', which is wrong. */ - - /* eat [ or [[ */ - if (eat_token(ctx, LBRACKET, 1, FLINE)) return -1; - if (llb) { - assert(ctx->tok.tok == LBRACKET); - if (eat_token(ctx, LBRACKET, 1, FLINE)) return -1; - } - - if (fill_tabpath(ctx)) return -1; - - /* For [x.y.z] or [[x.y.z]], remove z from tpath. - */ - token_t z = ctx->tpath.tok[ctx->tpath.top-1]; - xfree(ctx->tpath.key[ctx->tpath.top-1]); - ctx->tpath.top--; - - /* set up ctx->curtab */ - if (walk_tabpath(ctx)) return -1; - - if (! llb) { - /* [x.y.z] -> create z = {} in x.y */ - toml_table_t* curtab = create_keytable_in_table(ctx, ctx->curtab, z); - if (!curtab) return -1; - ctx->curtab = curtab; - } else { - /* [[x.y.z]] -> create z = [] in x.y */ - toml_array_t* arr = 0; - { - char* zstr = normalize_key(ctx, z); - if (!zstr) return -1; - arr = toml_array_in(ctx->curtab, zstr); - xfree(zstr); - } - if (!arr) { - arr = create_keyarray_in_table(ctx, ctx->curtab, z, 't'); - if (!arr) return -1; - } - if (arr->kind != 't') - return e_syntax(ctx, z.lineno, "array mismatch"); - - /* add to z[] */ - toml_table_t* dest; - { - toml_table_t* t = create_table_in_array(ctx, arr); - if (!t) return -1; - - if (0 == (t->key = STRDUP("__anon__"))) - return e_outofmemory(ctx, FLINE); - - dest = t; - } - - ctx->curtab = dest; - } - - if (ctx->tok.tok != RBRACKET) { - return e_syntax(ctx, ctx->tok.lineno, "expects ]"); - } - if (llb) { - if (! (ctx->tok.ptr + 1 < ctx->stop && ctx->tok.ptr[1] == ']')) { - return e_syntax(ctx, ctx->tok.lineno, "expects ]]"); - } - if (eat_token(ctx, RBRACKET, 1, FLINE)) return -1; - } - - if (eat_token(ctx, RBRACKET, 1, FLINE)) - return -1; - - if (ctx->tok.tok != NEWLINE) - return e_syntax(ctx, ctx->tok.lineno, "extra chars after ] or ]]"); - - return 0; -} - - - - -toml_table_t* toml_parse(char* conf, - char* errbuf, - int errbufsz) -{ - context_t ctx; - - // clear errbuf - if (errbufsz <= 0) errbufsz = 0; - if (errbufsz > 0) errbuf[0] = 0; - - // init context - memset(&ctx, 0, sizeof(ctx)); - ctx.start = conf; - ctx.stop = ctx.start + strlen(conf); - ctx.errbuf = errbuf; - ctx.errbufsz = errbufsz; - - // start with an artificial newline of length 0 - ctx.tok.tok = NEWLINE; - ctx.tok.lineno = 1; - ctx.tok.ptr = conf; - ctx.tok.len = 0; - - // make a root table - if (0 == (ctx.root = CALLOC(1, sizeof(*ctx.root)))) { - e_outofmemory(&ctx, FLINE); - // Do not goto fail, root table not set up yet - return 0; - } - - // set root as default table - ctx.curtab = ctx.root; - - /* Scan forward until EOF */ - for (token_t tok = ctx.tok; ! tok.eof ; tok = ctx.tok) { - switch (tok.tok) { - - case NEWLINE: - if (next_token(&ctx, 1)) goto fail; - break; - - case STRING: - if (parse_keyval(&ctx, ctx.curtab)) goto fail; - - if (ctx.tok.tok != NEWLINE) { - e_syntax(&ctx, ctx.tok.lineno, "extra chars after value"); - goto fail; - } - - if (eat_token(&ctx, NEWLINE, 1, FLINE)) goto fail; - break; - - case LBRACKET: /* [ x.y.z ] or [[ x.y.z ]] */ - if (parse_select(&ctx)) goto fail; - break; - - default: - e_syntax(&ctx, tok.lineno, "syntax error"); - goto fail; - } - } - - /* success */ - for (int i = 0; i < ctx.tpath.top; i++) xfree(ctx.tpath.key[i]); - return ctx.root; - -fail: - // Something bad has happened. Free resources and return error. - for (int i = 0; i < ctx.tpath.top; i++) xfree(ctx.tpath.key[i]); - toml_free(ctx.root); - return 0; -} - - -toml_table_t* toml_parse_file(FILE* fp, - char* errbuf, - int errbufsz) -{ - int bufsz = 0; - char* buf = 0; - int off = 0; - - /* read from fp into buf */ - while (! feof(fp)) { - - if (off == bufsz) { - int xsz = bufsz + 1000; - char* x = expand(buf, bufsz, xsz); - if (!x) { - snprintf(errbuf, errbufsz, "out of memory"); - xfree(buf); - return 0; - } - buf = x; - bufsz = xsz; - } - - errno = 0; - int n = fread(buf + off, 1, bufsz - off, fp); - if (ferror(fp)) { - snprintf(errbuf, errbufsz, "%s", - errno ? strerror(errno) : "Error reading file"); - xfree(buf); - return 0; - } - off += n; - } - - /* tag on a NUL to cap the string */ - if (off == bufsz) { - int xsz = bufsz + 1; - char* x = expand(buf, bufsz, xsz); - if (!x) { - snprintf(errbuf, errbufsz, "out of memory"); - xfree(buf); - return 0; - } - buf = x; - bufsz = xsz; - } - buf[off] = 0; - - /* parse it, cleanup and finish */ - toml_table_t* ret = toml_parse(buf, errbuf, errbufsz); - xfree(buf); - return ret; -} - - -static void xfree_kval(toml_keyval_t* p) -{ - if (!p) return; - xfree(p->key); - xfree(p->val); - xfree(p); -} - -static void xfree_tab(toml_table_t* p); - -static void xfree_arr(toml_array_t* p) -{ - if (!p) return; - - xfree(p->key); - const int n = p->nitem; - for (int i = 0; i < n; i++) { - toml_arritem_t* a = &p->item[i]; - if (a->val) - xfree(a->val); - else if (a->arr) - xfree_arr(a->arr); - else if (a->tab) - xfree_tab(a->tab); - } - xfree(p->item); - xfree(p); -} - - -static void xfree_tab(toml_table_t* p) -{ - int i; - - if (!p) return; - - xfree(p->key); - - for (i = 0; i < p->nkval; i++) xfree_kval(p->kval[i]); - xfree(p->kval); - - for (i = 0; i < p->narr; i++) xfree_arr(p->arr[i]); - xfree(p->arr); - - for (i = 0; i < p->ntab; i++) xfree_tab(p->tab[i]); - xfree(p->tab); - - xfree(p); -} - - -void toml_free(toml_table_t* tab) -{ - xfree_tab(tab); -} - - -static void set_token(context_t* ctx, tokentype_t tok, int lineno, char* ptr, int len) -{ - token_t t; - t.tok = tok; - t.lineno = lineno; - t.ptr = ptr; - t.len = len; - t.eof = 0; - ctx->tok = t; -} - -static void set_eof(context_t* ctx, int lineno) -{ - set_token(ctx, NEWLINE, lineno, ctx->stop, 0); - ctx->tok.eof = 1; -} - - -/* Scan p for n digits compositing entirely of [0-9] */ -static int scan_digits(const char* p, int n) -{ - int ret = 0; - for ( ; n > 0 && isdigit(*p); n--, p++) { - ret = 10 * ret + (*p - '0'); - } - return n ? -1 : ret; -} - -static int scan_date(const char* p, int* YY, int* MM, int* DD) -{ - int year, month, day; - year = scan_digits(p, 4); - month = (year >= 0 && p[4] == '-') ? scan_digits(p+5, 2) : -1; - day = (month >= 0 && p[7] == '-') ? scan_digits(p+8, 2) : -1; - if (YY) *YY = year; - if (MM) *MM = month; - if (DD) *DD = day; - return (year >= 0 && month >= 0 && day >= 0) ? 0 : -1; -} - -static int scan_time(const char* p, int* hh, int* mm, int* ss) -{ - int hour, minute, second; - hour = scan_digits(p, 2); - minute = (hour >= 0 && p[2] == ':') ? scan_digits(p+3, 2) : -1; - second = (minute >= 0 && p[5] == ':') ? scan_digits(p+6, 2) : -1; - if (hh) *hh = hour; - if (mm) *mm = minute; - if (ss) *ss = second; - return (hour >= 0 && minute >= 0 && second >= 0) ? 0 : -1; -} - - -static int scan_string(context_t* ctx, char* p, int lineno, int dotisspecial) -{ - char* orig = p; - if (0 == strncmp(p, "'''", 3)) { - char* q = p + 3; - - while (1) { - q = strstr(q, "'''"); - if (0 == q) { - return e_syntax(ctx, lineno, "unterminated triple-s-quote"); - } - while (q[3] == '\'') q++; - break; - } - - set_token(ctx, STRING, lineno, orig, q + 3 - orig); - return 0; - } - - if (0 == strncmp(p, "\"\"\"", 3)) { - char* q = p + 3; - - while (1) { - q = strstr(q, "\"\"\""); - if (0 == q) { - return e_syntax(ctx, lineno, "unterminated triple-d-quote"); - } - if (q[-1] == '\\') { - q++; - continue; - } - while (q[3] == '\"') q++; - break; - } - - // the string is [p+3, q-1] - - int hexreq = 0; /* #hex required */ - int escape = 0; - for (p += 3; p < q; p++) { - if (escape) { - escape = 0; - if (strchr("btnfr\"\\", *p)) continue; - if (*p == 'u') { hexreq = 4; continue; } - if (*p == 'U') { hexreq = 8; continue; } - if (p[strspn(p, " \t\r")] == '\n') continue; /* allow for line ending backslash */ - return e_syntax(ctx, lineno, "bad escape char"); - } - if (hexreq) { - hexreq--; - if (strchr("0123456789ABCDEF", *p)) continue; - return e_syntax(ctx, lineno, "expect hex char"); - } - if (*p == '\\') { escape = 1; continue; } - } - if (escape) - return e_syntax(ctx, lineno, "expect an escape char"); - if (hexreq) - return e_syntax(ctx, lineno, "expected more hex char"); - - set_token(ctx, STRING, lineno, orig, q + 3 - orig); - return 0; - } - - if ('\'' == *p) { - for (p++; *p && *p != '\n' && *p != '\''; p++); - if (*p != '\'') { - return e_syntax(ctx, lineno, "unterminated s-quote"); - } - - set_token(ctx, STRING, lineno, orig, p + 1 - orig); - return 0; - } - - if ('\"' == *p) { - int hexreq = 0; /* #hex required */ - int escape = 0; - for (p++; *p; p++) { - if (escape) { - escape = 0; - if (strchr("btnfr\"\\", *p)) continue; - if (*p == 'u') { hexreq = 4; continue; } - if (*p == 'U') { hexreq = 8; continue; } - return e_syntax(ctx, lineno, "bad escape char"); - } - if (hexreq) { - hexreq--; - if (strchr("0123456789ABCDEF", *p)) continue; - return e_syntax(ctx, lineno, "expect hex char"); - } - if (*p == '\\') { escape = 1; continue; } - if (*p == '\'') { - if (p[1] == '\'' && p[2] == '\'') { - return e_syntax(ctx, lineno, "triple-s-quote inside string lit"); - } - continue; - } - if (*p == '\n') break; - if (*p == '"') break; - } - if (*p != '"') { - return e_syntax(ctx, lineno, "unterminated quote"); - } - - set_token(ctx, STRING, lineno, orig, p + 1 - orig); - return 0; - } - - /* check for timestamp without quotes */ - if (0 == scan_date(p, 0, 0, 0) || 0 == scan_time(p, 0, 0, 0)) { - // forward thru the timestamp - for ( ; strchr("0123456789.:+-T Z", toupper(*p)); p++); - // squeeze out any spaces at end of string - for ( ; p[-1] == ' '; p--); - // tokenize - set_token(ctx, STRING, lineno, orig, p - orig); - return 0; - } - - /* literals */ - for ( ; *p && *p != '\n'; p++) { - int ch = *p; - if (ch == '.' && dotisspecial) break; - if ('A' <= ch && ch <= 'Z') continue; - if ('a' <= ch && ch <= 'z') continue; - if (strchr("0123456789+-_.", ch)) continue; - break; - } - - set_token(ctx, STRING, lineno, orig, p - orig); - return 0; -} - - -static int next_token(context_t* ctx, int dotisspecial) -{ - int lineno = ctx->tok.lineno; - char* p = ctx->tok.ptr; - int i; - - /* eat this tok */ - for (i = 0; i < ctx->tok.len; i++) { - if (*p++ == '\n') - lineno++; - } - - /* make next tok */ - while (p < ctx->stop) { - /* skip comment. stop just before the \n. */ - if (*p == '#') { - for (p++; p < ctx->stop && *p != '\n'; p++); - continue; - } - - if (dotisspecial && *p == '.') { - set_token(ctx, DOT, lineno, p, 1); - return 0; - } - - switch (*p) { - case ',': set_token(ctx, COMMA, lineno, p, 1); return 0; - case '=': set_token(ctx, EQUAL, lineno, p, 1); return 0; - case '{': set_token(ctx, LBRACE, lineno, p, 1); return 0; - case '}': set_token(ctx, RBRACE, lineno, p, 1); return 0; - case '[': set_token(ctx, LBRACKET, lineno, p, 1); return 0; - case ']': set_token(ctx, RBRACKET, lineno, p, 1); return 0; - case '\n': set_token(ctx, NEWLINE, lineno, p, 1); return 0; - case '\r': case ' ': case '\t': - /* ignore white spaces */ - p++; - continue; - } - - return scan_string(ctx, p, lineno, dotisspecial); - } - - set_eof(ctx, lineno); - return 0; -} - - -const char* toml_key_in(const toml_table_t* tab, int keyidx) -{ - if (keyidx < tab->nkval) return tab->kval[keyidx]->key; - - keyidx -= tab->nkval; - if (keyidx < tab->narr) return tab->arr[keyidx]->key; - - keyidx -= tab->narr; - if (keyidx < tab->ntab) return tab->tab[keyidx]->key; - - return 0; -} - -toml_raw_t toml_raw_in(const toml_table_t* tab, const char* key) -{ - int i; - for (i = 0; i < tab->nkval; i++) { - if (0 == strcmp(key, tab->kval[i]->key)) - return tab->kval[i]->val; - } - return 0; -} - -toml_array_t* toml_array_in(const toml_table_t* tab, const char* key) -{ - int i; - for (i = 0; i < tab->narr; i++) { - if (0 == strcmp(key, tab->arr[i]->key)) - return tab->arr[i]; - } - return 0; -} - - -toml_table_t* toml_table_in(const toml_table_t* tab, const char* key) -{ - int i; - for (i = 0; i < tab->ntab; i++) { - if (0 == strcmp(key, tab->tab[i]->key)) - return tab->tab[i]; - } - return 0; -} - -toml_raw_t toml_raw_at(const toml_array_t* arr, int idx) -{ - return (0 <= idx && idx < arr->nitem) ? arr->item[idx].val : 0; -} - -char toml_array_kind(const toml_array_t* arr) -{ - return arr->kind; -} - -char toml_array_type(const toml_array_t* arr) -{ - if (arr->kind != 'v') - return 0; - - if (arr->nitem == 0) - return 0; - - return arr->type; -} - - -int toml_array_nelem(const toml_array_t* arr) -{ - return arr->nitem; -} - -const char* toml_array_key(const toml_array_t* arr) -{ - return arr ? arr->key : (const char*) NULL; -} - -int toml_table_nkval(const toml_table_t* tab) -{ - return tab->nkval; -} - -int toml_table_narr(const toml_table_t* tab) -{ - return tab->narr; -} - -int toml_table_ntab(const toml_table_t* tab) -{ - return tab->ntab; -} - -const char* toml_table_key(const toml_table_t* tab) -{ - return tab ? tab->key : (const char*) NULL; -} - -toml_array_t* toml_array_at(const toml_array_t* arr, int idx) -{ - return (0 <= idx && idx < arr->nitem) ? arr->item[idx].arr : 0; -} - -toml_table_t* toml_table_at(const toml_array_t* arr, int idx) -{ - return (0 <= idx && idx < arr->nitem) ? arr->item[idx].tab : 0; -} - - -int toml_rtots(toml_raw_t src_, toml_timestamp_t* ret) -{ - if (! src_) return -1; - - const char* p = src_; - int must_parse_time = 0; - - memset(ret, 0, sizeof(*ret)); - - int* year = &ret->__buffer.year; - int* month = &ret->__buffer.month; - int* day = &ret->__buffer.day; - int* hour = &ret->__buffer.hour; - int* minute = &ret->__buffer.minute; - int* second = &ret->__buffer.second; - int* millisec = &ret->__buffer.millisec; - - /* parse date YYYY-MM-DD */ - if (0 == scan_date(p, year, month, day)) { - ret->year = year; - ret->month = month; - ret->day = day; - - p += 10; - if (*p) { - // parse the T or space separator - if (*p != 'T' && *p != ' ') return -1; - must_parse_time = 1; - p++; - } - } - - /* parse time HH:MM:SS */ - if (0 == scan_time(p, hour, minute, second)) { - ret->hour = hour; - ret->minute = minute; - ret->second = second; - - /* optionally, parse millisec */ - p += 8; - if (*p == '.') { - char* qq; - p++; - errno = 0; - *millisec = strtol(p, &qq, 0); - if (errno) { - return -1; - } - while (*millisec > 999) { - *millisec /= 10; - } - - ret->millisec = millisec; - p = qq; - } - - if (*p) { - /* parse and copy Z */ - char* z = ret->__buffer.z; - ret->z = z; - if (*p == 'Z' || *p == 'z') { - *z++ = 'Z'; p++; - *z = 0; - - } else if (*p == '+' || *p == '-') { - *z++ = *p++; - - if (! (isdigit(p[0]) && isdigit(p[1]))) return -1; - *z++ = *p++; - *z++ = *p++; - - if (*p == ':') { - *z++ = *p++; - - if (! (isdigit(p[0]) && isdigit(p[1]))) return -1; - *z++ = *p++; - *z++ = *p++; - } - - *z = 0; - } - } - } - if (*p != 0) - return -1; - - if (must_parse_time && !ret->hour) - return -1; - - return 0; -} - - -/* Raw to boolean */ -int toml_rtob(toml_raw_t src, int* ret_) -{ - if (!src) return -1; - int dummy; - int* ret = ret_ ? ret_ : &dummy; - - if (0 == strcmp(src, "true")) { - *ret = 1; - return 0; - } - if (0 == strcmp(src, "false")) { - *ret = 0; - return 0; - } - return -1; -} - - -/* Raw to integer */ -int toml_rtoi(toml_raw_t src, int64_t* ret_) -{ - if (!src) return -1; - - char buf[100]; - char* p = buf; - char* q = p + sizeof(buf); - const char* s = src; - int base = 0; - int64_t dummy; - int64_t* ret = ret_ ? ret_ : &dummy; - - - /* allow +/- */ - if (s[0] == '+' || s[0] == '-') - *p++ = *s++; - - /* disallow +_100 */ - if (s[0] == '_') - return -1; - - /* if 0 ... */ - if ('0' == s[0]) { - switch (s[1]) { - case 'x': base = 16; s += 2; break; - case 'o': base = 8; s += 2; break; - case 'b': base = 2; s += 2; break; - case '\0': return *ret = 0, 0; - default: - /* ensure no other digits after it */ - if (s[1]) return -1; - } - } - - /* just strip underscores and pass to strtoll */ - while (*s && p < q) { - int ch = *s++; - switch (ch) { - case '_': - // disallow '__' - if (s[0] == '_') return -1; - continue; /* skip _ */ - default: - break; - } - *p++ = ch; - } - if (*s || p == q) return -1; - - /* last char cannot be '_' */ - if (s[-1] == '_') return -1; - - /* cap with NUL */ - *p = 0; - - /* Run strtoll on buf to get the integer */ - char* endp; - errno = 0; - *ret = strtoll(buf, &endp, base); - return (errno || *endp) ? -1 : 0; -} - - -int toml_rtod_ex(toml_raw_t src, double* ret_, char* buf, int buflen) -{ - if (!src) return -1; - - char* p = buf; - char* q = p + buflen; - const char* s = src; - double dummy; - double* ret = ret_ ? ret_ : &dummy; - - - /* allow +/- */ - if (s[0] == '+' || s[0] == '-') - *p++ = *s++; - - /* disallow +_1.00 */ - if (s[0] == '_') - return -1; - - /* decimal point, if used, must be surrounded by at least one digit on each side */ - { - char* dot = strchr(s, '.'); - if (dot) { - if (dot == s || !isdigit(dot[-1]) || !isdigit(dot[1])) - return -1; - } - } - - /* zero must be followed by . or 'e', or NUL */ - if (s[0] == '0' && s[1] && !strchr("eE.", s[1])) - return -1; - - /* just strip underscores and pass to strtod */ - while (*s && p < q) { - int ch = *s++; - if (ch == '_') { - // disallow '__' - if (s[0] == '_') return -1; - // disallow last char '_' - if (s[0] == 0) return -1; - continue; /* skip _ */ - } - *p++ = ch; - } - if (*s || p == q) return -1; /* reached end of string or buffer is full? */ - - /* cap with NUL */ - *p = 0; - - /* Run strtod on buf to get the value */ - char* endp; - errno = 0; - *ret = strtod(buf, &endp); - return (errno || *endp) ? -1 : 0; -} - -int toml_rtod(toml_raw_t src, double* ret_) -{ - char buf[100]; - return toml_rtod_ex(src, ret_, buf, sizeof(buf)); -} - - - - -int toml_rtos(toml_raw_t src, char** ret) -{ - int multiline = 0; - const char* sp; - const char* sq; - - *ret = 0; - if (!src) return -1; - - int qchar = src[0]; - int srclen = strlen(src); - if (! (qchar == '\'' || qchar == '"')) { - return -1; - } - - // triple quotes? - if (qchar == src[1] && qchar == src[2]) { - multiline = 1; - sp = src + 3; - sq = src + srclen - 3; - /* last 3 chars in src must be qchar */ - if (! (sp <= sq && sq[0] == qchar && sq[1] == qchar && sq[2] == qchar)) - return -1; - - /* skip new line immediate after qchar */ - if (sp[0] == '\n') - sp++; - else if (sp[0] == '\r' && sp[1] == '\n') - sp += 2; - - } else { - sp = src + 1; - sq = src + srclen - 1; - /* last char in src must be qchar */ - if (! (sp <= sq && *sq == qchar)) - return -1; - } - - if (qchar == '\'') { - *ret = norm_lit_str(sp, sq - sp, - multiline, - 0, 0); - } else { - *ret = norm_basic_str(sp, sq - sp, - multiline, - 0, 0); - } - - return *ret ? 0 : -1; -} - - -toml_datum_t toml_string_at(const toml_array_t* arr, int idx) -{ - toml_datum_t ret; - memset(&ret, 0, sizeof(ret)); - ret.ok = (0 == toml_rtos(toml_raw_at(arr, idx), &ret.u.s)); - return ret; -} - -toml_datum_t toml_bool_at(const toml_array_t* arr, int idx) -{ - toml_datum_t ret; - memset(&ret, 0, sizeof(ret)); - ret.ok = (0 == toml_rtob(toml_raw_at(arr, idx), &ret.u.b)); - return ret; -} - -toml_datum_t toml_int_at(const toml_array_t* arr, int idx) -{ - toml_datum_t ret; - memset(&ret, 0, sizeof(ret)); - ret.ok = (0 == toml_rtoi(toml_raw_at(arr, idx), &ret.u.i)); - return ret; -} - -toml_datum_t toml_double_at(const toml_array_t* arr, int idx) -{ - toml_datum_t ret; - memset(&ret, 0, sizeof(ret)); - ret.ok = (0 == toml_rtod(toml_raw_at(arr, idx), &ret.u.d)); - return ret; -} - -toml_datum_t toml_timestamp_at(const toml_array_t* arr, int idx) -{ - toml_timestamp_t ts; - toml_datum_t ret; - memset(&ret, 0, sizeof(ret)); - ret.ok = (0 == toml_rtots(toml_raw_at(arr, idx), &ts)); - if (ret.ok) { - ret.ok = !!(ret.u.ts = malloc(sizeof(*ret.u.ts))); - if (ret.ok) { - *ret.u.ts = ts; - if (ret.u.ts->year) ret.u.ts->year = &ret.u.ts->__buffer.year; - if (ret.u.ts->month) ret.u.ts->month = &ret.u.ts->__buffer.month; - if (ret.u.ts->day) ret.u.ts->day = &ret.u.ts->__buffer.day; - if (ret.u.ts->hour) ret.u.ts->hour = &ret.u.ts->__buffer.hour; - if (ret.u.ts->minute) ret.u.ts->minute = &ret.u.ts->__buffer.minute; - if (ret.u.ts->second) ret.u.ts->second = &ret.u.ts->__buffer.second; - if (ret.u.ts->millisec) ret.u.ts->millisec = &ret.u.ts->__buffer.millisec; - if (ret.u.ts->z) ret.u.ts->z = ret.u.ts->__buffer.z; - } - } - return ret; -} - -toml_datum_t toml_string_in(const toml_table_t* arr, const char* key) -{ - toml_datum_t ret; - memset(&ret, 0, sizeof(ret)); - toml_raw_t raw = toml_raw_in(arr, key); - if (raw) { - ret.ok = (0 == toml_rtos(raw, &ret.u.s)); - } - return ret; -} - -toml_datum_t toml_bool_in(const toml_table_t* arr, const char* key) -{ - toml_datum_t ret; - memset(&ret, 0, sizeof(ret)); - ret.ok = (0 == toml_rtob(toml_raw_in(arr, key), &ret.u.b)); - return ret; -} - -toml_datum_t toml_int_in(const toml_table_t* arr, const char* key) -{ - toml_datum_t ret; - memset(&ret, 0, sizeof(ret)); - ret.ok = (0 == toml_rtoi(toml_raw_in(arr, key), &ret.u.i)); - return ret; -} - -toml_datum_t toml_double_in(const toml_table_t* arr, const char* key) -{ - toml_datum_t ret; - memset(&ret, 0, sizeof(ret)); - ret.ok = (0 == toml_rtod(toml_raw_in(arr, key), &ret.u.d)); - return ret; -} - -toml_datum_t toml_timestamp_in(const toml_table_t* arr, const char* key) -{ - toml_timestamp_t ts; - toml_datum_t ret; - memset(&ret, 0, sizeof(ret)); - ret.ok = (0 == toml_rtots(toml_raw_in(arr, key), &ts)); - if (ret.ok) { - ret.ok = !!(ret.u.ts = malloc(sizeof(*ret.u.ts))); - if (ret.ok) { - *ret.u.ts = ts; - if (ret.u.ts->year) ret.u.ts->year = &ret.u.ts->__buffer.year; - if (ret.u.ts->month) ret.u.ts->month = &ret.u.ts->__buffer.month; - if (ret.u.ts->day) ret.u.ts->day = &ret.u.ts->__buffer.day; - if (ret.u.ts->hour) ret.u.ts->hour = &ret.u.ts->__buffer.hour; - if (ret.u.ts->minute) ret.u.ts->minute = &ret.u.ts->__buffer.minute; - if (ret.u.ts->second) ret.u.ts->second = &ret.u.ts->__buffer.second; - if (ret.u.ts->millisec) ret.u.ts->millisec = &ret.u.ts->__buffer.millisec; - if (ret.u.ts->z) ret.u.ts->z = ret.u.ts->__buffer.z; - } - } - return ret; -} diff --git a/libraries/tomlplusplus b/libraries/tomlplusplus new file mode 160000 index 00000000..fb8ce803 --- /dev/null +++ b/libraries/tomlplusplus @@ -0,0 +1 @@ +Subproject commit fb8ce80350bcde1d109e9b4d7a571bbc2f29a8bd From 60b38de69f43f2623b7c972e5072fc4ce5aeb3c4 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sat, 24 Sep 2022 11:43:54 +0200 Subject: [PATCH 234/273] fix: fallback for languages without a native name Signed-off-by: Sefa Eyeoglu --- launcher/translations/TranslationsModel.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index 848b4d19..a1ebd0c1 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -86,6 +86,10 @@ struct Language else { result = locale.nativeLanguageName(); } + + if (result.isEmpty()) { + result = key; + } return result; } From b187231b0ea798cd8ebeaa200b4befe29a433a55 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sat, 24 Sep 2022 11:48:14 +0200 Subject: [PATCH 235/273] fix: sort languages by their name instead of key Signed-off-by: Sefa Eyeoglu --- launcher/translations/TranslationsModel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/translations/TranslationsModel.cpp b/launcher/translations/TranslationsModel.cpp index a1ebd0c1..2f57de3a 100644 --- a/launcher/translations/TranslationsModel.cpp +++ b/launcher/translations/TranslationsModel.cpp @@ -398,7 +398,7 @@ void TranslationsModel::reloadLocalFiles() return false; } } - return a.key < b.key; + return a.languageName().toLower() < b.languageName().toLower(); }); endInsertRows(); } From 8a4f1c66f83a339d33ab0ba0076d8c1141055067 Mon Sep 17 00:00:00 2001 From: ErogigGit Date: Sat, 24 Sep 2022 22:37:51 +0200 Subject: [PATCH 236/273] Allow double clicking to mark for dowload Signed-off-by: Erogig --- launcher/ui/pages/modplatform/ModPage.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 986caa77..4fce0242 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -60,6 +60,7 @@ ModPage::ModPage(ModDownloadDialog* dialog, BaseInstance* instance, ModAPI* api) connect(ui->searchButton, &QPushButton::clicked, this, &ModPage::triggerSearch); connect(ui->modFilterButton, &QPushButton::clicked, this, &ModPage::filterMods); + connect(ui->packView, &QListView::doubleClicked, this, &ModPage::onModSelected); m_search_timer.setTimerType(Qt::TimerType::CoarseTimer); m_search_timer.setSingleShot(true); From a1800ec23f016054b4fd346a86e2fcf8df448bb8 Mon Sep 17 00:00:00 2001 From: Trial97 Date: Sun, 25 Sep 2022 00:20:01 +0300 Subject: [PATCH 237/273] Prefer the system tomlplusplus Signed-off-by: Trial97 --- CMakeLists.txt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 364e16d6..109db157 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -312,7 +312,15 @@ endif() add_subdirectory(libraries/rainbow) # Qt extension for colors add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions add_subdirectory(libraries/classparser) # class parser library -add_subdirectory(libraries/tomlplusplus) # toml parser +if(NOT Launcher_FORCE_BUNDLED_LIBS) + find_package(tomlplusplus 3.2.0 QUIET) +endif() +if(NOT tomlplusplus_FOUND) + message(STATUS "Using bundled tomlplusplus") + add_subdirectory(libraries/tomlplusplus) # toml parser +else() + message(STATUS "Using system tomlplusplus") +endif() add_subdirectory(libraries/katabasis) # An OAuth2 library that tried to do too much add_subdirectory(libraries/gamemode) add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API From 3d4feeec8d9e0f29de81c4e827724e4dd8b28077 Mon Sep 17 00:00:00 2001 From: ErogigGit Date: Sat, 24 Sep 2022 23:31:30 +0200 Subject: [PATCH 238/273] DCO Remediation Commit for ErogigGit I, ErogigGit , hereby add my Signed-off-by to this commit: 8a4f1c66f83a339d33ab0ba0076d8c1141055067 Signed-off-by: ErogigGit From ed261f0af98b67f1da4f6247c4b422000575bb45 Mon Sep 17 00:00:00 2001 From: Alexandru Ionut Tripon Date: Sun, 25 Sep 2022 20:38:55 +0300 Subject: [PATCH 239/273] Update launcher/minecraft/mod/tasks/LocalModParseTask.cpp Co-authored-by: flow Signed-off-by: Alexandru Ionut Tripon --- launcher/minecraft/mod/tasks/LocalModParseTask.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp index ae8e95ff..a694e7b2 100644 --- a/launcher/minecraft/mod/tasks/LocalModParseTask.cpp +++ b/launcher/minecraft/mod/tasks/LocalModParseTask.cpp @@ -93,7 +93,6 @@ ModDetails ReadMCModTOML(QByteArray contents) { ModDetails details; - // auto tomlData = toml::parse(contents.toStdString()); toml::table tomlData; #if TOML_EXCEPTIONS try { From 9ff364b0d3c12f9138037cce585c03c850721b82 Mon Sep 17 00:00:00 2001 From: timoreo Date: Mon, 26 Sep 2022 11:50:31 +0200 Subject: [PATCH 240/273] huge nit: added const refs, everywhere Signed-off-by: timoreo --- launcher/InstanceList.cpp | 4 ++-- launcher/InstanceList.h | 4 ++-- launcher/InstanceTask.cpp | 2 +- launcher/InstanceTask.h | 2 +- launcher/minecraft/VanillaInstanceCreationTask.cpp | 4 +++- launcher/minecraft/VanillaInstanceCreationTask.h | 4 +++- launcher/modplatform/flame/FlameAPI.cpp | 2 +- launcher/modplatform/flame/FlameAPI.h | 2 +- .../modplatform/flame/FlameInstanceCreationTask.cpp | 4 ++-- launcher/modplatform/flame/FlameInstanceCreationTask.h | 2 +- launcher/modplatform/helpers/OverrideUtils.cpp | 4 ++-- launcher/modplatform/helpers/OverrideUtils.h | 4 ++-- .../modrinth/ModrinthInstanceCreationTask.cpp | 10 +++++----- .../modrinth/ModrinthInstanceCreationTask.h | 2 +- launcher/ui/dialogs/NewInstanceDialog.cpp | 7 ++++--- launcher/ui/dialogs/NewInstanceDialog.h | 4 ++-- 16 files changed, 33 insertions(+), 28 deletions(-) diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 6b0e3a4b..cebd70d7 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -535,7 +535,7 @@ InstancePtr InstanceList::getInstanceById(QString instId) const return InstancePtr(); } -InstancePtr InstanceList::getInstanceByManagedName(QString managed_name) const +InstancePtr InstanceList::getInstanceByManagedName(const QString& managed_name) const { if (managed_name.isEmpty()) return {}; @@ -880,7 +880,7 @@ QString InstanceList::getStagedInstancePath() return path; } -bool InstanceList::commitStagedInstance(QString path, InstanceName const& instanceName, QString groupName, bool should_override) +bool InstanceList::commitStagedInstance(const QString& path, InstanceName const& instanceName, const QString& groupName, bool should_override) { QDir dir; QString instID; diff --git a/launcher/InstanceList.h b/launcher/InstanceList.h index df11b55a..3673298f 100644 --- a/launcher/InstanceList.h +++ b/launcher/InstanceList.h @@ -104,7 +104,7 @@ public: /* O(n) */ InstancePtr getInstanceById(QString id) const; /* O(n) */ - InstancePtr getInstanceByManagedName(QString managed_name) const; + InstancePtr getInstanceByManagedName(const QString& managed_name) const; QModelIndex getInstanceIndexById(const QString &id) const; QStringList getGroups(); bool isGroupCollapsed(const QString &groupName); @@ -133,7 +133,7 @@ public: * should_override is used when another similar instance already exists, and we want to override it * - for instance, when updating it. */ - bool commitStagedInstance(QString keyPath, const InstanceName& instanceName, QString groupName, bool should_override); + bool commitStagedInstance(const QString& keyPath, const InstanceName& instanceName, const QString& groupName, bool should_override); /** * Destroy a previously created staging area given by @keyPath - used when creation fails. diff --git a/launcher/InstanceTask.cpp b/launcher/InstanceTask.cpp index da280731..55a44fd3 100644 --- a/launcher/InstanceTask.cpp +++ b/launcher/InstanceTask.cpp @@ -2,7 +2,7 @@ #include "ui/dialogs/CustomMessageBox.h" -InstanceNameChange askForChangingInstanceName(QWidget* parent, QString old_name, QString new_name) +InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& old_name, const QString& new_name) { auto dialog = CustomMessageBox::selectable(parent, QObject::tr("Change instance name"), diff --git a/launcher/InstanceTask.h b/launcher/InstanceTask.h index 0987b557..e35533fc 100644 --- a/launcher/InstanceTask.h +++ b/launcher/InstanceTask.h @@ -5,7 +5,7 @@ /* Helpers */ enum class InstanceNameChange { ShouldChange, ShouldKeep }; -[[nodiscard]] InstanceNameChange askForChangingInstanceName(QWidget* parent, QString old_name, QString new_name); +[[nodiscard]] InstanceNameChange askForChangingInstanceName(QWidget* parent, const QString& old_name, const QString& new_name); struct InstanceName { public: diff --git a/launcher/minecraft/VanillaInstanceCreationTask.cpp b/launcher/minecraft/VanillaInstanceCreationTask.cpp index fb11379c..c45daa9a 100644 --- a/launcher/minecraft/VanillaInstanceCreationTask.cpp +++ b/launcher/minecraft/VanillaInstanceCreationTask.cpp @@ -1,12 +1,14 @@ #include "VanillaInstanceCreationTask.h" +#include + #include "FileSystem.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" #include "settings/INISettingsObject.h" VanillaCreationTask::VanillaCreationTask(BaseVersionPtr version, QString loader, BaseVersionPtr loader_version) - : InstanceCreationTask(), m_version(version), m_using_loader(true), m_loader(loader), m_loader_version(loader_version) + : InstanceCreationTask(), m_version(std::move(version)), m_using_loader(true), m_loader(std::move(loader)), m_loader_version(std::move(loader_version)) {} bool VanillaCreationTask::createInstance() diff --git a/launcher/minecraft/VanillaInstanceCreationTask.h b/launcher/minecraft/VanillaInstanceCreationTask.h index 540ecb70..7a37bbd6 100644 --- a/launcher/minecraft/VanillaInstanceCreationTask.h +++ b/launcher/minecraft/VanillaInstanceCreationTask.h @@ -2,10 +2,12 @@ #include "InstanceCreationTask.h" +#include + class VanillaCreationTask final : public InstanceCreationTask { Q_OBJECT public: - VanillaCreationTask(BaseVersionPtr version) : InstanceCreationTask(), m_version(version) {} + VanillaCreationTask(BaseVersionPtr version) : InstanceCreationTask(), m_version(std::move(version)) {} VanillaCreationTask(BaseVersionPtr version, QString loader, BaseVersionPtr loader_version); bool createInstance() override; diff --git a/launcher/modplatform/flame/FlameAPI.cpp b/launcher/modplatform/flame/FlameAPI.cpp index f8f50dc6..4d71da21 100644 --- a/launcher/modplatform/flame/FlameAPI.cpp +++ b/launcher/modplatform/flame/FlameAPI.cpp @@ -184,7 +184,7 @@ auto FlameAPI::getProjects(QStringList addonIds, QByteArray* response) const -> return netJob; } -auto FlameAPI::getFiles(QStringList fileIds, QByteArray* response) const -> NetJob* +auto FlameAPI::getFiles(const QStringList& fileIds, QByteArray* response) const -> NetJob* { auto* netJob = new NetJob(QString("Flame::GetFiles"), APPLICATION->network()); diff --git a/launcher/modplatform/flame/FlameAPI.h b/launcher/modplatform/flame/FlameAPI.h index 5e6166b9..4c6ca64c 100644 --- a/launcher/modplatform/flame/FlameAPI.h +++ b/launcher/modplatform/flame/FlameAPI.h @@ -12,7 +12,7 @@ class FlameAPI : public NetworkModAPI { auto getLatestVersion(VersionSearchArgs&& args) -> ModPlatform::IndexedVersion; auto getProjects(QStringList addonIds, QByteArray* response) const -> NetJob* override; - auto getFiles(QStringList fileIds, QByteArray* response) const -> NetJob*; + auto getFiles(const QStringList& fileIds, QByteArray* response) const -> NetJob*; private: inline auto getSortFieldInt(QString sortString) const -> int diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp index 69a41e55..48ac02e0 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.cpp +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.cpp @@ -126,7 +126,7 @@ bool FlameCreationTask::updateInstance() // TODO: Currently 'overrides' will always override the stuff on update. How do we preserve unchanged overrides? // FIXME: We may want to do something about disabled mods. auto old_overrides = Override::readOverrides("overrides", old_index_folder); - for (auto entry : old_overrides) { + for (const auto& entry : old_overrides) { if (entry.isEmpty()) continue; qDebug() << "Scheduling" << entry << "for removal"; @@ -320,7 +320,7 @@ bool FlameCreationTask::createInstance() qDebug() << "Found jarmods:"; QDir jarmodsDir(jarmodsPath); QStringList jarMods; - for (auto info : jarmodsDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) { + for (const auto& info : jarmodsDir.entryInfoList(QDir::NoDotAndDotDot | QDir::Files)) { qDebug() << info.fileName(); jarMods.push_back(info.absoluteFilePath()); } diff --git a/launcher/modplatform/flame/FlameInstanceCreationTask.h b/launcher/modplatform/flame/FlameInstanceCreationTask.h index ccb5f827..ded0e2ce 100644 --- a/launcher/modplatform/flame/FlameInstanceCreationTask.h +++ b/launcher/modplatform/flame/FlameInstanceCreationTask.h @@ -14,7 +14,7 @@ class FlameCreationTask final : public InstanceCreationTask { Q_OBJECT public: - FlameCreationTask(QString staging_path, SettingsObjectPtr global_settings, QWidget* parent) + FlameCreationTask(const QString& staging_path, SettingsObjectPtr global_settings, QWidget* parent) : InstanceCreationTask(), m_parent(parent) { setStagingPath(staging_path); diff --git a/launcher/modplatform/helpers/OverrideUtils.cpp b/launcher/modplatform/helpers/OverrideUtils.cpp index 3496a286..65b5f760 100644 --- a/launcher/modplatform/helpers/OverrideUtils.cpp +++ b/launcher/modplatform/helpers/OverrideUtils.cpp @@ -6,7 +6,7 @@ namespace Override { -void createOverrides(QString name, QString parent_folder, QString override_path) +void createOverrides(const QString& name, const QString& parent_folder, const QString& override_path) { QString file_path(FS::PathCombine(parent_folder, name + ".txt")); if (QFile::exists(file_path)) @@ -33,7 +33,7 @@ void createOverrides(QString name, QString parent_folder, QString override_path) file.close(); } -QStringList readOverrides(QString name, QString parent_folder) +QStringList readOverrides(const QString& name, const QString& parent_folder) { QString file_path(FS::PathCombine(parent_folder, name + ".txt")); diff --git a/launcher/modplatform/helpers/OverrideUtils.h b/launcher/modplatform/helpers/OverrideUtils.h index 73f059d6..536261a2 100644 --- a/launcher/modplatform/helpers/OverrideUtils.h +++ b/launcher/modplatform/helpers/OverrideUtils.h @@ -9,12 +9,12 @@ namespace Override { * * If there's already an existing such file, it will be ovewritten. */ -void createOverrides(QString name, QString parent_folder, QString override_path); +void createOverrides(const QString& name, const QString& parent_folder, const QString& override_path); /** This reads an existing overrides archive, returning a list of overrides. * * If there's no such file in `parent_folder`, it will return an empty list. */ -QStringList readOverrides(QString name, QString parent_folder); +QStringList readOverrides(const QString& name, const QString& parent_folder); } // namespace Override diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index 2cb6e786..f9709551 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -121,7 +121,7 @@ bool ModrinthCreationTask::updateInstance() // TODO: Currently 'overrides' will always override the stuff on update. How do we preserve unchanged overrides? // FIXME: We may want to do something about disabled mods. auto old_overrides = Override::readOverrides("overrides", old_index_folder); - for (auto entry : old_overrides) { + for (const auto& entry : old_overrides) { if (entry.isEmpty()) continue; qDebug() << "Scheduling" << entry << "for removal"; @@ -129,7 +129,7 @@ bool ModrinthCreationTask::updateInstance() } auto old_client_overrides = Override::readOverrides("client-overrides", old_index_folder); - for (auto entry : old_overrides) { + for (const auto& entry : old_overrides) { if (entry.isEmpty()) continue; qDebug() << "Scheduling" << entry << "for removal"; @@ -235,7 +235,7 @@ bool ModrinthCreationTask::createInstance() dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); m_files_job->addNetAction(dl); - if (file.downloads.size() > 0) { + if (!file.downloads.empty()) { // FIXME: This really needs to be put into a ConcurrentTask of // MultipleOptionsTask's , once those exist :) connect(dl.get(), &NetAction::failed, [this, &file, path, dl] { @@ -281,7 +281,7 @@ bool ModrinthCreationTask::createInstance() return ended_well; } -bool ModrinthCreationTask::parseManifest(QString index_path, std::vector& files, bool set_managed_info, bool show_optional_dialog) +bool ModrinthCreationTask::parseManifest(const QString& index_path, std::vector& files, bool set_managed_info, bool show_optional_dialog) { try { auto doc = Json::requireDocument(index_path); @@ -300,7 +300,7 @@ bool ModrinthCreationTask::parseManifest(QString index_path, std::vector(obj, "files", "modrinth.index.json"); bool had_optional = false; - for (auto modInfo : jsonFiles) { + for (const auto& modInfo : jsonFiles) { Modrinth::File file; file.path = Json::requireString(modInfo, "path"); diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h index 01d0d1bf..e459aadf 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.h @@ -27,7 +27,7 @@ class ModrinthCreationTask final : public InstanceCreationTask { bool createInstance() override; private: - bool parseManifest(QString, std::vector&, bool set_managed_info = true, bool show_optional_dialog = true); + bool parseManifest(const QString&, std::vector&, bool set_managed_info = true, bool show_optional_dialog = true); QString getManagedPackID() const; private: diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp index 04fecbde..d203795a 100644 --- a/launcher/ui/dialogs/NewInstanceDialog.cpp +++ b/launcher/ui/dialogs/NewInstanceDialog.cpp @@ -51,6 +51,7 @@ #include #include #include +#include #include "ui/widgets/PageContainer.h" #include "ui/pages/modplatform/VanillaPage.h" @@ -177,7 +178,7 @@ NewInstanceDialog::~NewInstanceDialog() delete ui; } -void NewInstanceDialog::setSuggestedPack(QString name, InstanceTask* task) +void NewInstanceDialog::setSuggestedPack(const QString& name, InstanceTask* task) { creationTask.reset(task); @@ -193,12 +194,12 @@ void NewInstanceDialog::setSuggestedPack(QString name, InstanceTask* task) m_buttons->button(QDialogButtonBox::Ok)->setEnabled(allowOK); } -void NewInstanceDialog::setSuggestedPack(QString name, QString version, InstanceTask* task) +void NewInstanceDialog::setSuggestedPack(const QString& name, QString version, InstanceTask* task) { creationTask.reset(task); ui->instNameTextBox->setPlaceholderText(name); - importVersion = version; + importVersion = std::move(version); if (!task) { ui->iconButton->setIcon(APPLICATION->icons()->getIcon("default")); diff --git a/launcher/ui/dialogs/NewInstanceDialog.h b/launcher/ui/dialogs/NewInstanceDialog.h index 185ab9e6..961f512e 100644 --- a/launcher/ui/dialogs/NewInstanceDialog.h +++ b/launcher/ui/dialogs/NewInstanceDialog.h @@ -60,8 +60,8 @@ public: void updateDialogState(); - void setSuggestedPack(QString name = QString(), InstanceTask * task = nullptr); - void setSuggestedPack(QString name, QString version, InstanceTask * task = nullptr); + void setSuggestedPack(const QString& name = QString(), InstanceTask * task = nullptr); + void setSuggestedPack(const QString& name, QString version, InstanceTask * task = nullptr); void setSuggestedIconFromFile(const QString &path, const QString &name); void setSuggestedIcon(const QString &key); From dd6f670dec7dfd1a9ad6f4595ad5447ac735c737 Mon Sep 17 00:00:00 2001 From: timoreo Date: Mon, 26 Sep 2022 11:50:55 +0200 Subject: [PATCH 241/273] fix: Fixed memory leak Signed-off-by: timoreo --- .../modrinth/ModrinthInstanceCreationTask.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp index f9709551..ddeea224 100644 --- a/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp +++ b/launcher/modplatform/modrinth/ModrinthInstanceCreationTask.cpp @@ -238,11 +238,12 @@ bool ModrinthCreationTask::createInstance() if (!file.downloads.empty()) { // FIXME: This really needs to be put into a ConcurrentTask of // MultipleOptionsTask's , once those exist :) - connect(dl.get(), &NetAction::failed, [this, &file, path, dl] { - auto dl = Net::Download::makeFile(file.downloads.dequeue(), path); - dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); - m_files_job->addNetAction(dl); - dl->succeeded(); + auto param = dl.toWeakRef(); + connect(dl.get(), &NetAction::failed, [this, &file, path, param] { + auto ndl = Net::Download::makeFile(file.downloads.dequeue(), path); + ndl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); + m_files_job->addNetAction(ndl); + if (auto shared = param.lock()) shared->succeeded(); }); } } From 1cdadafdf8b59988fb8d89a29d0698620e09dfb5 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Mon, 26 Sep 2022 12:30:49 +0200 Subject: [PATCH 242/273] refactor: use QCommandLineParser instead Signed-off-by: Sefa Eyeoglu --- launcher/Application.cpp | 95 ++------- launcher/Commandline.cpp | 408 --------------------------------------- launcher/Commandline.h | 213 -------------------- 3 files changed, 20 insertions(+), 696 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 0d1db11c..7ac36e71 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -78,6 +78,7 @@ #include #include +#include #include #include #include @@ -110,7 +111,6 @@ #include "translations/TranslationsModel.h" #include "meta/Index.h" -#include #include #include #include @@ -136,8 +136,6 @@ static const QLatin1String liveCheckFile("live.check"); -using namespace Commandline; - #define MACOS_HINT "If you are on macOS Sierra, you might have to move the app to your /Applications or ~/Applications folder. "\ "This usually fixes the problem and you can move the application elsewhere afterwards.\n"\ "\n" @@ -242,80 +240,27 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) this->setQuitOnLastWindowClosed(false); // Commandline parsing - QHash args; - { - Parser parser(FlagStyle::GNU, ArgumentStyle::SpaceAndEquals); + QCommandLineParser parser; + parser.setApplicationDescription(BuildConfig.LAUNCHER_NAME); - // --help - parser.addSwitch("help"); - parser.addShortOpt("help", 'h'); - parser.addDocumentation("help", "Display this help and exit."); - // --version - parser.addSwitch("version"); - parser.addShortOpt("version", 'V'); - parser.addDocumentation("version", "Display program version and exit."); - // --dir - parser.addOption("dir"); - parser.addShortOpt("dir", 'd'); - parser.addDocumentation("dir", "Use the supplied folder as application root instead of the binary location (use '.' for current)"); - // --launch - parser.addOption("launch"); - parser.addShortOpt("launch", 'l'); - parser.addDocumentation("launch", "Launch the specified instance (by instance ID)"); - // --server - parser.addOption("server"); - parser.addShortOpt("server", 's'); - parser.addDocumentation("server", "Join the specified server on launch (only valid in combination with --launch)"); - // --profile - parser.addOption("profile"); - parser.addShortOpt("profile", 'a'); - parser.addDocumentation("profile", "Use the account specified by its profile name (only valid in combination with --launch)"); - // --alive - parser.addSwitch("alive"); - parser.addDocumentation("alive", "Write a small '" + liveCheckFile + "' file after the launcher starts"); - // --import - parser.addOption("import"); - parser.addShortOpt("import", 'I'); - parser.addDocumentation("import", "Import instance from specified zip (local path or URL)"); + parser.addOptions({ + {{"d", "dir"}, "Use a custom path as application root (use '.' for current directory)", "directory"}, + {{"l", "launch"}, "Launch the specified instance (by instance ID)", "instance"}, + {{"s", "server"}, "Join the specified server on launch (only valid in combination with --launch)", "address"}, + {{"a", "profile"}, "Use the account specified by its profile name (only valid in combination with --launch)", "profile"}, + {"alive", "Write a small '" + liveCheckFile + "' file after the launcher starts"}, + {{"I", "import"}, "Import instance from specified zip (local path or URL)", "file"} + }); + parser.addHelpOption(); + parser.addVersionOption(); - // parse the arguments - try - { - args = parser.parse(arguments()); - } - catch (const ParsingError &e) - { - std::cerr << "CommandLineError: " << e.what() << std::endl; - if(argc > 0) - std::cerr << "Try '" << argv[0] << " -h' to get help on command line parameters." - << std::endl; - m_status = Application::Failed; - return; - } + parser.process(arguments()); - // display help and exit - if (args["help"].toBool()) - { - std::cout << qPrintable(parser.compileHelp(arguments()[0])); - m_status = Application::Succeeded; - return; - } - - // display version and exit - if (args["version"].toBool()) - { - std::cout << "Version " << BuildConfig.printableVersionString().toStdString() << std::endl; - std::cout << "Git " << BuildConfig.GIT_COMMIT.toStdString() << std::endl; - m_status = Application::Succeeded; - return; - } - } - - m_instanceIdToLaunch = args["launch"].toString(); - m_serverToJoin = args["server"].toString(); - m_profileToUse = args["profile"].toString(); - m_liveCheck = args["alive"].toBool(); - m_zipToImport = args["import"].toUrl(); + m_instanceIdToLaunch = parser.value("launch"); + m_serverToJoin = parser.value("server"); + m_profileToUse = parser.value("profile"); + m_liveCheck = parser.isSet("alive"); + m_zipToImport = parser.value("import"); // error if --launch is missing with --server or --profile if((!m_serverToJoin.isEmpty() || !m_profileToUse.isEmpty()) && m_instanceIdToLaunch.isEmpty()) @@ -346,7 +291,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) QString adjustedBy; QString dataPath; // change folder - QString dirParam = args["dir"].toString(); + QString dirParam = parser.value("dir"); if (!dirParam.isEmpty()) { // the dir param. it makes multimc data path point to whatever the user specified diff --git a/launcher/Commandline.cpp b/launcher/Commandline.cpp index 8e7356bb..6d97918d 100644 --- a/launcher/Commandline.cpp +++ b/launcher/Commandline.cpp @@ -92,412 +92,4 @@ QStringList splitArgs(QString args) argv << current; return argv; } - -Parser::Parser(FlagStyle::Enum flagStyle, ArgumentStyle::Enum argStyle) -{ - m_flagStyle = flagStyle; - m_argStyle = argStyle; -} - -// styles setter/getter -void Parser::setArgumentStyle(ArgumentStyle::Enum style) -{ - m_argStyle = style; -} -ArgumentStyle::Enum Parser::argumentStyle() -{ - return m_argStyle; -} - -void Parser::setFlagStyle(FlagStyle::Enum style) -{ - m_flagStyle = style; -} -FlagStyle::Enum Parser::flagStyle() -{ - return m_flagStyle; -} - -// setup methods -void Parser::addSwitch(QString name, bool def) -{ - if (m_params.contains(name)) - throw "Name not unique"; - - OptionDef *param = new OptionDef; - param->type = otSwitch; - param->name = name; - param->metavar = QString("<%1>").arg(name); - param->def = def; - - m_options[name] = param; - m_params[name] = (CommonDef *)param; - m_optionList.append(param); -} - -void Parser::addOption(QString name, QVariant def) -{ - if (m_params.contains(name)) - throw "Name not unique"; - - OptionDef *param = new OptionDef; - param->type = otOption; - param->name = name; - param->metavar = QString("<%1>").arg(name); - param->def = def; - - m_options[name] = param; - m_params[name] = (CommonDef *)param; - m_optionList.append(param); -} - -void Parser::addArgument(QString name, bool required, QVariant def) -{ - if (m_params.contains(name)) - throw "Name not unique"; - - PositionalDef *param = new PositionalDef; - param->name = name; - param->def = def; - param->required = required; - param->metavar = name; - - m_positionals.append(param); - m_params[name] = (CommonDef *)param; -} - -void Parser::addDocumentation(QString name, QString doc, QString metavar) -{ - if (!m_params.contains(name)) - throw "Name does not exist"; - - CommonDef *param = m_params[name]; - param->doc = doc; - if (!metavar.isNull()) - param->metavar = metavar; -} - -void Parser::addShortOpt(QString name, QChar flag) -{ - if (!m_params.contains(name)) - throw "Name does not exist"; - if (!m_options.contains(name)) - throw "Name is not an Option or Swtich"; - - OptionDef *param = m_options[name]; - m_flags[flag] = param; - param->flag = flag; -} - -// help methods -QString Parser::compileHelp(QString progName, int helpIndent, bool useFlags) -{ - QStringList help; - help << compileUsage(progName, useFlags) << "\r\n"; - - // positionals - if (!m_positionals.isEmpty()) - { - help << "\r\n"; - help << "Positional arguments:\r\n"; - QListIterator it2(m_positionals); - while (it2.hasNext()) - { - PositionalDef *param = it2.next(); - help << " " << param->metavar; - help << " " << QString(helpIndent - param->metavar.length() - 1, ' '); - help << param->doc << "\r\n"; - } - } - - // Options - if (!m_optionList.isEmpty()) - { - help << "\r\n"; - QString optPrefix, flagPrefix; - getPrefix(optPrefix, flagPrefix); - - help << "Options & Switches:\r\n"; - QListIterator it(m_optionList); - while (it.hasNext()) - { - OptionDef *option = it.next(); - help << " "; - int nameLength = optPrefix.length() + option->name.length(); - if (!option->flag.isNull()) - { - nameLength += 3 + flagPrefix.length(); - help << flagPrefix << option->flag << ", "; - } - help << optPrefix << option->name; - if (option->type == otOption) - { - QString arg = QString("%1%2").arg( - ((m_argStyle == ArgumentStyle::Equals) ? "=" : " "), option->metavar); - nameLength += arg.length(); - help << arg; - } - help << " " << QString(helpIndent - nameLength - 1, ' '); - help << option->doc << "\r\n"; - } - } - - return help.join(""); -} - -QString Parser::compileUsage(QString progName, bool useFlags) -{ - QStringList usage; - usage << "Usage: " << progName; - - QString optPrefix, flagPrefix; - getPrefix(optPrefix, flagPrefix); - - // options - QListIterator it(m_optionList); - while (it.hasNext()) - { - OptionDef *option = it.next(); - usage << " ["; - if (!option->flag.isNull() && useFlags) - usage << flagPrefix << option->flag; - else - usage << optPrefix << option->name; - if (option->type == otOption) - usage << ((m_argStyle == ArgumentStyle::Equals) ? "=" : " ") << option->metavar; - usage << "]"; - } - - // arguments - QListIterator it2(m_positionals); - while (it2.hasNext()) - { - PositionalDef *param = it2.next(); - usage << " " << (param->required ? "<" : "["); - usage << param->metavar; - usage << (param->required ? ">" : "]"); - } - - return usage.join(""); -} - -// parsing -QHash Parser::parse(QStringList argv) -{ - QHash map; - - QStringListIterator it(argv); - QString programName = it.next(); - - QString optionPrefix; - QString flagPrefix; - QListIterator positionals(m_positionals); - QStringList expecting; - - getPrefix(optionPrefix, flagPrefix); - - while (it.hasNext()) - { - QString arg = it.next(); - - if (!expecting.isEmpty()) - // we were expecting an argument - { - QString name = expecting.first(); -/* - if (map.contains(name)) - throw ParsingError( - QString("Option %2%1 was given multiple times").arg(name, optionPrefix)); -*/ - map[name] = QVariant(arg); - - expecting.removeFirst(); - continue; - } - - if (arg.startsWith(optionPrefix)) - // we have an option - { - // qDebug("Found option %s", qPrintable(arg)); - - QString name = arg.mid(optionPrefix.length()); - QString equals; - - if ((m_argStyle == ArgumentStyle::Equals || - m_argStyle == ArgumentStyle::SpaceAndEquals) && - name.contains("=")) - { - int i = name.indexOf("="); - equals = name.mid(i + 1); - name = name.left(i); - } - - if (m_options.contains(name)) - { - /* - if (map.contains(name)) - throw ParsingError(QString("Option %2%1 was given multiple times") - .arg(name, optionPrefix)); -*/ - OptionDef *option = m_options[name]; - if (option->type == otSwitch) - map[name] = true; - else // if (option->type == otOption) - { - if (m_argStyle == ArgumentStyle::Space) - expecting.append(name); - else if (!equals.isNull()) - map[name] = equals; - else if (m_argStyle == ArgumentStyle::SpaceAndEquals) - expecting.append(name); - else - throw ParsingError(QString("Option %2%1 reqires an argument.") - .arg(name, optionPrefix)); - } - - continue; - } - - throw ParsingError(QString("Unknown Option %2%1").arg(name, optionPrefix)); - } - - if (arg.startsWith(flagPrefix)) - // we have (a) flag(s) - { - // qDebug("Found flags %s", qPrintable(arg)); - - QString flags = arg.mid(flagPrefix.length()); - QString equals; - - if ((m_argStyle == ArgumentStyle::Equals || - m_argStyle == ArgumentStyle::SpaceAndEquals) && - flags.contains("=")) - { - int i = flags.indexOf("="); - equals = flags.mid(i + 1); - flags = flags.left(i); - } - - for (int i = 0; i < flags.length(); i++) - { - QChar flag = flags.at(i); - - if (!m_flags.contains(flag)) - throw ParsingError(QString("Unknown flag %2%1").arg(flag, flagPrefix)); - - OptionDef *option = m_flags[flag]; -/* - if (map.contains(option->name)) - throw ParsingError(QString("Option %2%1 was given multiple times") - .arg(option->name, optionPrefix)); -*/ - if (option->type == otSwitch) - map[option->name] = true; - else // if (option->type == otOption) - { - if (m_argStyle == ArgumentStyle::Space) - expecting.append(option->name); - else if (!equals.isNull()) - if (i == flags.length() - 1) - map[option->name] = equals; - else - throw ParsingError(QString("Flag %4%2 of Argument-requiring Option " - "%1 not last flag in %4%3") - .arg(option->name, flag, flags, flagPrefix)); - else if (m_argStyle == ArgumentStyle::SpaceAndEquals) - expecting.append(option->name); - else - throw ParsingError(QString("Option %1 reqires an argument. (flag %3%2)") - .arg(option->name, flag, flagPrefix)); - } - } - - continue; - } - - // must be a positional argument - if (!positionals.hasNext()) - throw ParsingError(QString("Don't know what to do with '%1'").arg(arg)); - - PositionalDef *param = positionals.next(); - - map[param->name] = arg; - } - - // check if we're missing something - if (!expecting.isEmpty()) - throw ParsingError(QString("Was still expecting arguments for %2%1").arg( - expecting.join(QString(", ") + optionPrefix), optionPrefix)); - - while (positionals.hasNext()) - { - PositionalDef *param = positionals.next(); - if (param->required) - throw ParsingError( - QString("Missing required positional argument '%1'").arg(param->name)); - else - map[param->name] = param->def; - } - - // fill out gaps - QListIterator iter(m_optionList); - while (iter.hasNext()) - { - OptionDef *option = iter.next(); - if (!map.contains(option->name)) - map[option->name] = option->def; - } - - return map; -} - -// clear defs -void Parser::clear() -{ - m_flags.clear(); - m_params.clear(); - m_options.clear(); - - QMutableListIterator it(m_optionList); - while (it.hasNext()) - { - OptionDef *option = it.next(); - it.remove(); - delete option; - } - - QMutableListIterator it2(m_positionals); - while (it2.hasNext()) - { - PositionalDef *arg = it2.next(); - it2.remove(); - delete arg; - } -} - -// Destructor -Parser::~Parser() -{ - clear(); -} - -// getPrefix -void Parser::getPrefix(QString &opt, QString &flag) -{ - if (m_flagStyle == FlagStyle::Windows) - opt = flag = "/"; - else if (m_flagStyle == FlagStyle::Unix) - opt = flag = "-"; - // else if (m_flagStyle == FlagStyle::GNU) - else - { - opt = "--"; - flag = "-"; - } -} - -// ParsingError -ParsingError::ParsingError(const QString &what) : std::runtime_error(what.toStdString()) -{ -} } diff --git a/launcher/Commandline.h b/launcher/Commandline.h index a4e7aa61..8bd79180 100644 --- a/launcher/Commandline.h +++ b/launcher/Commandline.h @@ -17,12 +17,7 @@ #pragma once -#include -#include - #include -#include -#include #include /** @@ -39,212 +34,4 @@ namespace Commandline * @return a QStringList containing all arguments */ QStringList splitArgs(QString args); - -/** - * @brief The FlagStyle enum - * Specifies how flags are decorated - */ - -namespace FlagStyle -{ -enum Enum -{ - GNU, /**< --option and -o (GNU Style) */ - Unix, /**< -option and -o (Unix Style) */ - Windows, /**< /option and /o (Windows Style) */ -#ifdef Q_OS_WIN32 - Default = Windows -#else - Default = GNU -#endif -}; -} - -/** - * @brief The ArgumentStyle enum - */ -namespace ArgumentStyle -{ -enum Enum -{ - Space, /**< --option value */ - Equals, /**< --option=value */ - SpaceAndEquals, /**< --option[= ]value */ -#ifdef Q_OS_WIN32 - Default = Equals -#else - Default = SpaceAndEquals -#endif -}; -} - -/** - * @brief The ParsingError class - */ -class ParsingError : public std::runtime_error -{ -public: - ParsingError(const QString &what); -}; - -/** - * @brief The Parser class - */ -class Parser -{ -public: - /** - * @brief Parser constructor - * @param flagStyle the FlagStyle to use in this Parser - * @param argStyle the ArgumentStyle to use in this Parser - */ - Parser(FlagStyle::Enum flagStyle = FlagStyle::Default, - ArgumentStyle::Enum argStyle = ArgumentStyle::Default); - - /** - * @brief set the flag style - * @param style - */ - void setFlagStyle(FlagStyle::Enum style); - - /** - * @brief get the flag style - * @return - */ - FlagStyle::Enum flagStyle(); - - /** - * @brief set the argument style - * @param style - */ - void setArgumentStyle(ArgumentStyle::Enum style); - - /** - * @brief get the argument style - * @return - */ - ArgumentStyle::Enum argumentStyle(); - - /** - * @brief define a boolean switch - * @param name the parameter name - * @param def the default value - */ - void addSwitch(QString name, bool def = false); - - /** - * @brief define an option that takes an additional argument - * @param name the parameter name - * @param def the default value - */ - void addOption(QString name, QVariant def = QVariant()); - - /** - * @brief define a positional argument - * @param name the parameter name - * @param required wether this argument is required - * @param def the default value - */ - void addArgument(QString name, bool required = true, QVariant def = QVariant()); - - /** - * @brief adds a flag to an existing parameter - * @param name the (existing) parameter name - * @param flag the flag character - * @see addSwitch addArgument addOption - * Note: any one parameter can only have one flag - */ - void addShortOpt(QString name, QChar flag); - - /** - * @brief adds documentation to a Parameter - * @param name the parameter name - * @param metavar a string to be displayed as placeholder for the value - * @param doc a QString containing the documentation - * Note: on positional arguments, metavar replaces the name as displayed. - * on options , metavar replaces the value placeholder - */ - void addDocumentation(QString name, QString doc, QString metavar = QString()); - - /** - * @brief generate a help message - * @param progName the program name to use in the help message - * @param helpIndent how much the parameter documentation should be indented - * @param flagsInUsage whether we should use flags instead of options in the usage - * @return a help message - */ - QString compileHelp(QString progName, int helpIndent = 22, bool flagsInUsage = true); - - /** - * @brief generate a short usage message - * @param progName the program name to use in the usage message - * @param useFlags whether we should use flags instead of options - * @return a usage message - */ - QString compileUsage(QString progName, bool useFlags = true); - - /** - * @brief parse - * @param argv a QStringList containing the program ARGV - * @return a QHash mapping argument names to their values - */ - QHash parse(QStringList argv); - - /** - * @brief clear all definitions - */ - void clear(); - - ~Parser(); - -private: - FlagStyle::Enum m_flagStyle; - ArgumentStyle::Enum m_argStyle; - - enum OptionType - { - otSwitch, - otOption - }; - - // Important: the common part MUST BE COMMON ON ALL THREE structs - struct CommonDef - { - QString name; - QString doc; - QString metavar; - QVariant def; - }; - - struct OptionDef - { - // common - QString name; - QString doc; - QString metavar; - QVariant def; - // option - OptionType type; - QChar flag; - }; - - struct PositionalDef - { - // common - QString name; - QString doc; - QString metavar; - QVariant def; - // positional - bool required; - }; - - QHash m_options; - QHash m_flags; - QHash m_params; - QList m_positionals; - QList m_optionList; - - void getPrefix(QString &opt, QString &flag); -}; } From 11c44c676c12cee5cf2e76303af2c8a791a44b77 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Mon, 26 Sep 2022 13:27:42 +0200 Subject: [PATCH 243/273] fix: remove unused MACOS_HINT Signed-off-by: Sefa Eyeoglu --- launcher/Application.cpp | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 7ac36e71..e08ea7f4 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -136,10 +136,6 @@ static const QLatin1String liveCheckFile("live.check"); -#define MACOS_HINT "If you are on macOS Sierra, you might have to move the app to your /Applications or ~/Applications folder. "\ - "This usually fixes the problem and you can move the application elsewhere afterwards.\n"\ - "\n" - namespace { void appDebugOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) { @@ -330,9 +326,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) QString( "The launcher data folder could not be created.\n" "\n" -#if defined(Q_OS_MAC) - MACOS_HINT -#endif "Make sure you have the right permissions to the launcher data folder and any folder needed to access it.\n" "(%1)\n" "\n" @@ -348,9 +341,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) QString( "The launcher data folder could not be opened.\n" "\n" -#if defined(Q_OS_MAC) - MACOS_HINT -#endif "Make sure you have the right permissions to the launcher data folder.\n" "(%1)\n" "\n" @@ -431,9 +421,6 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) QString( "The launcher couldn't create a log file - the data folder is not writable.\n" "\n" - #if defined(Q_OS_MAC) - MACOS_HINT - #endif "Make sure you have write permissions to the data folder.\n" "(%1)\n" "\n" From aad4f8d1f7ff5f777253d3cc114fd55f8333370f Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Mon, 26 Sep 2022 13:38:45 +0200 Subject: [PATCH 244/273] fix: update Git modules Signed-off-by: Sefa Eyeoglu --- .gitmodules | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitmodules b/.gitmodules index b29a0477..534ffd37 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,12 +1,9 @@ [submodule "depends/libnbtplusplus"] path = libraries/libnbtplusplus url = https://github.com/PolyMC/libnbtplusplus.git - pushurl = git@github.com:PolyMC/libnbtplusplus.git - [submodule "libraries/quazip"] path = libraries/quazip url = https://github.com/stachenov/quazip.git [submodule "libraries/tomlplusplus"] path = libraries/tomlplusplus url = https://github.com/marzer/tomlplusplus.git - shallow = true From 62841c5b238b86f5a01eac5c156f15d51e886597 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Mon, 26 Sep 2022 13:40:31 +0200 Subject: [PATCH 245/273] fix: pin tomlplusplus to latest release Signed-off-by: Sefa Eyeoglu --- libraries/tomlplusplus | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/tomlplusplus b/libraries/tomlplusplus index fb8ce803..4b166b69 160000 --- a/libraries/tomlplusplus +++ b/libraries/tomlplusplus @@ -1 +1 @@ -Subproject commit fb8ce80350bcde1d109e9b4d7a571bbc2f29a8bd +Subproject commit 4b166b69f28e70a416a1a04a98f365d2aeb90de8 From 7c6bb80cee123d7bdd140d2892a3f473f972ebc8 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Mon, 26 Sep 2022 13:43:26 +0200 Subject: [PATCH 246/273] fix: move toml++ find call Signed-off-by: Sefa Eyeoglu --- CMakeLists.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 109db157..46192414 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -192,6 +192,11 @@ if (Qt5_POSITION_INDEPENDENT_CODE) SET(CMAKE_POSITION_INDEPENDENT_CODE ON) endif() +# Find toml++ +if(NOT Launcher_FORCE_BUNDLED_LIBS) + find_package(tomlplusplus 3.2.0 QUIET) +endif() + ####################################### Program Info ####################################### set(Launcher_APP_BINARY_NAME "polymc" CACHE STRING "Name of the Launcher binary") @@ -312,9 +317,6 @@ endif() add_subdirectory(libraries/rainbow) # Qt extension for colors add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions add_subdirectory(libraries/classparser) # class parser library -if(NOT Launcher_FORCE_BUNDLED_LIBS) - find_package(tomlplusplus 3.2.0 QUIET) -endif() if(NOT tomlplusplus_FOUND) message(STATUS "Using bundled tomlplusplus") add_subdirectory(libraries/tomlplusplus) # toml parser From 0f59a1dde12f94eaff71f6f6b3d8d271c2dcce26 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Mon, 26 Sep 2022 15:42:45 +0200 Subject: [PATCH 247/273] fix(nix): add tomlplusplus Signed-off-by: Sefa Eyeoglu --- flake.lock | 19 ++++++++++++++++++- flake.nix | 7 ++++--- nix/default.nix | 6 ++++++ 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/flake.lock b/flake.lock index bfc9ac6d..a72286bb 100644 --- a/flake.lock +++ b/flake.lock @@ -52,7 +52,24 @@ "inputs": { "flake-compat": "flake-compat", "libnbtplusplus": "libnbtplusplus", - "nixpkgs": "nixpkgs" + "nixpkgs": "nixpkgs", + "tomlplusplus": "tomlplusplus" + } + }, + "tomlplusplus": { + "flake": false, + "locked": { + "lastModified": 1664034574, + "narHash": "sha256-EFMAl6tsTvkgK0DWC/pZfOIq06b2e5SnxJa1ngGRIQA=", + "owner": "marzer", + "repo": "tomlplusplus", + "rev": "8aa5c8b2a4ff2c440d4630addf64fa4f62146170", + "type": "github" + }, + "original": { + "owner": "marzer", + "repo": "tomlplusplus", + "type": "github" } } }, diff --git a/flake.nix b/flake.nix index 51bc1fda..93192725 100644 --- a/flake.nix +++ b/flake.nix @@ -5,9 +5,10 @@ nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; flake-compat = { url = "github:edolstra/flake-compat"; flake = false; }; libnbtplusplus = { url = "github:PolyMC/libnbtplusplus"; flake = false; }; + tomlplusplus = { url = "github:marzer/tomlplusplus"; flake = false; }; }; - outputs = { self, nixpkgs, libnbtplusplus, ... }: + outputs = { self, nixpkgs, libnbtplusplus, tomlplusplus, ... }: let # User-friendly version number. version = builtins.substring 0 8 self.lastModifiedDate; @@ -22,8 +23,8 @@ pkgs = forAllSystems (system: nixpkgs.legacyPackages.${system}); packagesFn = pkgs: rec { - polymc = pkgs.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus; }; - polymc-qt6 = pkgs.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus; }; + polymc = pkgs.libsForQt5.callPackage ./nix { inherit version self libnbtplusplus tomlplusplus; }; + polymc-qt6 = pkgs.qt6Packages.callPackage ./nix { inherit version self libnbtplusplus tomlplusplus; }; }; in { diff --git a/nix/default.nix b/nix/default.nix index 42ddda18..88b540ab 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -21,6 +21,7 @@ , self , version , libnbtplusplus +, tomlplusplus , enableLTO ? false }: @@ -59,6 +60,11 @@ stdenv.mkDerivation rec { mkdir source/libraries/libnbtplusplus cp -a ${libnbtplusplus}/* source/libraries/libnbtplusplus chmod a+r+w source/libraries/libnbtplusplus/* + # Copy tomlplusplus + rm -rf source/libraries/tomlplusplus + mkdir source/libraries/tomlplusplus + cp -a ${tomlplusplus}/* source/libraries/tomlplusplus + chmod a+r+w source/libraries/tomlplusplus/* ''; cmakeFlags = [ From be3d780720688ea7869fc4a5744c99c7a84ec167 Mon Sep 17 00:00:00 2001 From: Vedant <83997633+vedantmgoyal2009@users.noreply.github.com> Date: Fri, 30 Sep 2022 22:51:47 +0530 Subject: [PATCH 248/273] Update winget.yml Signed-off-by: Vedant <83997633+vedantmgoyal2009@users.noreply.github.com> --- .github/workflows/winget.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/winget.yml b/.github/workflows/winget.yml index 98981e80..d3f787b6 100644 --- a/.github/workflows/winget.yml +++ b/.github/workflows/winget.yml @@ -7,8 +7,9 @@ jobs: publish: runs-on: windows-latest steps: - - uses: vedantmgoyal2009/winget-releaser@latest + - uses: vedantmgoyal2009/winget-releaser@v1 with: identifier: PolyMC.PolyMC + version: ${{ github.event.release.tag_name }} installers-regex: 'PolyMC-Windows-Setup-.+\.exe$' token: ${{ secrets.WINGET_TOKEN }} From 7ccafdc99321e82ee0f504a573a5c978480374a2 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Fri, 30 Sep 2022 19:56:01 +0200 Subject: [PATCH 249/273] fix: add missing includes to fix Qt 6.4 build Signed-off-by: Sefa Eyeoglu --- launcher/ui/themes/BrightTheme.cpp | 2 ++ launcher/ui/themes/DarkTheme.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/launcher/ui/themes/BrightTheme.cpp b/launcher/ui/themes/BrightTheme.cpp index b9188bdd..7469edfc 100644 --- a/launcher/ui/themes/BrightTheme.cpp +++ b/launcher/ui/themes/BrightTheme.cpp @@ -1,5 +1,7 @@ #include "BrightTheme.h" +#include + QString BrightTheme::id() { return "bright"; diff --git a/launcher/ui/themes/DarkTheme.cpp b/launcher/ui/themes/DarkTheme.cpp index 712a9d3e..c2a6a8df 100644 --- a/launcher/ui/themes/DarkTheme.cpp +++ b/launcher/ui/themes/DarkTheme.cpp @@ -1,5 +1,7 @@ #include "DarkTheme.h" +#include + QString DarkTheme::id() { return "dark"; From 8e43d9713349af3834f51a4c6632b8b054415268 Mon Sep 17 00:00:00 2001 From: stoltsvensk <104643560+stoltsvensk@users.noreply.github.com> Date: Sat, 1 Oct 2022 16:47:23 +0200 Subject: [PATCH 250/273] Microsoft account only Signed-off-by: stoltsvensk <104643560+stoltsvensk@users.noreply.github.com> --- launcher/LaunchController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 830fcf82..56e920f0 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -93,7 +93,7 @@ void LaunchController::decideAccount() auto reply = CustomMessageBox::selectable( m_parentWidget, tr("No Accounts"), - tr("In order to play Minecraft, you must have at least one Mojang or Microsoft " + tr("In order to play Minecraft, you must have at least one Microsoft " "account logged in. " "Would you like to open the account manager to add an account now?"), QMessageBox::Information, From 41276403dfe219f71f37b0610cebd7270ce3a9c6 Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Sun, 2 Oct 2022 12:20:28 +0200 Subject: [PATCH 251/273] feat(actions): add codeql code scanning Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> --- .github/workflows/build.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ae8947ab..f455416d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -64,6 +64,12 @@ jobs: with: submodules: 'true' + - name: Initialize CodeQL + if: runner.os == 'Linux' && matrix.qt_ver == 6 + uses: github/codeql-action/init@v2 + with: + languages: cpp, java + - name: 'Setup MSYS2' if: runner.os == 'Windows' uses: msys2/setup-msys2@v2 @@ -209,6 +215,14 @@ jobs: run: | ctest --test-dir build --output-on-failure + ## + # CODE SCAN + ## + + - name: Perform CodeQL Analysis + if: runner.os == 'Linux' && matrix.qt_ver == 6 + uses: github/codeql-action/analyze@v2 + ## # PACKAGE BUILDS ## From 23b3990f99d2614f932f331138afa30a7ad1c413 Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Sun, 2 Oct 2022 15:11:06 +0200 Subject: [PATCH 252/273] feat(code scanning): enable security-and-quality query Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f455416d..9f377e58 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -68,6 +68,7 @@ jobs: if: runner.os == 'Linux' && matrix.qt_ver == 6 uses: github/codeql-action/init@v2 with: + queries: security-and-quality languages: cpp, java - name: 'Setup MSYS2' From 80e9eed35a537482897555eac641dad12b921064 Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Sun, 2 Oct 2022 15:30:38 +0200 Subject: [PATCH 253/273] fix: remove old unused lgtm.yml, exclude cpp/fixme-comment Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> --- .github/codeql/codeql-config.yml | 3 +++ .github/workflows/build.yml | 1 + lgtm.yml | 2 -- 3 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 .github/codeql/codeql-config.yml delete mode 100644 lgtm.yml diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml new file mode 100644 index 00000000..70acfdfd --- /dev/null +++ b/.github/codeql/codeql-config.yml @@ -0,0 +1,3 @@ +query-filters: + - exclude: + id: cpp/fixme-comment diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9f377e58..6a9f393c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -68,6 +68,7 @@ jobs: if: runner.os == 'Linux' && matrix.qt_ver == 6 uses: github/codeql-action/init@v2 with: + config-file: ./.github/codeql/codeql-config.yml queries: security-and-quality languages: cpp, java diff --git a/lgtm.yml b/lgtm.yml deleted file mode 100644 index 39cd3036..00000000 --- a/lgtm.yml +++ /dev/null @@ -1,2 +0,0 @@ -queries: - - exclude: "cpp/fixme-comment" # We like to use FIXME From 6ebc9abb8090cdbf78ab71f071ccd1f495eb7c0e Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Thu, 6 Oct 2022 14:46:12 +0200 Subject: [PATCH 254/273] fix: update capabilities before first-run wizard On first run, the condition for the wizard would return, before running updateCapabilities(). This moves that call up, as its only dependency is the settings system. Signed-off-by: Sefa Eyeoglu --- launcher/Application.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index e08ea7f4..968dd08e 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -859,12 +859,13 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) qDebug() << "<> Application theme set."; } + updateCapabilities(); + if(createSetupWizard()) { return; } - updateCapabilities(); performMainStartupAction(); } From f26049009e0482193b2a9d0d037ba7cb8ca82def Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Sat, 8 Oct 2022 16:20:46 +0200 Subject: [PATCH 255/273] fix: mod updating isn't upcoming anymore :p Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> --- launcher/ui/pages/global/LauncherPage.ui | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/pages/global/LauncherPage.ui b/launcher/ui/pages/global/LauncherPage.ui index 645f7ef6..0d14f147 100644 --- a/launcher/ui/pages/global/LauncherPage.ui +++ b/launcher/ui/pages/global/LauncherPage.ui @@ -176,7 +176,7 @@ - <html><head/><body><p><span style=" font-weight:600; color:#f5c211;">Warning</span><span style=" color:#f5c211;">: Disabling mod metadata may also disable some upcoming QoL features, such as mod updating!</span></p></body></html> + <html><head/><body><p><span style=" font-weight:600; color:#f5c211;">Warning</span><span style=" color:#f5c211;">: Disabling mod metadata may also disable some QoL features, such as mod updating!</span></p></body></html> true From ebee50eedc751680bc7b7b407b984574d3433498 Mon Sep 17 00:00:00 2001 From: Trisave <42098407+Protrikk@users.noreply.github.com> Date: Sat, 8 Oct 2022 19:33:24 +0200 Subject: [PATCH 256/273] Improve default light and dark themes (#1174) --- launcher/ui/themes/BrightTheme.cpp | 26 +++++++++++++------------- launcher/ui/themes/DarkTheme.cpp | 14 +++++++------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/launcher/ui/themes/BrightTheme.cpp b/launcher/ui/themes/BrightTheme.cpp index 7469edfc..696ffdfb 100644 --- a/launcher/ui/themes/BrightTheme.cpp +++ b/launcher/ui/themes/BrightTheme.cpp @@ -20,19 +20,19 @@ bool BrightTheme::hasColorScheme() QPalette BrightTheme::colorScheme() { QPalette brightPalette; - brightPalette.setColor(QPalette::Window, QColor(239,240,241)); - brightPalette.setColor(QPalette::WindowText, QColor(49,54,59)); - brightPalette.setColor(QPalette::Base, QColor(252,252,252)); - brightPalette.setColor(QPalette::AlternateBase, QColor(239,240,241)); - brightPalette.setColor(QPalette::ToolTipBase, QColor(49,54,59)); - brightPalette.setColor(QPalette::ToolTipText, QColor(239,240,241)); - brightPalette.setColor(QPalette::Text, QColor(49,54,59)); - brightPalette.setColor(QPalette::Button, QColor(239,240,241)); - brightPalette.setColor(QPalette::ButtonText, QColor(49,54,59)); + brightPalette.setColor(QPalette::Window, QColor(255,255,255)); + brightPalette.setColor(QPalette::WindowText, QColor(17,17,17)); + brightPalette.setColor(QPalette::Base, QColor(250,250,250)); + brightPalette.setColor(QPalette::AlternateBase, QColor(240,240,240)); + brightPalette.setColor(QPalette::ToolTipBase, QColor(17,17,17)); + brightPalette.setColor(QPalette::ToolTipText, QColor(255,255,255)); + brightPalette.setColor(QPalette::Text, Qt::black); + brightPalette.setColor(QPalette::Button, QColor(249,249,249)); + brightPalette.setColor(QPalette::ButtonText, Qt::black); brightPalette.setColor(QPalette::BrightText, Qt::red); - brightPalette.setColor(QPalette::Link, QColor(41, 128, 185)); - brightPalette.setColor(QPalette::Highlight, QColor(61, 174, 233)); - brightPalette.setColor(QPalette::HighlightedText, QColor(239,240,241)); + brightPalette.setColor(QPalette::Link, QColor(37,137,164)); + brightPalette.setColor(QPalette::Highlight, QColor(137,207,84)); + brightPalette.setColor(QPalette::HighlightedText, Qt::black); return fadeInactive(brightPalette, fadeAmount(), fadeColor()); } @@ -43,7 +43,7 @@ double BrightTheme::fadeAmount() QColor BrightTheme::fadeColor() { - return QColor(239,240,241); + return QColor(255,255,255); } bool BrightTheme::hasStyleSheet() diff --git a/launcher/ui/themes/DarkTheme.cpp b/launcher/ui/themes/DarkTheme.cpp index c2a6a8df..07a2efd2 100644 --- a/launcher/ui/themes/DarkTheme.cpp +++ b/launcher/ui/themes/DarkTheme.cpp @@ -20,18 +20,18 @@ bool DarkTheme::hasColorScheme() QPalette DarkTheme::colorScheme() { QPalette darkPalette; - darkPalette.setColor(QPalette::Window, QColor(49,54,59)); + darkPalette.setColor(QPalette::Window, QColor(49,49,49)); darkPalette.setColor(QPalette::WindowText, Qt::white); - darkPalette.setColor(QPalette::Base, QColor(35,38,41)); - darkPalette.setColor(QPalette::AlternateBase, QColor(49,54,59)); + darkPalette.setColor(QPalette::Base, QColor(34,34,34)); + darkPalette.setColor(QPalette::AlternateBase, QColor(42,42,42)); darkPalette.setColor(QPalette::ToolTipBase, Qt::white); darkPalette.setColor(QPalette::ToolTipText, Qt::white); darkPalette.setColor(QPalette::Text, Qt::white); - darkPalette.setColor(QPalette::Button, QColor(49,54,59)); + darkPalette.setColor(QPalette::Button, QColor(48,48,48)); darkPalette.setColor(QPalette::ButtonText, Qt::white); darkPalette.setColor(QPalette::BrightText, Qt::red); - darkPalette.setColor(QPalette::Link, QColor(42, 130, 218)); - darkPalette.setColor(QPalette::Highlight, QColor(42, 130, 218)); + darkPalette.setColor(QPalette::Link, QColor(47,163,198)); + darkPalette.setColor(QPalette::Highlight, QColor(145,205,92)); darkPalette.setColor(QPalette::HighlightedText, Qt::black); darkPalette.setColor(QPalette::PlaceholderText, Qt::darkGray); return fadeInactive(darkPalette, fadeAmount(), fadeColor()); @@ -44,7 +44,7 @@ double DarkTheme::fadeAmount() QColor DarkTheme::fadeColor() { - return QColor(49,54,59); + return QColor(49,49,49); } bool DarkTheme::hasStyleSheet() From 3111e6a7212ae914041fce6d851d7662975dc1be Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sat, 8 Oct 2022 20:09:53 +0200 Subject: [PATCH 257/273] chore: add missing license headers Signed-off-by: Sefa Eyeoglu --- launcher/NullInstance.h | 35 ++++++++++++++++ launcher/RuntimeContext.h | 18 +++++++++ launcher/minecraft/Component.cpp | 35 ++++++++++++++++ launcher/minecraft/Library.cpp | 35 ++++++++++++++++ launcher/minecraft/Library.h | 35 ++++++++++++++++ launcher/minecraft/MinecraftInstance.h | 35 ++++++++++++++++ launcher/minecraft/PackProfile.h | 40 ++++++++++++++----- launcher/minecraft/Rule.cpp | 40 ++++++++++++++----- launcher/minecraft/Rule.h | 40 ++++++++++++++----- launcher/minecraft/launch/ModMinecraftJar.cpp | 40 ++++++++++++++----- launcher/minecraft/launch/ScanModFolders.cpp | 40 ++++++++++++++----- launcher/minecraft/update/FoldersTask.cpp | 35 ++++++++++++++++ tests/Library_test.cpp | 35 ++++++++++++++++ 13 files changed, 413 insertions(+), 50 deletions(-) diff --git a/launcher/NullInstance.h b/launcher/NullInstance.h index 628aff5f..53edfa0b 100644 --- a/launcher/NullInstance.h +++ b/launcher/NullInstance.h @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #pragma once #include "BaseInstance.h" #include "launch/LaunchTask.h" diff --git a/launcher/RuntimeContext.h b/launcher/RuntimeContext.h index 6090897c..c1b71318 100644 --- a/launcher/RuntimeContext.h +++ b/launcher/RuntimeContext.h @@ -1,3 +1,21 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #pragma once #include diff --git a/launcher/minecraft/Component.cpp b/launcher/minecraft/Component.cpp index ebe4eac7..7e5b6058 100644 --- a/launcher/minecraft/Component.cpp +++ b/launcher/minecraft/Component.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #include #include #include "Component.h" diff --git a/launcher/minecraft/Library.cpp b/launcher/minecraft/Library.cpp index 42a42d87..cb2b5254 100644 --- a/launcher/minecraft/Library.cpp +++ b/launcher/minecraft/Library.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #include "Library.h" #include "MinecraftInstance.h" diff --git a/launcher/minecraft/Library.h b/launcher/minecraft/Library.h index b8fae9ec..950aec9d 100644 --- a/launcher/minecraft/Library.h +++ b/launcher/minecraft/Library.h @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #pragma once #include #include diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h index ef48b286..1895d187 100644 --- a/launcher/minecraft/MinecraftInstance.h +++ b/launcher/minecraft/MinecraftInstance.h @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #pragma once #include "BaseInstance.h" #include diff --git a/launcher/minecraft/PackProfile.h b/launcher/minecraft/PackProfile.h index 11057a0c..807511a2 100644 --- a/launcher/minecraft/PackProfile.h +++ b/launcher/minecraft/PackProfile.h @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. * - * http://www.apache.org/licenses/LICENSE-2.0 + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #pragma once diff --git a/launcher/minecraft/Rule.cpp b/launcher/minecraft/Rule.cpp index 2f41e1fb..ff3d75f2 100644 --- a/launcher/minecraft/Rule.cpp +++ b/launcher/minecraft/Rule.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. * - * http://www.apache.org/licenses/LICENSE-2.0 + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #include diff --git a/launcher/minecraft/Rule.h b/launcher/minecraft/Rule.h index 4c75cee4..236f9a87 100644 --- a/launcher/minecraft/Rule.h +++ b/launcher/minecraft/Rule.h @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. * - * http://www.apache.org/licenses/LICENSE-2.0 + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #pragma once diff --git a/launcher/minecraft/launch/ModMinecraftJar.cpp b/launcher/minecraft/launch/ModMinecraftJar.cpp index 8e47e0e5..1d6eecf2 100644 --- a/launcher/minecraft/launch/ModMinecraftJar.cpp +++ b/launcher/minecraft/launch/ModMinecraftJar.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. * - * http://www.apache.org/licenses/LICENSE-2.0 + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #include "ModMinecraftJar.h" diff --git a/launcher/minecraft/launch/ScanModFolders.cpp b/launcher/minecraft/launch/ScanModFolders.cpp index 2e817e0a..bdffeadd 100644 --- a/launcher/minecraft/launch/ScanModFolders.cpp +++ b/launcher/minecraft/launch/ScanModFolders.cpp @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. * - * http://www.apache.org/licenses/LICENSE-2.0 + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #include "ScanModFolders.h" diff --git a/launcher/minecraft/update/FoldersTask.cpp b/launcher/minecraft/update/FoldersTask.cpp index 22768bd9..b9ee9d98 100644 --- a/launcher/minecraft/update/FoldersTask.cpp +++ b/launcher/minecraft/update/FoldersTask.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #include "FoldersTask.h" #include "minecraft/MinecraftInstance.h" #include diff --git a/tests/Library_test.cpp b/tests/Library_test.cpp index b9027e12..db8380c7 100644 --- a/tests/Library_test.cpp +++ b/tests/Library_test.cpp @@ -1,3 +1,38 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #include #include From fecf1ffcb935c69aaba1049dfa5291615fa7aefa Mon Sep 17 00:00:00 2001 From: Ozynt <104643560+Ozynt@users.noreply.github.com> Date: Sun, 9 Oct 2022 13:19:38 +0200 Subject: [PATCH 258/273] Update LaunchController.cpp Signed-off-by: Ozynt <104643560+Ozynt@users.noreply.github.com> --- launcher/LaunchController.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 56e920f0..15ce2545 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -93,8 +93,8 @@ void LaunchController::decideAccount() auto reply = CustomMessageBox::selectable( m_parentWidget, tr("No Accounts"), - tr("In order to play Minecraft, you must have at least one Microsoft " - "account logged in. " + tr("In order to play Minecraft, you must have at least one Microsoft or Mojang " + "account logged in. Mojang accounts are only usable offline." "Would you like to open the account manager to add an account now?"), QMessageBox::Information, QMessageBox::Yes | QMessageBox::No From 7e67fd8c7931c5ddfc166cc711e518ea52309c3d Mon Sep 17 00:00:00 2001 From: Ozynt <104643560+Ozynt@users.noreply.github.com> Date: Sun, 9 Oct 2022 13:20:50 +0200 Subject: [PATCH 259/273] Update LaunchController.cpp Signed-off-by: Ozynt <104643560+Ozynt@users.noreply.github.com> --- launcher/LaunchController.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 15ce2545..e61c5f67 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -94,7 +94,7 @@ void LaunchController::decideAccount() m_parentWidget, tr("No Accounts"), tr("In order to play Minecraft, you must have at least one Microsoft or Mojang " - "account logged in. Mojang accounts are only usable offline." + "account logged in. Mojang accounts can only be used offline. " "Would you like to open the account manager to add an account now?"), QMessageBox::Information, QMessageBox::Yes | QMessageBox::No From 93a2e0f777668b12fe4f678d1def5deafd11f101 Mon Sep 17 00:00:00 2001 From: Tayou Date: Mon, 10 Oct 2022 23:20:21 +0200 Subject: [PATCH 260/273] Merge Launch Buttons Signed-off-by: Tayou --- launcher/ui/MainWindow.cpp | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index 5729b44d..efd6b398 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -792,7 +792,6 @@ public: instanceToolBar->addSeparator(); instanceToolBar->addAction(actionLaunchInstance); - instanceToolBar->addAction(actionLaunchInstanceOffline); instanceToolBar->addAction(actionKillInstance); instanceToolBar->addSeparator(); @@ -1197,7 +1196,6 @@ void MainWindow::updateMainToolBar() void MainWindow::updateToolsMenu() { QToolButton *launchButton = dynamic_cast(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstance)); - QToolButton *launchOfflineButton = dynamic_cast(ui->instanceToolBar->widgetForAction(ui->actionLaunchInstanceOffline)); bool currentInstanceRunning = m_selectedInstance && m_selectedInstance->isRunning(); @@ -1206,9 +1204,7 @@ void MainWindow::updateToolsMenu() ui->actionLaunchInstanceDemo->setDisabled(!m_selectedInstance || currentInstanceRunning); QMenu *launchMenu = ui->actionLaunchInstance->menu(); - QMenu *launchOfflineMenu = ui->actionLaunchInstanceOffline->menu(); launchButton->setPopupMode(QToolButton::MenuButtonPopup); - launchOfflineButton->setPopupMode(QToolButton::MenuButtonPopup); if (launchMenu) { launchMenu->clear(); @@ -1217,19 +1213,12 @@ void MainWindow::updateToolsMenu() { launchMenu = new QMenu(this); } - if (launchOfflineMenu) { - launchOfflineMenu->clear(); - } - else - { - launchOfflineMenu = new QMenu(this); - } QAction *normalLaunch = launchMenu->addAction(tr("Launch")); normalLaunch->setShortcut(QKeySequence::Open); - QAction *normalLaunchOffline = launchOfflineMenu->addAction(tr("Launch Offline")); + QAction *normalLaunchOffline = launchMenu->addAction(tr("Launch Offline")); normalLaunchOffline->setShortcut(QKeySequence(tr("Ctrl+Shift+O"))); - QAction *normalLaunchDemo = launchOfflineMenu->addAction(tr("Launch Demo")); + QAction *normalLaunchDemo = launchMenu->addAction(tr("Launch Demo")); normalLaunchDemo->setShortcut(QKeySequence(tr("Ctrl+Alt+O"))); if (m_selectedInstance) { @@ -1262,11 +1251,10 @@ void MainWindow::updateToolsMenu() QString profilersTitle = tr("Profilers"); launchMenu->addSeparator()->setText(profilersTitle); - launchOfflineMenu->addSeparator()->setText(profilersTitle); for (auto profiler : APPLICATION->profilers().values()) { QAction *profilerAction = launchMenu->addAction(profiler->name()); - QAction *profilerOfflineAction = launchOfflineMenu->addAction(profiler->name()); + QAction *profilerOfflineAction = launchMenu->addAction(profiler->name() + " Offline"); QString error; if (!profiler->check(&error)) { @@ -1297,7 +1285,6 @@ void MainWindow::updateToolsMenu() } } ui->actionLaunchInstance->setMenu(launchMenu); - ui->actionLaunchInstanceOffline->setMenu(launchOfflineMenu); } void MainWindow::repopulateAccountsMenu() From aaba99dc10acc6585caae18afc87a33053b55e4b Mon Sep 17 00:00:00 2001 From: Tayou <31988415+TayouVR@users.noreply.github.com> Date: Tue, 11 Oct 2022 14:58:34 +0200 Subject: [PATCH 261/273] Update launcher/ui/MainWindow.cpp make " Offline" string for profilers translatable Co-authored-by: Sefa Eyeoglu Signed-off-by: Tayou <31988415+TayouVR@users.noreply.github.com> --- launcher/ui/MainWindow.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index efd6b398..124de8e3 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -1254,7 +1254,7 @@ void MainWindow::updateToolsMenu() for (auto profiler : APPLICATION->profilers().values()) { QAction *profilerAction = launchMenu->addAction(profiler->name()); - QAction *profilerOfflineAction = launchMenu->addAction(profiler->name() + " Offline"); + QAction *profilerOfflineAction = launchMenu->addAction(tr("%1 Offline").arg(profiler->name())); QString error; if (!profiler->check(&error)) { From b2a5d8daf4e006e11c9bc67b10a88f9f5f51ba54 Mon Sep 17 00:00:00 2001 From: flow Date: Wed, 12 Oct 2022 10:26:14 -0300 Subject: [PATCH 262/273] fix: don't include opted out versions with the 'Any' filter on the MD Signed-off-by: flow --- launcher/ui/pages/modplatform/ModPage.cpp | 4 +++- launcher/ui/pages/modplatform/ModPage.h | 1 + launcher/ui/pages/modplatform/flame/FlameModPage.cpp | 5 +++++ launcher/ui/pages/modplatform/flame/FlameModPage.h | 1 + 4 files changed, 10 insertions(+), 1 deletion(-) diff --git a/launcher/ui/pages/modplatform/ModPage.cpp b/launcher/ui/pages/modplatform/ModPage.cpp index 4fce0242..2af9a10a 100644 --- a/launcher/ui/pages/modplatform/ModPage.cpp +++ b/launcher/ui/pages/modplatform/ModPage.cpp @@ -265,7 +265,9 @@ void ModPage::updateModVersions(int prev_count) break; } } - if(valid || m_filter->versions.size() == 0) + + // Only add the version if it's valid or using the 'Any' filter, but never if the version is opted out + if ((valid || m_filter->versions.empty()) && !optedOut(version)) ui->versionSelectionBox->addItem(version.version, QVariant(i)); } if (ui->versionSelectionBox->count() == 0 && prev_count != 0) { diff --git a/launcher/ui/pages/modplatform/ModPage.h b/launcher/ui/pages/modplatform/ModPage.h index 3f31651c..ae3d7e77 100644 --- a/launcher/ui/pages/modplatform/ModPage.h +++ b/launcher/ui/pages/modplatform/ModPage.h @@ -51,6 +51,7 @@ class ModPage : public QWidget, public BasePage { auto shouldDisplay() const -> bool override = 0; virtual auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders = ModAPI::Unspecified) const -> bool = 0; + virtual bool optedOut(ModPlatform::IndexedVersion& ver) const { return false; }; auto apiProvider() -> ModAPI* { return api.get(); }; auto getFilter() const -> const std::shared_ptr { return m_filter; } diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp index 772fd2e0..54a7be04 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModPage.cpp +++ b/launcher/ui/pages/modplatform/flame/FlameModPage.cpp @@ -67,6 +67,11 @@ auto FlameModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString min return ver.mcVersion.contains(mineVer) && !ver.downloadUrl.isEmpty(); } +bool FlameModPage::optedOut(ModPlatform::IndexedVersion& ver) const +{ + return ver.downloadUrl.isEmpty(); +} + // I don't know why, but doing this on the parent class makes it so that // other mod providers start loading before being selected, at least with // my Qt, so we need to implement this in every derived class... diff --git a/launcher/ui/pages/modplatform/flame/FlameModPage.h b/launcher/ui/pages/modplatform/flame/FlameModPage.h index 2cd484cb..50dedd6f 100644 --- a/launcher/ui/pages/modplatform/flame/FlameModPage.h +++ b/launcher/ui/pages/modplatform/flame/FlameModPage.h @@ -61,6 +61,7 @@ class FlameModPage : public ModPage { inline auto metaEntryBase() const -> QString override { return "FlameMods"; }; auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderTypes loaders = ModAPI::Unspecified) const -> bool override; + bool optedOut(ModPlatform::IndexedVersion& ver) const override; auto shouldDisplay() const -> bool override; }; From 2901039a485c27c686ebadebf8b67a5126ce9df0 Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Sat, 1 Oct 2022 09:19:59 +0200 Subject: [PATCH 263/273] feat(actions): macOS-Legacy package still no updater part though Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> --- .github/workflows/build.yml | 22 ++++++++++++++++++---- .github/workflows/trigger_release.yml | 2 ++ 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6a9f393c..1740f12a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,12 +39,21 @@ jobs: qt_ver: 6 - os: macos-12 + name: macOS macosx_deployment_target: 10.15 qt_ver: 6 qt_host: mac qt_version: '6.3.0' qt_modules: 'qt5compat qtimageformats' + - os: macos-12 + name: macOS-Legacy + macosx_deployment_target: 10.13 + qt_ver: 5 + qt_host: mac + qt_version: '5.15.2' + qt_modules: '' + runs-on: ${{ matrix.os }} env: @@ -148,7 +157,7 @@ jobs: sudo apt-get -y install qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools libqt5core5a libqt5network5 libqt5gui5 - name: Install Qt (macOS and AppImage) - if: matrix.qt_ver == 6 && runner.os != 'Windows' + if: runner.os == 'Linux' && matrix.qt_ver == 6 || runner.os == 'macOS' uses: jurplel/install-qt-action@v3 with: version: ${{ matrix.qt_version }} @@ -172,9 +181,14 @@ jobs: ## - name: Configure CMake (macOS) - if: runner.os == 'macOS' + if: runner.os == 'macOS' && matrix.qt_ver == 6 run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -DLauncher_BUILD_PLATFORM=macOS -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -G Ninja + cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64" -G Ninja + + - name: Configure CMake (macOS-Legacy) + if: runner.os == 'macOS' && matrix.qt_ver == 5 + run: | + cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -G Ninja - name: Configure CMake (Windows) if: runner.os == 'Windows' @@ -339,7 +353,7 @@ jobs: if: runner.os == 'macOS' uses: actions/upload-artifact@v3 with: - name: PolyMC-${{ runner.os }}-${{ env.VERSION }}-${{ inputs.build_type }} + name: PolyMC-${{ matrix.name }}-${{ env.VERSION }}-${{ inputs.build_type }} path: PolyMC.tar.gz - name: Upload binary zip (Windows) diff --git a/.github/workflows/trigger_release.yml b/.github/workflows/trigger_release.yml index 45ef7281..71180d7a 100644 --- a/.github/workflows/trigger_release.yml +++ b/.github/workflows/trigger_release.yml @@ -40,6 +40,7 @@ jobs: mv PolyMC-Linux-Portable*/PolyMC-portable.tar.gz PolyMC-Linux-Portable-${{ env.VERSION }}.tar.gz mv PolyMC-Linux*/PolyMC.tar.gz PolyMC-Linux-${{ env.VERSION }}.tar.gz mv PolyMC-*.AppImage/PolyMC-*.AppImage PolyMC-Linux-${{ env.VERSION }}-x86_64.AppImage + mv PolyMC-macOS-Legacy*/PolyMC.tar.gz PolyMC-macOS-Legacy-${{ env.VERSION }}.tar.gz mv PolyMC-macOS*/PolyMC.tar.gz PolyMC-macOS-${{ env.VERSION }}.tar.gz tar -czf PolyMC-${{ env.VERSION }}.tar.gz PolyMC-${{ env.VERSION }} @@ -80,4 +81,5 @@ jobs: PolyMC-Windows-Portable-${{ env.VERSION }}.zip PolyMC-Windows-Setup-${{ env.VERSION }}.exe PolyMC-macOS-${{ env.VERSION }}.tar.gz + PolyMC-macOS-Legacy-${{ env.VERSION }}.tar.gz PolyMC-${{ env.VERSION }}.tar.gz From 5bdb3703ede735129d7eb57c0b1f6d2c497e3fa2 Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Sun, 2 Oct 2022 08:30:19 +0200 Subject: [PATCH 264/273] fix: stop forcing libc++ on macOS Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> --- CMakeLists.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 46192414..4d1d3d87 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,9 +33,6 @@ set(CMAKE_CXX_STANDARD 17) set(CMAKE_C_STANDARD 11) include(GenerateExportHeader) set(CMAKE_CXX_FLAGS "-Wall -pedantic -fstack-protector-strong --param=ssp-buffer-size=4 ${CMAKE_CXX_FLAGS}") -if(UNIX AND APPLE) - set(CMAKE_CXX_FLAGS "-stdlib=libc++ ${CMAKE_CXX_FLAGS}") -endif() # Fix build with Qt 5.13 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DQT_NO_DEPRECATED_WARNINGS=Y") From c520faed6d0e5e9472622f848ad8512b6c71c8e0 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 13 Oct 2022 18:37:44 -0300 Subject: [PATCH 265/273] feat: add gulrak/filesystem submodule ... for old macs that don't have std::filesystem in their stdlib. Signed-off-by: flow --- .gitmodules | 3 +++ CMakeLists.txt | 1 + launcher/CMakeLists.txt | 1 + libraries/README.md | 8 ++++++++ libraries/filesystem | 1 + 5 files changed, 14 insertions(+) create mode 160000 libraries/filesystem diff --git a/.gitmodules b/.gitmodules index 534ffd37..efd5de29 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "libraries/tomlplusplus"] path = libraries/tomlplusplus url = https://github.com/marzer/tomlplusplus.git +[submodule "libraries/filesystem"] + path = libraries/filesystem + url = https://github.com/gulrak/filesystem diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d1d3d87..05ea9f66 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -323,6 +323,7 @@ endif() add_subdirectory(libraries/katabasis) # An OAuth2 library that tried to do too much add_subdirectory(libraries/gamemode) add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API +add_subdirectory(libraries/filesystem) # Implementation of std::filesystem for old C++, for usage in old macOS ############################### Built Artifacts ############################### diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index c5894268..d04f3e4d 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -976,6 +976,7 @@ target_link_libraries(Launcher_logic BuildConfig Katabasis Qt${QT_VERSION_MAJOR}::Widgets + ghc_filesystem ) if (UNIX AND NOT CYGWIN AND NOT APPLE) diff --git a/libraries/README.md b/libraries/README.md index 6297cd9b..dfb57a65 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -10,6 +10,14 @@ This library has served as a base for some (much more full-featured and advanced Copyright belongs to Petr Mrázek, unless explicitly stated otherwise in the source files. Available under the Apache 2.0 license. +## filesystem + +Gulrak's implementation of C++17 std::filesystem for C++11 /C++14/C++17/C++20 on Windows, macOS, Linux and FreeBSD. + +See [github repo](https://github.com/gulrak/filesystem). + +MIT licensed. + ## gamemode A performance optimization daemon. diff --git a/libraries/filesystem b/libraries/filesystem new file mode 160000 index 00000000..cd6805e9 --- /dev/null +++ b/libraries/filesystem @@ -0,0 +1 @@ +Subproject commit cd6805e94dd5d6346be1b75a54cdc27787319dd2 From 124097d3a50ba4ae97478ce7d33e5d33690cef75 Mon Sep 17 00:00:00 2001 From: flow Date: Thu, 13 Oct 2022 18:38:52 -0300 Subject: [PATCH 266/273] feat!: use ghc/filesystem in place of std's one if needed Signed-off-by: flow --- launcher/FileSystem.cpp | 31 ++++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/launcher/FileSystem.cpp b/launcher/FileSystem.cpp index 5d179641..c2db0dbc 100644 --- a/launcher/FileSystem.cpp +++ b/launcher/FileSystem.cpp @@ -59,7 +59,24 @@ #include #endif +// Snippet from https://github.com/gulrak/filesystem#using-it-as-single-file-header + +#ifdef __APPLE__ +#include // for deployment target to support pre-catalina targets without std::fs +#endif // __APPLE__ + +#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) || (defined(__cplusplus) && __cplusplus >= 201703L)) && defined(__has_include) +#if __has_include() && (!defined(__MAC_OS_X_VERSION_MIN_REQUIRED) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500) +#define GHC_USE_STD_FS #include +namespace fs = std::filesystem; +#endif // MacOS min version check +#endif // Other OSes version check + +#ifndef GHC_USE_STD_FS +#include +namespace fs = ghc::filesystem; +#endif #if defined Q_OS_WIN32 @@ -147,7 +164,7 @@ bool ensureFolderPathExists(QString foldernamepath) bool copy::operator()(const QString& offset) { - using copy_opts = std::filesystem::copy_options; + using copy_opts = fs::copy_options; // NOTE always deep copy on windows. the alternatives are too messy. #if defined Q_OS_WIN32 @@ -159,7 +176,7 @@ bool copy::operator()(const QString& offset) std::error_code err; - std::filesystem::copy_options opt = copy_opts::none; + fs::copy_options opt = copy_opts::none; // The default behavior is to follow symlinks if (!m_followSymlinks) @@ -182,7 +199,7 @@ bool copy::operator()(const QString& offset) auto dst_path = PathCombine(dst, relative_path); ensureFilePathExists(dst_path); - std::filesystem::copy(toStdString(src_path), toStdString(dst_path), opt, err); + fs::copy(toStdString(src_path), toStdString(dst_path), opt, err); if (err) { qWarning() << "Failed to copy files:" << QString::fromStdString(err.message()); qDebug() << "Source file:" << src_path; @@ -197,7 +214,7 @@ bool deletePath(QString path) { std::error_code err; - std::filesystem::remove_all(toStdString(path), err); + fs::remove_all(toStdString(path), err); if (err) { qWarning() << "Failed to remove files:" << QString::fromStdString(err.message()); @@ -376,15 +393,15 @@ bool createShortCut(QString location, QString dest, QStringList args, QString na bool overrideFolder(QString overwritten_path, QString override_path) { - using copy_opts = std::filesystem::copy_options; + using copy_opts = fs::copy_options; if (!FS::ensureFolderPathExists(overwritten_path)) return false; std::error_code err; - std::filesystem::copy_options opt = copy_opts::recursive | copy_opts::overwrite_existing; + fs::copy_options opt = copy_opts::recursive | copy_opts::overwrite_existing; - std::filesystem::copy(toStdString(override_path), toStdString(overwritten_path), opt, err); + fs::copy(toStdString(override_path), toStdString(overwritten_path), opt, err); if (err) { qCritical() << QString("Failed to apply override from %1 to %2").arg(override_path, overwritten_path); From 2aff7bac4aa5dc39b2b68eb2f9d894be832d48c9 Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Fri, 14 Oct 2022 14:12:51 +0200 Subject: [PATCH 267/273] fix: disable updater on macOS-Legacy Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1740f12a..6d001cfc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -188,7 +188,7 @@ jobs: - name: Configure CMake (macOS-Legacy) if: runner.os == 'macOS' && matrix.qt_ver == 5 run: | - cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -G Ninja + cmake -S . -B ${{ env.BUILD_DIR }} -DCMAKE_INSTALL_PREFIX=${{ env.INSTALL_DIR }} -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} -DENABLE_LTO=ON -DLauncher_BUILD_PLATFORM=${{ matrix.name }} -DCMAKE_C_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DCMAKE_CXX_COMPILER_LAUNCHER=${{ env.CCACHE_VAR }} -DLauncher_QT_VERSION_MAJOR=${{ matrix.qt_ver }} -DMACOSX_SPARKLE_UPDATE_PUBLIC_KEY="" -DMACOSX_SPARKLE_UPDATE_FEED_URL="" -G Ninja - name: Configure CMake (Windows) if: runner.os == 'Windows' @@ -254,7 +254,7 @@ jobs: tar -czf ../PolyMC.tar.gz * - name: Make Sparkle signature (macOS) - if: runner.os == 'macOS' + if: matrix.name == 'macOS' run: | if [ '${{ secrets.SPARKLE_ED25519_KEY }}' != '' ]; then brew install openssl@3 From 89e45a61b33ed7ae73aa9903149530462fd760b6 Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Fri, 14 Oct 2022 16:45:13 +0200 Subject: [PATCH 268/273] fix: workaround ghc::filesystem bug Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> --- CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 05ea9f66..45dd691d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -194,6 +194,9 @@ if(NOT Launcher_FORCE_BUNDLED_LIBS) find_package(tomlplusplus 3.2.0 QUIET) endif() +# Workaround ghc::filesystem bug +set(GHC_FILESYSTEM_WITH_INSTALL OFF) + ####################################### Program Info ####################################### set(Launcher_APP_BINARY_NAME "polymc" CACHE STRING "Name of the Launcher binary") From 303628bb0579f13d3ceac915b3caaf4d7fc9b2db Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sat, 15 Oct 2022 13:13:34 +0200 Subject: [PATCH 269/273] refactor: support system ghc-filesystem Signed-off-by: Sefa Eyeoglu --- CMakeLists.txt | 17 ++++++++++++----- launcher/CMakeLists.txt | 2 +- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 45dd691d..310cec59 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -189,13 +189,13 @@ if (Qt5_POSITION_INDEPENDENT_CODE) SET(CMAKE_POSITION_INDEPENDENT_CODE ON) endif() -# Find toml++ if(NOT Launcher_FORCE_BUNDLED_LIBS) + # Find toml++ find_package(tomlplusplus 3.2.0 QUIET) -endif() -# Workaround ghc::filesystem bug -set(GHC_FILESYSTEM_WITH_INSTALL OFF) + # Find ghc_filesystem + find_package(ghc_filesystem QUIET) +endif() ####################################### Program Info ####################################### @@ -326,7 +326,14 @@ endif() add_subdirectory(libraries/katabasis) # An OAuth2 library that tried to do too much add_subdirectory(libraries/gamemode) add_subdirectory(libraries/murmur2) # Hash for usage with the CurseForge API -add_subdirectory(libraries/filesystem) # Implementation of std::filesystem for old C++, for usage in old macOS +if (NOT ghc_filesystem_FOUND) + message(STATUS "Using bundled ghc_filesystem") + set(GHC_FILESYSTEM_WITH_INSTALL OFF) # Workaround ghc::filesystem bug + add_subdirectory(libraries/filesystem) # Implementation of std::filesystem for old C++, for usage in old macOS + add_library(ghcFilesystem::ghc_filesystem ALIAS ghc_filesystem) +else() + message(STATUS "Using system ghc_filesystem") +endif() ############################### Built Artifacts ############################### diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index d04f3e4d..c7d0d68c 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -976,7 +976,7 @@ target_link_libraries(Launcher_logic BuildConfig Katabasis Qt${QT_VERSION_MAJOR}::Widgets - ghc_filesystem + ghcFilesystem::ghc_filesystem ) if (UNIX AND NOT CYGWIN AND NOT APPLE) From 03d077e915cf2bf06474b17e51e0c664f57eb348 Mon Sep 17 00:00:00 2001 From: Sefa Eyeoglu Date: Sat, 15 Oct 2022 13:13:56 +0200 Subject: [PATCH 270/273] fix(nix): add ghc_filesystem dependency Signed-off-by: Sefa Eyeoglu --- nix/default.nix | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nix/default.nix b/nix/default.nix index 88b540ab..19136cad 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -5,6 +5,7 @@ , ninja , jdk8 , jdk +, ghc_filesystem , zlib , file , wrapQtAppsHook @@ -49,7 +50,7 @@ stdenv.mkDerivation rec { src = lib.cleanSource self; - nativeBuildInputs = [ cmake extra-cmake-modules ninja jdk file wrapQtAppsHook ]; + nativeBuildInputs = [ cmake extra-cmake-modules ninja jdk ghc_filesystem file wrapQtAppsHook ]; buildInputs = [ qtbase quazip zlib ]; dontWrapQtApps = true; From 87d35f0d16d3be56020f9e6295cc8bfa0c657d27 Mon Sep 17 00:00:00 2001 From: DioEgizio <83089242+DioEgizio@users.noreply.github.com> Date: Sat, 15 Oct 2022 20:15:46 +0200 Subject: [PATCH 271/273] fix: remove some unused libs Signed-off-by: DioEgizio <83089242+DioEgizio@users.noreply.github.com> --- CMakeLists.txt | 2 - launcher/CMakeLists.txt | 1 - libraries/README.md | 16 +- libraries/classparser/CMakeLists.txt | 42 - libraries/classparser/include/classparser.h | 27 - .../classparser/include/classparser_config.h | 22 - libraries/classparser/src/annotations.cpp | 85 -- libraries/classparser/src/annotations.h | 278 ---- libraries/classparser/src/classfile.h | 156 --- libraries/classparser/src/classparser.cpp | 83 -- libraries/classparser/src/constants.h | 232 ---- libraries/classparser/src/errors.h | 8 - libraries/classparser/src/javaendian.h | 59 - libraries/classparser/src/membuffer.h | 63 - libraries/xz-embedded/CMakeLists.txt | 26 - libraries/xz-embedded/include/xz.h | 321 ----- libraries/xz-embedded/src/xz_config.h | 119 -- libraries/xz-embedded/src/xz_crc32.c | 61 - libraries/xz-embedded/src/xz_crc64.c | 52 - libraries/xz-embedded/src/xz_dec_bcj.c | 588 -------- libraries/xz-embedded/src/xz_dec_lzma2.c | 1231 ----------------- libraries/xz-embedded/src/xz_dec_stream.c | 860 ------------ libraries/xz-embedded/src/xz_lzma2.h | 204 --- libraries/xz-embedded/src/xz_private.h | 150 -- libraries/xz-embedded/src/xz_stream.h | 62 - libraries/xz-embedded/xzminidec.c | 144 -- 26 files changed, 1 insertion(+), 4891 deletions(-) delete mode 100644 libraries/classparser/CMakeLists.txt delete mode 100644 libraries/classparser/include/classparser.h delete mode 100644 libraries/classparser/include/classparser_config.h delete mode 100644 libraries/classparser/src/annotations.cpp delete mode 100644 libraries/classparser/src/annotations.h delete mode 100644 libraries/classparser/src/classfile.h delete mode 100644 libraries/classparser/src/classparser.cpp delete mode 100644 libraries/classparser/src/constants.h delete mode 100644 libraries/classparser/src/errors.h delete mode 100644 libraries/classparser/src/javaendian.h delete mode 100644 libraries/classparser/src/membuffer.h delete mode 100644 libraries/xz-embedded/CMakeLists.txt delete mode 100644 libraries/xz-embedded/include/xz.h delete mode 100644 libraries/xz-embedded/src/xz_config.h delete mode 100644 libraries/xz-embedded/src/xz_crc32.c delete mode 100644 libraries/xz-embedded/src/xz_crc64.c delete mode 100644 libraries/xz-embedded/src/xz_dec_bcj.c delete mode 100644 libraries/xz-embedded/src/xz_dec_lzma2.c delete mode 100644 libraries/xz-embedded/src/xz_dec_stream.c delete mode 100644 libraries/xz-embedded/src/xz_lzma2.h delete mode 100644 libraries/xz-embedded/src/xz_private.h delete mode 100644 libraries/xz-embedded/src/xz_stream.h delete mode 100644 libraries/xz-embedded/xzminidec.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 310cec59..caecddbd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -305,7 +305,6 @@ add_subdirectory(libraries/systeminfo) # system information library add_subdirectory(libraries/hoedown) # markdown parser add_subdirectory(libraries/launcher) # java based launcher part for Minecraft add_subdirectory(libraries/javacheck) # java compatibility checker -add_subdirectory(libraries/xz-embedded) # xz compression if (FORCE_BUNDLED_QUAZIP) message(STATUS "Using bundled QuaZip") set(BUILD_SHARED_LIBS 0) # link statically to avoid conflicts. @@ -316,7 +315,6 @@ else() endif() add_subdirectory(libraries/rainbow) # Qt extension for colors add_subdirectory(libraries/LocalPeer) # fork of a library from Qt solutions -add_subdirectory(libraries/classparser) # class parser library if(NOT tomlplusplus_FOUND) message(STATUS "Using bundled tomlplusplus") add_subdirectory(libraries/tomlplusplus) # toml parser diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index c7d0d68c..6ad91a85 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -968,7 +968,6 @@ add_library(Launcher_logic STATIC ${LOGIC_SOURCES} ${LAUNCHER_SOURCES} ${LAUNCHE target_include_directories(Launcher_logic PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) target_link_libraries(Launcher_logic systeminfo - Launcher_classparser Launcher_murmur2 nbt++ ${ZLIB_LIBRARIES} diff --git a/libraries/README.md b/libraries/README.md index dfb57a65..9a26dd69 100644 --- a/libraries/README.md +++ b/libraries/README.md @@ -2,14 +2,6 @@ This folder has third-party or otherwise external libraries needed for other parts to work. -## classparser - -A simplistic parser for Java class files. - -This library has served as a base for some (much more full-featured and advanced) work under NDA for AVG. It, however, should NOT be confused with that work. - -Copyright belongs to Petr Mrázek, unless explicitly stated otherwise in the source files. Available under the Apache 2.0 license. - ## filesystem Gulrak's implementation of C++17 std::filesystem for C++11 /C++14/C++17/C++20 on Windows, macOS, Linux and FreeBSD. @@ -191,10 +183,4 @@ A TOML language parser. Used by Forge 1.14+ to store mod metadata. See [github repo](https://github.com/marzer/tomlplusplus). -Licenced under the MIT licence. - -## xz-embedded - -Tiny implementation of LZMA2 de/compression. This format was only used by Forge to save bandwidth. - -Public domain. +Licenced under the MIT licence. \ No newline at end of file diff --git a/libraries/classparser/CMakeLists.txt b/libraries/classparser/CMakeLists.txt deleted file mode 100644 index 05412c30..00000000 --- a/libraries/classparser/CMakeLists.txt +++ /dev/null @@ -1,42 +0,0 @@ -project(classparser) - -set(CMAKE_AUTOMOC ON) - -######## Check endianness ######## -include(TestBigEndian) -test_big_endian(BIGENDIAN) -if(${BIGENDIAN}) - add_definitions(-DMULTIMC_BIG_ENDIAN) -endif(${BIGENDIAN}) - -# Find Qt -if(QT_VERSION_MAJOR EQUAL 5) - find_package(Qt5 COMPONENTS Core REQUIRED) -elseif(Launcher_QT_VERSION_MAJOR EQUAL 6) - find_package(Qt6 COMPONENTS Core REQUIRED) -endif() - -set(CLASSPARSER_HEADERS -# Public headers -include/classparser_config.h -include/classparser.h - -# Private headers -src/annotations.h -src/classfile.h -src/constants.h -src/errors.h -src/javaendian.h -src/membuffer.h -) - -set(CLASSPARSER_SOURCES -src/classparser.cpp -src/annotations.cpp -) - -add_definitions(-DCLASSPARSER_LIBRARY) - -add_library(Launcher_classparser STATIC ${CLASSPARSER_SOURCES} ${CLASSPARSER_HEADERS}) -target_include_directories(Launcher_classparser PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") -target_link_libraries(Launcher_classparser QuaZip::QuaZip Qt${QT_VERSION_MAJOR}::Core) diff --git a/libraries/classparser/include/classparser.h b/libraries/classparser/include/classparser.h deleted file mode 100644 index 3660026b..00000000 --- a/libraries/classparser/include/classparser.h +++ /dev/null @@ -1,27 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Authors: Orochimarufan - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#pragma once -#include -#include "classparser_config.h" - -namespace classparser -{ -/** - * @brief Get the version from a minecraft.jar by parsing its class files. Expensive! - */ -QString GetMinecraftJarVersion(QString jar); -} diff --git a/libraries/classparser/include/classparser_config.h b/libraries/classparser/include/classparser_config.h deleted file mode 100644 index 7bfae7cc..00000000 --- a/libraries/classparser/include/classparser_config.h +++ /dev/null @@ -1,22 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include - -#ifdef CLASSPARSER_LIBRARY -#define CLASSPARSER_EXPORT Q_DECL_EXPORT -#else -#define CLASSPARSER_EXPORT Q_DECL_IMPORT -#endif diff --git a/libraries/classparser/src/annotations.cpp b/libraries/classparser/src/annotations.cpp deleted file mode 100644 index 89b201bc..00000000 --- a/libraries/classparser/src/annotations.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include "classfile.h" -#include "annotations.h" -#include - -namespace java -{ -std::string annotation::toString() -{ - std::ostringstream ss; - ss << "Annotation type : " << type_index << " - " << pool[type_index].str_data << std::endl; - ss << "Contains " << name_val_pairs.size() << " pairs:" << std::endl; - for (unsigned i = 0; i < name_val_pairs.size(); i++) - { - std::pair &val = name_val_pairs[i]; - auto name_idx = val.first; - ss << pool[name_idx].str_data << "(" << name_idx << ")" - << " = " << val.second->toString() << std::endl; - } - return ss.str(); -} - -annotation *annotation::read(util::membuffer &input, constant_pool &pool) -{ - uint16_t type_index = 0; - input.read_be(type_index); - annotation *ann = new annotation(type_index, pool); - - uint16_t num_pairs = 0; - input.read_be(num_pairs); - while (num_pairs) - { - uint16_t name_idx = 0; - // read name index - input.read_be(name_idx); - auto elem = element_value::readElementValue(input, pool); - // read value - ann->add_pair(name_idx, elem); - num_pairs--; - } - return ann; -} - -element_value *element_value::readElementValue(util::membuffer &input, - java::constant_pool &pool) -{ - element_value_type type = INVALID; - input.read(type); - uint16_t index = 0; - uint16_t index2 = 0; - std::vector vals; - switch (type) - { - case PRIMITIVE_BYTE: - case PRIMITIVE_CHAR: - case PRIMITIVE_DOUBLE: - case PRIMITIVE_FLOAT: - case PRIMITIVE_INT: - case PRIMITIVE_LONG: - case PRIMITIVE_SHORT: - case PRIMITIVE_BOOLEAN: - case STRING: - input.read_be(index); - return new element_value_simple(type, index, pool); - case ENUM_CONSTANT: - input.read_be(index); - input.read_be(index2); - return new element_value_enum(type, index, index2, pool); - case CLASS: // Class - input.read_be(index); - return new element_value_class(type, index, pool); - case ANNOTATION: // Annotation - // FIXME: runtime visibility info needs to be passed from parent - return new element_value_annotation(ANNOTATION, annotation::read(input, pool), pool); - case ARRAY: // Array - input.read_be(index); - for (int i = 0; i < index; i++) - { - vals.push_back(element_value::readElementValue(input, pool)); - } - return new element_value_array(ARRAY, vals, pool); - default: - throw java::classfile_exception(); - } -} -} diff --git a/libraries/classparser/src/annotations.h b/libraries/classparser/src/annotations.h deleted file mode 100644 index 15bf05a4..00000000 --- a/libraries/classparser/src/annotations.h +++ /dev/null @@ -1,278 +0,0 @@ -#pragma once -#include "classfile.h" -#include -#include - -namespace java -{ -enum element_value_type : uint8_t -{ - INVALID = 0, - STRING = 's', - ENUM_CONSTANT = 'e', - CLASS = 'c', - ANNOTATION = '@', - ARRAY = '[', // one array dimension - PRIMITIVE_INT = 'I', // integer - PRIMITIVE_BYTE = 'B', // signed byte - PRIMITIVE_CHAR = 'C', // Unicode character code point in the Basic Multilingual Plane, - // encoded with UTF-16 - PRIMITIVE_DOUBLE = 'D', // double-precision floating-point value - PRIMITIVE_FLOAT = 'F', // single-precision floating-point value - PRIMITIVE_LONG = 'J', // long integer - PRIMITIVE_SHORT = 'S', // signed short - PRIMITIVE_BOOLEAN = 'Z' // true or false -}; -/** - * The element_value structure is a discriminated union representing the value of an - *element-value pair. - * It is used to represent element values in all attributes that describe annotations - * - RuntimeVisibleAnnotations - * - RuntimeInvisibleAnnotations - * - RuntimeVisibleParameterAnnotations - * - RuntimeInvisibleParameterAnnotations). - * - * The element_value structure has the following format: - */ -class element_value -{ -protected: - element_value_type type; - constant_pool &pool; - -public: - element_value(element_value_type type, constant_pool &pool) : type(type), pool(pool) {}; - virtual ~element_value() {} - - element_value_type getElementValueType() - { - return type; - } - - virtual std::string toString() = 0; - - static element_value *readElementValue(util::membuffer &input, constant_pool &pool); -}; - -/** - * Each value of the annotations table represents a single runtime-visible annotation on a - * program element. - * The annotation structure has the following format: - */ -class annotation -{ -public: - typedef std::vector> value_list; - -protected: - /** - * The value of the type_index item must be a valid index into the constant_pool table. - * The constant_pool entry at that index must be a CONSTANT_Utf8_info (§4.4.7) structure - * representing a field descriptor representing the annotation type corresponding - * to the annotation represented by this annotation structure. - */ - uint16_t type_index; - /** - * map between element_name_index and value. - * - * The value of the element_name_index item must be a valid index into the constant_pool - *table. - * The constant_pool entry at that index must be a CONSTANT_Utf8_info (§4.4.7) structure - *representing - * a valid field descriptor (§4.3.2) that denotes the name of the annotation type element - *represented - * by this element_value_pairs entry. - */ - value_list name_val_pairs; - /** - * Reference to the parent constant pool - */ - constant_pool &pool; - -public: - annotation(uint16_t type_index, constant_pool &pool) - : type_index(type_index), pool(pool) {}; - ~annotation() - { - for (unsigned i = 0; i < name_val_pairs.size(); i++) - { - delete name_val_pairs[i].second; - } - } - void add_pair(uint16_t key, element_value *value) - { - name_val_pairs.push_back(std::make_pair(key, value)); - } - ; - value_list::const_iterator begin() - { - return name_val_pairs.cbegin(); - } - value_list::const_iterator end() - { - return name_val_pairs.cend(); - } - std::string toString(); - static annotation *read(util::membuffer &input, constant_pool &pool); -}; -typedef std::vector annotation_table; - -/// type for simple value annotation elements -class element_value_simple : public element_value -{ -protected: - /// index of the constant in the constant pool - uint16_t index; - -public: - element_value_simple(element_value_type type, uint16_t index, constant_pool &pool) - : element_value(type, pool), index(index) { - // TODO: verify consistency - }; - uint16_t getIndex() - { - return index; - } - virtual std::string toString() - { - return pool[index].toString(); - } - ; -}; -/// The enum_const_value item is used if the tag item is 'e'. -class element_value_enum : public element_value -{ -protected: - /** - * The value of the type_name_index item must be a valid index into the constant_pool table. - * The constant_pool entry at that index must be a CONSTANT_Utf8_info (§4.4.7) structure - * representing a valid field descriptor (§4.3.2) that denotes the internal form of the - * binary - * name (§4.2.1) of the type of the enum constant represented by this element_value - * structure. - */ - uint16_t typeIndex; - /** - * The value of the const_name_index item must be a valid index into the constant_pool - * table. - * The constant_pool entry at that index must be a CONSTANT_Utf8_info (§4.4.7) structure - * representing the simple name of the enum constant represented by this element_value - * structure. - */ - uint16_t valueIndex; - -public: - element_value_enum(element_value_type type, uint16_t typeIndex, uint16_t valueIndex, - constant_pool &pool) - : element_value(type, pool), typeIndex(typeIndex), valueIndex(valueIndex) - { - // TODO: verify consistency - } - uint16_t getValueIndex() - { - return valueIndex; - } - uint16_t getTypeIndex() - { - return typeIndex; - } - virtual std::string toString() - { - return "enum value"; - } - ; -}; - -class element_value_class : public element_value -{ -protected: - /** - * The class_info_index item must be a valid index into the constant_pool table. - * The constant_pool entry at that index must be a CONSTANT_Utf8_info (§4.4.7) structure - * representing the return descriptor (§4.3.3) of the type that is reified by the class - * represented by this element_value structure. - * - * For example, 'V' for Void.class, 'Ljava/lang/Object;' for Object, etc. - * - * Or in plain english, you can store type information in annotations. Yay. - */ - uint16_t classIndex; - -public: - element_value_class(element_value_type type, uint16_t classIndex, constant_pool &pool) - : element_value(type, pool), classIndex(classIndex) - { - // TODO: verify consistency - } - uint16_t getIndex() - { - return classIndex; - } - virtual std::string toString() - { - return "class"; - } - ; -}; - -/// nested annotations... yay -class element_value_annotation : public element_value -{ -private: - annotation *nestedAnnotation; - -public: - element_value_annotation(element_value_type type, annotation *nestedAnnotation, - constant_pool &pool) - : element_value(type, pool), nestedAnnotation(nestedAnnotation) {}; - ~element_value_annotation() - { - if (nestedAnnotation) - { - delete nestedAnnotation; - nestedAnnotation = nullptr; - } - } - virtual std::string toString() - { - return "nested annotation"; - } - ; -}; - -/// and arrays! -class element_value_array : public element_value -{ -public: - typedef std::vector elem_vec; - -protected: - elem_vec values; - -public: - element_value_array(element_value_type type, std::vector &values, - constant_pool &pool) - : element_value(type, pool), values(values) {}; - ~element_value_array() - { - for (unsigned i = 0; i < values.size(); i++) - { - delete values[i]; - } - } - ; - elem_vec::const_iterator begin() - { - return values.cbegin(); - } - elem_vec::const_iterator end() - { - return values.cend(); - } - virtual std::string toString() - { - return "array"; - } - ; -}; -} \ No newline at end of file diff --git a/libraries/classparser/src/classfile.h b/libraries/classparser/src/classfile.h deleted file mode 100644 index d629dde1..00000000 --- a/libraries/classparser/src/classfile.h +++ /dev/null @@ -1,156 +0,0 @@ -#pragma once -#include "membuffer.h" -#include "constants.h" -#include "annotations.h" -#include -namespace java -{ -/** - * Class representing a Java .class file - */ -class classfile : public util::membuffer -{ -public: - classfile(char *data, std::size_t size) : membuffer(data, size) - { - valid = false; - is_synthetic = false; - read_be(magic); - if (magic != 0xCAFEBABE) - throw classfile_exception(); - read_be(minor_version); - read_be(major_version); - constants.load(*this); - read_be(access_flags); - read_be(this_class); - read_be(super_class); - - // Interfaces - uint16_t iface_count = 0; - read_be(iface_count); - while (iface_count) - { - uint16_t iface; - read_be(iface); - interfaces.push_back(iface); - iface_count--; - } - - // Fields - // read fields (and attributes from inside fields) (and possible inner classes. yay for - // recursion!) - // for now though, we will ignore all attributes - /* - * field_info - * { - * u2 access_flags; - * u2 name_index; - * u2 descriptor_index; - * u2 attributes_count; - * attribute_info attributes[attributes_count]; - * } - */ - uint16_t field_count = 0; - read_be(field_count); - while (field_count) - { - // skip field stuff - skip(6); - // and skip field attributes - uint16_t attr_count = 0; - read_be(attr_count); - while (attr_count) - { - skip(2); - uint32_t attr_length = 0; - read_be(attr_length); - skip(attr_length); - attr_count--; - } - field_count--; - } - - // class methods - /* - * method_info - * { - * u2 access_flags; - * u2 name_index; - * u2 descriptor_index; - * u2 attributes_count; - * attribute_info attributes[attributes_count]; - * } - */ - uint16_t method_count = 0; - read_be(method_count); - while (method_count) - { - skip(6); - // and skip method attributes - uint16_t attr_count = 0; - read_be(attr_count); - while (attr_count) - { - skip(2); - uint32_t attr_length = 0; - read_be(attr_length); - skip(attr_length); - attr_count--; - } - method_count--; - } - - // class attributes - // there are many kinds of attributes. this is just the generic wrapper structure. - // type is decided by attribute name. extensions to the standard are *possible* - // class annotations are one kind of a attribute (one per class) - /* - * attribute_info - * { - * u2 attribute_name_index; - * u4 attribute_length; - * u1 info[attribute_length]; - * } - */ - uint16_t class_attr_count = 0; - read_be(class_attr_count); - while (class_attr_count) - { - uint16_t name_idx = 0; - read_be(name_idx); - uint32_t attr_length = 0; - read_be(attr_length); - - auto name = constants[name_idx]; - if (name.str_data == "RuntimeVisibleAnnotations") - { - uint16_t num_annotations = 0; - read_be(num_annotations); - while (num_annotations) - { - visible_class_annotations.push_back(annotation::read(*this, constants)); - num_annotations--; - } - } - else - skip(attr_length); - class_attr_count--; - } - valid = true; - } - ; - bool valid; - bool is_synthetic; - uint32_t magic; - uint16_t minor_version; - uint16_t major_version; - constant_pool constants; - uint16_t access_flags; - uint16_t this_class; - uint16_t super_class; - // interfaces this class implements ? must be. investigate. - std::vector interfaces; - // FIXME: doesn't free up memory on delete - java::annotation_table visible_class_annotations; -}; -} diff --git a/libraries/classparser/src/classparser.cpp b/libraries/classparser/src/classparser.cpp deleted file mode 100644 index 601521f6..00000000 --- a/libraries/classparser/src/classparser.cpp +++ /dev/null @@ -1,83 +0,0 @@ -/* Copyright 2013-2021 MultiMC Contributors - * - * Authors: Orochimarufan - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -#include "classfile.h" -#include "classparser.h" - -#include -#include -#include - -namespace classparser -{ - -QString GetMinecraftJarVersion(QString jarName) -{ - QString version; - - // check if minecraft.jar exists - QFile jar(jarName); - if (!jar.exists()) - return version; - - // open minecraft.jar - QuaZip zip(&jar); - if (!zip.open(QuaZip::mdUnzip)) - return version; - - // open Minecraft.class - zip.setCurrentFile("net/minecraft/client/Minecraft.class", QuaZip::csSensitive); - QuaZipFile Minecraft(&zip); - if (!Minecraft.open(QuaZipFile::ReadOnly)) - return version; - - // read Minecraft.class - qint64 size = Minecraft.size(); - char *classfile = new char[size]; - Minecraft.read(classfile, size); - - // parse Minecraft.class - try - { - char *temp = classfile; - java::classfile MinecraftClass(temp, size); - java::constant_pool constants = MinecraftClass.constants; - for (java::constant_pool::container_type::const_iterator iter = constants.begin(); - iter != constants.end(); iter++) - { - const java::constant &constant = *iter; - if (constant.type != java::constant_type_t::j_string_data) - continue; - const std::string &str = constant.str_data; - qDebug() << QString::fromStdString(str); - if (str.compare(0, 20, "Minecraft Minecraft ") == 0) - { - version = str.substr(20).data(); - break; - } - } - } - catch (const java::classfile_exception &) { } - - // clean up - delete[] classfile; - Minecraft.close(); - zip.close(); - jar.close(); - - return version; -} -} diff --git a/libraries/classparser/src/constants.h b/libraries/classparser/src/constants.h deleted file mode 100644 index 47b325b9..00000000 --- a/libraries/classparser/src/constants.h +++ /dev/null @@ -1,232 +0,0 @@ -#pragma once -#include "errors.h" -#include "membuffer.h" -#include - -namespace java -{ -enum class constant_type_t : uint8_t -{ - j_hole = 0, // HACK: this is a hole in the array, because java is crazy - j_string_data = 1, - j_int = 3, - j_float = 4, - j_long = 5, - j_double = 6, - j_class = 7, - j_string = 8, - j_fieldref = 9, - j_methodref = 10, - j_interface_methodref = 11, - j_nameandtype = 12 - // FIXME: missing some constant types, see https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4 -}; - -struct ref_type_t -{ - /** - * Class reference: - * an index within the constant pool to a UTF-8 string containing - * the fully qualified class name (in internal format) - * Used for j_class, j_fieldref, j_methodref and j_interface_methodref - */ - uint16_t class_idx; - // used for j_fieldref, j_methodref and j_interface_methodref - uint16_t name_and_type_idx; -}; - -struct name_and_type_t -{ - uint16_t name_index; - uint16_t descriptor_index; -}; - -class constant -{ -public: - constant_type_t type = constant_type_t::j_hole; - - constant(util::membuffer &buf) - { - buf.read(type); - - // load data depending on type - switch (type) - { - case constant_type_t::j_float: - buf.read_be(data.int_data); - break; - case constant_type_t::j_int: - buf.read_be(data.int_data); // same as float data really - break; - case constant_type_t::j_double: - buf.read_be(data.long_data); - break; - case constant_type_t::j_long: - buf.read_be(data.long_data); // same as double - break; - case constant_type_t::j_class: - buf.read_be(data.ref_type.class_idx); - break; - case constant_type_t::j_fieldref: - case constant_type_t::j_methodref: - case constant_type_t::j_interface_methodref: - buf.read_be(data.ref_type.class_idx); - buf.read_be(data.ref_type.name_and_type_idx); - break; - case constant_type_t::j_string: - buf.read_be(data.index); - break; - case constant_type_t::j_string_data: - // HACK HACK: for now, we call these UTF-8 and do no further processing. - // Later, we should do some decoding. It's really modified UTF-8 - // * U+0000 is represented as 0xC0,0x80 invalid character - // * any single zero byte ends the string - // * characters above U+10000 are encoded like in CESU-8 - buf.read_jstr(str_data); - break; - case constant_type_t::j_nameandtype: - buf.read_be(data.name_and_type.name_index); - buf.read_be(data.name_and_type.descriptor_index); - break; - default: - // invalid constant type! - throw classfile_exception(); - } - } - constant(int) - { - } - - std::string toString() - { - std::ostringstream ss; - switch (type) - { - case constant_type_t::j_hole: - ss << "Fake legacy entry"; - break; - case constant_type_t::j_float: - ss << "Float: " << data.float_data; - break; - case constant_type_t::j_double: - ss << "Double: " << data.double_data; - break; - case constant_type_t::j_int: - ss << "Int: " << data.int_data; - break; - case constant_type_t::j_long: - ss << "Long: " << data.long_data; - break; - case constant_type_t::j_string_data: - ss << "StrData: " << str_data; - break; - case constant_type_t::j_string: - ss << "Str: " << data.index; - break; - case constant_type_t::j_fieldref: - ss << "FieldRef: " << data.ref_type.class_idx << " " << data.ref_type.name_and_type_idx; - break; - case constant_type_t::j_methodref: - ss << "MethodRef: " << data.ref_type.class_idx << " " << data.ref_type.name_and_type_idx; - break; - case constant_type_t::j_interface_methodref: - ss << "IfMethodRef: " << data.ref_type.class_idx << " " << data.ref_type.name_and_type_idx; - break; - case constant_type_t::j_class: - ss << "Class: " << data.ref_type.class_idx; - break; - case constant_type_t::j_nameandtype: - ss << "NameAndType: " << data.name_and_type.name_index << " " - << data.name_and_type.descriptor_index; - break; - default: - ss << "Invalid entry (" << int(type) << ")"; - break; - } - return ss.str(); - } - - std::string str_data; /** String data in 'modified utf-8'.*/ - - // store everything here. - union - { - int32_t int_data; - int64_t long_data; - float float_data; - double double_data; - uint16_t index; - ref_type_t ref_type; - name_and_type_t name_and_type; - } data = {0}; -}; - -/** - * A helper class that represents the custom container used in Java class file for storage of - * constants - */ -class constant_pool -{ -public: - /** - * Create a pool of constants - */ - constant_pool() - { - } - /** - * Load a java constant pool - */ - void load(util::membuffer &buf) - { - // FIXME: @SANITY this should check for the end of buffer. - uint16_t length = 0; - buf.read_be(length); - length--; - const constant *last_constant = nullptr; - while (length) - { - const constant &cnst = constant(buf); - constants.push_back(cnst); - last_constant = &constants[constants.size() - 1]; - if (last_constant->type == constant_type_t::j_double || - last_constant->type == constant_type_t::j_long) - { - // push in a fake constant to preserve indexing - constants.push_back(constant(0)); - length -= 2; - } - else - { - length--; - } - } - } - typedef std::vector container_type; - /** - * Access constants based on jar file index numbers (index of the first element is 1) - */ - java::constant &operator[](std::size_t constant_index) - { - if (constant_index == 0 || constant_index > constants.size()) - { - throw classfile_exception(); - } - return constants[constant_index - 1]; - } - ; - container_type::const_iterator begin() const - { - return constants.begin(); - } - ; - container_type::const_iterator end() const - { - return constants.end(); - } - -private: - container_type constants; -}; -} diff --git a/libraries/classparser/src/errors.h b/libraries/classparser/src/errors.h deleted file mode 100644 index ddbbd828..00000000 --- a/libraries/classparser/src/errors.h +++ /dev/null @@ -1,8 +0,0 @@ -#pragma once -#include -namespace java -{ -class classfile_exception : public std::exception -{ -}; -} diff --git a/libraries/classparser/src/javaendian.h b/libraries/classparser/src/javaendian.h deleted file mode 100644 index 5a6e107b..00000000 --- a/libraries/classparser/src/javaendian.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once -#include - -/** - * Swap bytes between big endian and local number representation - */ -namespace util -{ -#ifdef MULTIMC_BIG_ENDIAN -inline uint64_t bigswap(uint64_t x) -{ - return x; -} - -inline uint32_t bigswap(uint32_t x) -{ - return x; -} - -inline uint16_t bigswap(uint16_t x) -{ - return x; -} - -#else -inline uint64_t bigswap(uint64_t x) -{ - return (x >> 56) | ((x << 40) & 0x00FF000000000000) | ((x << 24) & 0x0000FF0000000000) | - ((x << 8) & 0x000000FF00000000) | ((x >> 8) & 0x00000000FF000000) | - ((x >> 24) & 0x0000000000FF0000) | ((x >> 40) & 0x000000000000FF00) | (x << 56); -} - -inline uint32_t bigswap(uint32_t x) -{ - return (x >> 24) | ((x << 8) & 0x00FF0000) | ((x >> 8) & 0x0000FF00) | (x << 24); -} - -inline uint16_t bigswap(uint16_t x) -{ - return (x >> 8) | (x << 8); -} - -#endif - -inline int64_t bigswap(int64_t x) -{ - return static_cast(bigswap(static_cast(x))); -} - -inline int32_t bigswap(int32_t x) -{ - return static_cast(bigswap(static_cast(x))); -} - -inline int16_t bigswap(int16_t x) -{ - return static_cast(bigswap(static_cast(x))); -} -} diff --git a/libraries/classparser/src/membuffer.h b/libraries/classparser/src/membuffer.h deleted file mode 100644 index f81c9705..00000000 --- a/libraries/classparser/src/membuffer.h +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include "javaendian.h" - -namespace util -{ -class membuffer -{ -public: - membuffer(char *buffer, std::size_t size) - { - current = start = buffer; - end = start + size; - } - ~membuffer() - { - // maybe? possibly? left out to avoid confusion. for now. - // delete start; - } - /** - * Read some value. That's all ;) - */ - template void read(T &val) - { - val = *(T *)current; - current += sizeof(T); - } - /** - * Read a big-endian number - * valid for 2-byte, 4-byte and 8-byte variables - */ - template void read_be(T &val) - { - val = util::bigswap(*(T *)current); - current += sizeof(T); - } - /** - * Read a string in the format: - * 2B length (big endian, unsigned) - * length bytes data - */ - void read_jstr(std::string &str) - { - uint16_t length = 0; - read_be(length); - str.append(current, length); - current += length; - } - /** - * Skip N bytes - */ - void skip(std::size_t N) - { - current += N; - } - -private: - char *start, *end, *current; -}; -} diff --git a/libraries/xz-embedded/CMakeLists.txt b/libraries/xz-embedded/CMakeLists.txt deleted file mode 100644 index 4ce46102..00000000 --- a/libraries/xz-embedded/CMakeLists.txt +++ /dev/null @@ -1,26 +0,0 @@ -cmake_minimum_required(VERSION 3.9.4) -project(xz-embedded LANGUAGES C) - -option(XZ_BUILD_BCJ "Build xz-embedded with BCJ support (native binary optimization)" OFF) -option(XZ_BUILD_CRC64 "Build xz-embedded with CRC64 checksum support" ON) -option(XZ_BUILD_MINIDEC "Build a tiny utility that decompresses xz streams" OFF) - -# See include/xz.h for manual feature configuration -# tweak this list and xz.h to fit your needs - -set(XZ_SOURCES - src/xz_crc32.c - src/xz_crc64.c - src/xz_dec_lzma2.c - src/xz_dec_stream.c -# src/xz_dec_bcj.c -) -add_library(xz-embedded STATIC ${XZ_SOURCES}) -target_include_directories(xz-embedded PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include") -set_property(TARGET xz-embedded PROPERTY C_STANDARD 99) - -if(${XZ_BUILD_MINIDEC}) - add_executable(xzminidec xzminidec.c) - target_link_libraries(xzminidec xz-embedded) - set_property(TARGET xzminidec PROPERTY C_STANDARD 99) -endif() diff --git a/libraries/xz-embedded/include/xz.h b/libraries/xz-embedded/include/xz.h deleted file mode 100644 index 3779124c..00000000 --- a/libraries/xz-embedded/include/xz.h +++ /dev/null @@ -1,321 +0,0 @@ -/* - * XZ decompressor - * - * Authors: Lasse Collin - * Igor Pavlov - * - * This file has been put into the public domain. - * You can do whatever you want with this file. - */ - -#ifndef XZ_H -#define XZ_H - -#ifdef __KERNEL__ -#include -#include -#else -#include -#include -#endif - -#ifdef __cplusplus -extern "C" { -#endif - -/* Definitions that determine available features */ -#define XZ_DEC_ANY_CHECK 1 -#define XZ_USE_CRC64 1 - -// native machine code compression stuff -/* -#define XZ_DEC_X86 -#define XZ_DEC_POWERPC -#define XZ_DEC_IA64 -#define XZ_DEC_ARM -#define XZ_DEC_ARMTHUMB -#define XZ_DEC_SPARC -*/ - -/* In Linux, this is used to make extern functions static when needed. */ -#ifndef XZ_EXTERN -#define XZ_EXTERN extern -#endif - -/** - * enum xz_mode - Operation mode - * - * @XZ_SINGLE: Single-call mode. This uses less RAM than - * than multi-call modes, because the LZMA2 - * dictionary doesn't need to be allocated as - * part of the decoder state. All required data - * structures are allocated at initialization, - * so xz_dec_run() cannot return XZ_MEM_ERROR. - * @XZ_PREALLOC: Multi-call mode with preallocated LZMA2 - * dictionary buffer. All data structures are - * allocated at initialization, so xz_dec_run() - * cannot return XZ_MEM_ERROR. - * @XZ_DYNALLOC: Multi-call mode. The LZMA2 dictionary is - * allocated once the required size has been - * parsed from the stream headers. If the - * allocation fails, xz_dec_run() will return - * XZ_MEM_ERROR. - * - * It is possible to enable support only for a subset of the above - * modes at compile time by defining XZ_DEC_SINGLE, XZ_DEC_PREALLOC, - * or XZ_DEC_DYNALLOC. The xz_dec kernel module is always compiled - * with support for all operation modes, but the preboot code may - * be built with fewer features to minimize code size. - */ -enum xz_mode -{ - XZ_SINGLE, - XZ_PREALLOC, - XZ_DYNALLOC -}; - -/** - * enum xz_ret - Return codes - * @XZ_OK: Everything is OK so far. More input or more - * output space is required to continue. This - * return code is possible only in multi-call mode - * (XZ_PREALLOC or XZ_DYNALLOC). - * @XZ_STREAM_END: Operation finished successfully. - * @XZ_UNSUPPORTED_CHECK: Integrity check type is not supported. Decoding - * is still possible in multi-call mode by simply - * calling xz_dec_run() again. - * Note that this return value is used only if - * XZ_DEC_ANY_CHECK was defined at build time, - * which is not used in the kernel. Unsupported - * check types return XZ_OPTIONS_ERROR if - * XZ_DEC_ANY_CHECK was not defined at build time. - * @XZ_MEM_ERROR: Allocating memory failed. This return code is - * possible only if the decoder was initialized - * with XZ_DYNALLOC. The amount of memory that was - * tried to be allocated was no more than the - * dict_max argument given to xz_dec_init(). - * @XZ_MEMLIMIT_ERROR: A bigger LZMA2 dictionary would be needed than - * allowed by the dict_max argument given to - * xz_dec_init(). This return value is possible - * only in multi-call mode (XZ_PREALLOC or - * XZ_DYNALLOC); the single-call mode (XZ_SINGLE) - * ignores the dict_max argument. - * @XZ_FORMAT_ERROR: File format was not recognized (wrong magic - * bytes). - * @XZ_OPTIONS_ERROR: This implementation doesn't support the requested - * compression options. In the decoder this means - * that the header CRC32 matches, but the header - * itself specifies something that we don't support. - * @XZ_DATA_ERROR: Compressed data is corrupt. - * @XZ_BUF_ERROR: Cannot make any progress. Details are slightly - * different between multi-call and single-call - * mode; more information below. - * - * In multi-call mode, XZ_BUF_ERROR is returned when two consecutive calls - * to XZ code cannot consume any input and cannot produce any new output. - * This happens when there is no new input available, or the output buffer - * is full while at least one output byte is still pending. Assuming your - * code is not buggy, you can get this error only when decoding a compressed - * stream that is truncated or otherwise corrupt. - * - * In single-call mode, XZ_BUF_ERROR is returned only when the output buffer - * is too small or the compressed input is corrupt in a way that makes the - * decoder produce more output than the caller expected. When it is - * (relatively) clear that the compressed input is truncated, XZ_DATA_ERROR - * is used instead of XZ_BUF_ERROR. - */ -enum xz_ret -{ - XZ_OK, - XZ_STREAM_END, - XZ_UNSUPPORTED_CHECK, - XZ_MEM_ERROR, - XZ_MEMLIMIT_ERROR, - XZ_FORMAT_ERROR, - XZ_OPTIONS_ERROR, - XZ_DATA_ERROR, - XZ_BUF_ERROR -}; - -/** - * struct xz_buf - Passing input and output buffers to XZ code - * @in: Beginning of the input buffer. This may be NULL if and only - * if in_pos is equal to in_size. - * @in_pos: Current position in the input buffer. This must not exceed - * in_size. - * @in_size: Size of the input buffer - * @out: Beginning of the output buffer. This may be NULL if and only - * if out_pos is equal to out_size. - * @out_pos: Current position in the output buffer. This must not exceed - * out_size. - * @out_size: Size of the output buffer - * - * Only the contents of the output buffer from out[out_pos] onward, and - * the variables in_pos and out_pos are modified by the XZ code. - */ -struct xz_buf -{ - const uint8_t *in; - size_t in_pos; - size_t in_size; - - uint8_t *out; - size_t out_pos; - size_t out_size; -}; - -/** - * struct xz_dec - Opaque type to hold the XZ decoder state - */ -struct xz_dec; - -/** - * xz_dec_init() - Allocate and initialize a XZ decoder state - * @mode: Operation mode - * @dict_max: Maximum size of the LZMA2 dictionary (history buffer) for - * multi-call decoding. This is ignored in single-call mode - * (mode == XZ_SINGLE). LZMA2 dictionary is always 2^n bytes - * or 2^n + 2^(n-1) bytes (the latter sizes are less common - * in practice), so other values for dict_max don't make sense. - * In the kernel, dictionary sizes of 64 KiB, 128 KiB, 256 KiB, - * 512 KiB, and 1 MiB are probably the only reasonable values, - * except for kernel and initramfs images where a bigger - * dictionary can be fine and useful. - * - * Single-call mode (XZ_SINGLE): xz_dec_run() decodes the whole stream at - * once. The caller must provide enough output space or the decoding will - * fail. The output space is used as the dictionary buffer, which is why - * there is no need to allocate the dictionary as part of the decoder's - * internal state. - * - * Because the output buffer is used as the workspace, streams encoded using - * a big dictionary are not a problem in single-call mode. It is enough that - * the output buffer is big enough to hold the actual uncompressed data; it - * can be smaller than the dictionary size stored in the stream headers. - * - * Multi-call mode with preallocated dictionary (XZ_PREALLOC): dict_max bytes - * of memory is preallocated for the LZMA2 dictionary. This way there is no - * risk that xz_dec_run() could run out of memory, since xz_dec_run() will - * never allocate any memory. Instead, if the preallocated dictionary is too - * small for decoding the given input stream, xz_dec_run() will return - * XZ_MEMLIMIT_ERROR. Thus, it is important to know what kind of data will be - * decoded to avoid allocating excessive amount of memory for the dictionary. - * - * Multi-call mode with dynamically allocated dictionary (XZ_DYNALLOC): - * dict_max specifies the maximum allowed dictionary size that xz_dec_run() - * may allocate once it has parsed the dictionary size from the stream - * headers. This way excessive allocations can be avoided while still - * limiting the maximum memory usage to a sane value to prevent running the - * system out of memory when decompressing streams from untrusted sources. - * - * On success, xz_dec_init() returns a pointer to struct xz_dec, which is - * ready to be used with xz_dec_run(). If memory allocation fails, - * xz_dec_init() returns NULL. - */ -XZ_EXTERN struct xz_dec *xz_dec_init(enum xz_mode mode, uint32_t dict_max); - -/** - * xz_dec_run() - Run the XZ decoder - * @s: Decoder state allocated using xz_dec_init() - * @b: Input and output buffers - * - * The possible return values depend on build options and operation mode. - * See enum xz_ret for details. - * - * Note that if an error occurs in single-call mode (return value is not - * XZ_STREAM_END), b->in_pos and b->out_pos are not modified and the - * contents of the output buffer from b->out[b->out_pos] onward are - * undefined. This is true even after XZ_BUF_ERROR, because with some filter - * chains, there may be a second pass over the output buffer, and this pass - * cannot be properly done if the output buffer is truncated. Thus, you - * cannot give the single-call decoder a too small buffer and then expect to - * get that amount valid data from the beginning of the stream. You must use - * the multi-call decoder if you don't want to uncompress the whole stream. - */ -XZ_EXTERN enum xz_ret xz_dec_run(struct xz_dec *s, struct xz_buf *b); - -/** - * xz_dec_reset() - Reset an already allocated decoder state - * @s: Decoder state allocated using xz_dec_init() - * - * This function can be used to reset the multi-call decoder state without - * freeing and reallocating memory with xz_dec_end() and xz_dec_init(). - * - * In single-call mode, xz_dec_reset() is always called in the beginning of - * xz_dec_run(). Thus, explicit call to xz_dec_reset() is useful only in - * multi-call mode. - */ -XZ_EXTERN void xz_dec_reset(struct xz_dec *s); - -/** - * xz_dec_end() - Free the memory allocated for the decoder state - * @s: Decoder state allocated using xz_dec_init(). If s is NULL, - * this function does nothing. - */ -XZ_EXTERN void xz_dec_end(struct xz_dec *s); - -/* - * Standalone build (userspace build or in-kernel build for boot time use) - * needs a CRC32 implementation. For normal in-kernel use, kernel's own - * CRC32 module is used instead, and users of this module don't need to - * care about the functions below. - */ -#ifndef XZ_INTERNAL_CRC32 -#ifdef __KERNEL__ -#define XZ_INTERNAL_CRC32 0 -#else -#define XZ_INTERNAL_CRC32 1 -#endif -#endif - -/* - * If CRC64 support has been enabled with XZ_USE_CRC64, a CRC64 - * implementation is needed too. - */ -#ifndef XZ_USE_CRC64 -#undef XZ_INTERNAL_CRC64 -#define XZ_INTERNAL_CRC64 0 -#endif -#ifndef XZ_INTERNAL_CRC64 -#ifdef __KERNEL__ -#error Using CRC64 in the kernel has not been implemented. -#else -#define XZ_INTERNAL_CRC64 1 -#endif -#endif - -#if XZ_INTERNAL_CRC32 -/* - * This must be called before any other xz_* function to initialize - * the CRC32 lookup table. - */ -XZ_EXTERN void xz_crc32_init(void); - -/* - * Update CRC32 value using the polynomial from IEEE-802.3. To start a new - * calculation, the third argument must be zero. To continue the calculation, - * the previously returned value is passed as the third argument. - */ -XZ_EXTERN uint32_t xz_crc32(const uint8_t *buf, size_t size, uint32_t crc); -#endif - -#if XZ_INTERNAL_CRC64 -/* - * This must be called before any other xz_* function (except xz_crc32_init()) - * to initialize the CRC64 lookup table. - */ -XZ_EXTERN void xz_crc64_init(void); - -/* - * Update CRC64 value using the polynomial from ECMA-182. To start a new - * calculation, the third argument must be zero. To continue the calculation, - * the previously returned value is passed as the third argument. - */ -XZ_EXTERN uint64_t xz_crc64(const uint8_t *buf, size_t size, uint64_t crc); -#endif - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/libraries/xz-embedded/src/xz_config.h b/libraries/xz-embedded/src/xz_config.h deleted file mode 100644 index effdb1bd..00000000 --- a/libraries/xz-embedded/src/xz_config.h +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Private includes and definitions for userspace use of XZ Embedded - * - * Author: Lasse Collin - * - * This file has been put into the public domain. - * You can do whatever you want with this file. - */ - -#ifndef XZ_CONFIG_H -#define XZ_CONFIG_H - -/* Uncomment to enable CRC64 support. */ -/* #define XZ_USE_CRC64 */ - -/* Uncomment as needed to enable BCJ filter decoders. */ -/* #define XZ_DEC_X86 */ -/* #define XZ_DEC_POWERPC */ -/* #define XZ_DEC_IA64 */ -/* #define XZ_DEC_ARM */ -/* #define XZ_DEC_ARMTHUMB */ -/* #define XZ_DEC_SPARC */ - -/* - * MSVC doesn't support modern C but XZ Embedded is mostly C89 - * so these are enough. - */ -#ifdef _MSC_VER -typedef unsigned char bool; -#define true 1 -#define false 0 -#define inline __inline -#else -#include -#endif - -#include -#include - -#include "xz.h" - -#define kmalloc(size, flags) malloc(size) -#define kfree(ptr) free(ptr) -#define vmalloc(size) malloc(size) -#define vfree(ptr) free(ptr) - -#define memeq(a, b, size) (memcmp(a, b, size) == 0) -#define memzero(buf, size) memset(buf, 0, size) - -#ifndef min -#define min(x, y) ((x) < (y) ? (x) : (y)) -#endif -#define min_t(type, x, y) min(x, y) - -/* - * Some functions have been marked with __always_inline to keep the - * performance reasonable even when the compiler is optimizing for - * small code size. You may be able to save a few bytes by #defining - * __always_inline to plain inline, but don't complain if the code - * becomes slow. - * - * NOTE: System headers on GNU/Linux may #define this macro already, - * so if you want to change it, you need to #undef it first. - */ -#ifndef __always_inline -#ifdef __GNUC__ -#define __always_inline inline __attribute__((__always_inline__)) -#else -#define __always_inline inline -#endif -#endif - -/* Inline functions to access unaligned unsigned 32-bit integers */ -#ifndef get_unaligned_le32 -static inline uint32_t get_unaligned_le32(const uint8_t *buf) -{ - return (uint32_t)buf[0] | ((uint32_t)buf[1] << 8) | ((uint32_t)buf[2] << 16) | - ((uint32_t)buf[3] << 24); -} -#endif - -#ifndef get_unaligned_be32 -static inline uint32_t get_unaligned_be32(const uint8_t *buf) -{ - return (uint32_t)(buf[0] << 24) | ((uint32_t)buf[1] << 16) | ((uint32_t)buf[2] << 8) | - (uint32_t)buf[3]; -} -#endif - -#ifndef put_unaligned_le32 -static inline void put_unaligned_le32(uint32_t val, uint8_t *buf) -{ - buf[0] = (uint8_t)val; - buf[1] = (uint8_t)(val >> 8); - buf[2] = (uint8_t)(val >> 16); - buf[3] = (uint8_t)(val >> 24); -} -#endif - -#ifndef put_unaligned_be32 -static inline void put_unaligned_be32(uint32_t val, uint8_t *buf) -{ - buf[0] = (uint8_t)(val >> 24); - buf[1] = (uint8_t)(val >> 16); - buf[2] = (uint8_t)(val >> 8); - buf[3] = (uint8_t)val; -} -#endif - -/* - * Use get_unaligned_le32() also for aligned access for simplicity. On - * little endian systems, #define get_le32(ptr) (*(const uint32_t *)(ptr)) - * could save a few bytes in code size. - */ -#ifndef get_le32 -#define get_le32 get_unaligned_le32 -#endif - -#endif diff --git a/libraries/xz-embedded/src/xz_crc32.c b/libraries/xz-embedded/src/xz_crc32.c deleted file mode 100644 index 65d9d5b8..00000000 --- a/libraries/xz-embedded/src/xz_crc32.c +++ /dev/null @@ -1,61 +0,0 @@ -/* - * CRC32 using the polynomial from IEEE-802.3 - * - * Authors: Lasse Collin - * Igor Pavlov - * - * This file has been put into the public domain. - * You can do whatever you want with this file. - */ - -/* - * This is not the fastest implementation, but it is pretty compact. - * The fastest versions of xz_crc32() on modern CPUs without hardware - * accelerated CRC instruction are 3-5 times as fast as this version, - * but they are bigger and use more memory for the lookup table. - */ - -#include "xz_private.h" - -/* - * STATIC_RW_DATA is used in the pre-boot environment on some architectures. - * See for details. - */ -#ifndef STATIC_RW_DATA -#define STATIC_RW_DATA static -#endif - -STATIC_RW_DATA uint32_t xz_crc32_table[256]; - -XZ_EXTERN void xz_crc32_init(void) -{ - const uint32_t poly = 0xEDB88320; - - uint32_t i; - uint32_t j; - uint32_t r; - - for (i = 0; i < 256; ++i) - { - r = i; - for (j = 0; j < 8; ++j) - r = (r >> 1) ^ (poly & ~((r & 1) - 1)); - - xz_crc32_table[i] = r; - } - - return; -} - -XZ_EXTERN uint32_t xz_crc32(const uint8_t *buf, size_t size, uint32_t crc) -{ - crc = ~crc; - - while (size != 0) - { - crc = xz_crc32_table[*buf++ ^ (crc & 0xFF)] ^ (crc >> 8); - --size; - } - - return ~crc; -} diff --git a/libraries/xz-embedded/src/xz_crc64.c b/libraries/xz-embedded/src/xz_crc64.c deleted file mode 100644 index 0f711d8d..00000000 --- a/libraries/xz-embedded/src/xz_crc64.c +++ /dev/null @@ -1,52 +0,0 @@ -/* - * CRC64 using the polynomial from ECMA-182 - * - * This file is similar to xz_crc32.c. See the comments there. - * - * Authors: Lasse Collin - * Igor Pavlov - * - * This file has been put into the public domain. - * You can do whatever you want with this file. - */ - -#include "xz_private.h" - -#ifndef STATIC_RW_DATA -#define STATIC_RW_DATA static -#endif - -STATIC_RW_DATA uint64_t xz_crc64_table[256]; - -XZ_EXTERN void xz_crc64_init(void) -{ - const uint64_t poly = 0xC96C5795D7870F42; - - uint32_t i; - uint32_t j; - uint64_t r; - - for (i = 0; i < 256; ++i) - { - r = i; - for (j = 0; j < 8; ++j) - r = (r >> 1) ^ (poly & ~((r & 1) - 1)); - - xz_crc64_table[i] = r; - } - - return; -} - -XZ_EXTERN uint64_t xz_crc64(const uint8_t *buf, size_t size, uint64_t crc) -{ - crc = ~crc; - - while (size != 0) - { - crc = xz_crc64_table[*buf++ ^ (crc & 0xFF)] ^ (crc >> 8); - --size; - } - - return ~crc; -} diff --git a/libraries/xz-embedded/src/xz_dec_bcj.c b/libraries/xz-embedded/src/xz_dec_bcj.c deleted file mode 100644 index a79fa76d..00000000 --- a/libraries/xz-embedded/src/xz_dec_bcj.c +++ /dev/null @@ -1,588 +0,0 @@ -/* - * Branch/Call/Jump (BCJ) filter decoders - * - * Authors: Lasse Collin - * Igor Pavlov - * - * This file has been put into the public domain. - * You can do whatever you want with this file. - */ - -#include "xz_private.h" - -/* - * The rest of the file is inside this ifdef. It makes things a little more - * convenient when building without support for any BCJ filters. - */ -#ifdef XZ_DEC_BCJ - -struct xz_dec_bcj -{ - /* Type of the BCJ filter being used */ - enum - { - BCJ_X86 = 4, /* x86 or x86-64 */ - BCJ_POWERPC = 5, /* Big endian only */ - BCJ_IA64 = 6, /* Big or little endian */ - BCJ_ARM = 7, /* Little endian only */ - BCJ_ARMTHUMB = 8, /* Little endian only */ - BCJ_SPARC = 9 /* Big or little endian */ - } type; - - /* - * Return value of the next filter in the chain. We need to preserve - * this information across calls, because we must not call the next - * filter anymore once it has returned XZ_STREAM_END. - */ - enum xz_ret ret; - - /* True if we are operating in single-call mode. */ - bool single_call; - - /* - * Absolute position relative to the beginning of the uncompressed - * data (in a single .xz Block). We care only about the lowest 32 - * bits so this doesn't need to be uint64_t even with big files. - */ - uint32_t pos; - - /* x86 filter state */ - uint32_t x86_prev_mask; - - /* Temporary space to hold the variables from struct xz_buf */ - uint8_t *out; - size_t out_pos; - size_t out_size; - - struct - { - /* Amount of already filtered data in the beginning of buf */ - size_t filtered; - - /* Total amount of data currently stored in buf */ - size_t size; - - /* - * Buffer to hold a mix of filtered and unfiltered data. This - * needs to be big enough to hold Alignment + 2 * Look-ahead: - * - * Type Alignment Look-ahead - * x86 1 4 - * PowerPC 4 0 - * IA-64 16 0 - * ARM 4 0 - * ARM-Thumb 2 2 - * SPARC 4 0 - */ - uint8_t buf[16]; - } temp; -}; - -#ifdef XZ_DEC_X86 -/* - * This is used to test the most significant byte of a memory address - * in an x86 instruction. - */ -static inline int bcj_x86_test_msbyte(uint8_t b) -{ - return b == 0x00 || b == 0xFF; -} - -static size_t bcj_x86(struct xz_dec_bcj *s, uint8_t *buf, size_t size) -{ - static const bool mask_to_allowed_status[8] = {true, true, true, false, - true, false, false, false}; - - static const uint8_t mask_to_bit_num[8] = {0, 1, 2, 2, 3, 3, 3, 3}; - - size_t i; - size_t prev_pos = (size_t) - 1; - uint32_t prev_mask = s->x86_prev_mask; - uint32_t src; - uint32_t dest; - uint32_t j; - uint8_t b; - - if (size <= 4) - return 0; - - size -= 4; - for (i = 0; i < size; ++i) - { - if ((buf[i] & 0xFE) != 0xE8) - continue; - - prev_pos = i - prev_pos; - if (prev_pos > 3) - { - prev_mask = 0; - } - else - { - prev_mask = (prev_mask << (prev_pos - 1)) & 7; - if (prev_mask != 0) - { - b = buf[i + 4 - mask_to_bit_num[prev_mask]]; - if (!mask_to_allowed_status[prev_mask] || bcj_x86_test_msbyte(b)) - { - prev_pos = i; - prev_mask = (prev_mask << 1) | 1; - continue; - } - } - } - - prev_pos = i; - - if (bcj_x86_test_msbyte(buf[i + 4])) - { - src = get_unaligned_le32(buf + i + 1); - while (true) - { - dest = src - (s->pos + (uint32_t)i + 5); - if (prev_mask == 0) - break; - - j = mask_to_bit_num[prev_mask] * 8; - b = (uint8_t)(dest >> (24 - j)); - if (!bcj_x86_test_msbyte(b)) - break; - - src = dest ^ (((uint32_t)1 << (32 - j)) - 1); - } - - dest &= 0x01FFFFFF; - dest |= (uint32_t)0 - (dest & 0x01000000); - put_unaligned_le32(dest, buf + i + 1); - i += 4; - } - else - { - prev_mask = (prev_mask << 1) | 1; - } - } - - prev_pos = i - prev_pos; - s->x86_prev_mask = prev_pos > 3 ? 0 : prev_mask << (prev_pos - 1); - return i; -} -#endif - -#ifdef XZ_DEC_POWERPC -static size_t bcj_powerpc(struct xz_dec_bcj *s, uint8_t *buf, size_t size) -{ - size_t i; - uint32_t instr; - - for (i = 0; i + 4 <= size; i += 4) - { - instr = get_unaligned_be32(buf + i); - if ((instr & 0xFC000003) == 0x48000001) - { - instr &= 0x03FFFFFC; - instr -= s->pos + (uint32_t)i; - instr &= 0x03FFFFFC; - instr |= 0x48000001; - put_unaligned_be32(instr, buf + i); - } - } - - return i; -} -#endif - -#ifdef XZ_DEC_IA64 -static size_t bcj_ia64(struct xz_dec_bcj *s, uint8_t *buf, size_t size) -{ - static const uint8_t branch_table[32] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 4, 4, 6, 6, 0, 0, 7, 7, 4, 4, 0, 0, 4, 4, 0, 0}; - - /* - * The local variables take a little bit stack space, but it's less - * than what LZMA2 decoder takes, so it doesn't make sense to reduce - * stack usage here without doing that for the LZMA2 decoder too. - */ - - /* Loop counters */ - size_t i; - size_t j; - - /* Instruction slot (0, 1, or 2) in the 128-bit instruction word */ - uint32_t slot; - - /* Bitwise offset of the instruction indicated by slot */ - uint32_t bit_pos; - - /* bit_pos split into byte and bit parts */ - uint32_t byte_pos; - uint32_t bit_res; - - /* Address part of an instruction */ - uint32_t addr; - - /* Mask used to detect which instructions to convert */ - uint32_t mask; - - /* 41-bit instruction stored somewhere in the lowest 48 bits */ - uint64_t instr; - - /* Instruction normalized with bit_res for easier manipulation */ - uint64_t norm; - - for (i = 0; i + 16 <= size; i += 16) - { - mask = branch_table[buf[i] & 0x1F]; - for (slot = 0, bit_pos = 5; slot < 3; ++slot, bit_pos += 41) - { - if (((mask >> slot) & 1) == 0) - continue; - - byte_pos = bit_pos >> 3; - bit_res = bit_pos & 7; - instr = 0; - for (j = 0; j < 6; ++j) - instr |= (uint64_t)(buf[i + j + byte_pos]) << (8 * j); - - norm = instr >> bit_res; - - if (((norm >> 37) & 0x0F) == 0x05 && ((norm >> 9) & 0x07) == 0) - { - addr = (norm >> 13) & 0x0FFFFF; - addr |= ((uint32_t)(norm >> 36) & 1) << 20; - addr <<= 4; - addr -= s->pos + (uint32_t)i; - addr >>= 4; - - norm &= ~((uint64_t)0x8FFFFF << 13); - norm |= (uint64_t)(addr & 0x0FFFFF) << 13; - norm |= (uint64_t)(addr & 0x100000) << (36 - 20); - - instr &= (1 << bit_res) - 1; - instr |= norm << bit_res; - - for (j = 0; j < 6; j++) - buf[i + j + byte_pos] = (uint8_t)(instr >> (8 * j)); - } - } - } - - return i; -} -#endif - -#ifdef XZ_DEC_ARM -static size_t bcj_arm(struct xz_dec_bcj *s, uint8_t *buf, size_t size) -{ - size_t i; - uint32_t addr; - - for (i = 0; i + 4 <= size; i += 4) - { - if (buf[i + 3] == 0xEB) - { - addr = - (uint32_t)buf[i] | ((uint32_t)buf[i + 1] << 8) | ((uint32_t)buf[i + 2] << 16); - addr <<= 2; - addr -= s->pos + (uint32_t)i + 8; - addr >>= 2; - buf[i] = (uint8_t)addr; - buf[i + 1] = (uint8_t)(addr >> 8); - buf[i + 2] = (uint8_t)(addr >> 16); - } - } - - return i; -} -#endif - -#ifdef XZ_DEC_ARMTHUMB -static size_t bcj_armthumb(struct xz_dec_bcj *s, uint8_t *buf, size_t size) -{ - size_t i; - uint32_t addr; - - for (i = 0; i + 4 <= size; i += 2) - { - if ((buf[i + 1] & 0xF8) == 0xF0 && (buf[i + 3] & 0xF8) == 0xF8) - { - addr = (((uint32_t)buf[i + 1] & 0x07) << 19) | ((uint32_t)buf[i] << 11) | - (((uint32_t)buf[i + 3] & 0x07) << 8) | (uint32_t)buf[i + 2]; - addr <<= 1; - addr -= s->pos + (uint32_t)i + 4; - addr >>= 1; - buf[i + 1] = (uint8_t)(0xF0 | ((addr >> 19) & 0x07)); - buf[i] = (uint8_t)(addr >> 11); - buf[i + 3] = (uint8_t)(0xF8 | ((addr >> 8) & 0x07)); - buf[i + 2] = (uint8_t)addr; - i += 2; - } - } - - return i; -} -#endif - -#ifdef XZ_DEC_SPARC -static size_t bcj_sparc(struct xz_dec_bcj *s, uint8_t *buf, size_t size) -{ - size_t i; - uint32_t instr; - - for (i = 0; i + 4 <= size; i += 4) - { - instr = get_unaligned_be32(buf + i); - if ((instr >> 22) == 0x100 || (instr >> 22) == 0x1FF) - { - instr <<= 2; - instr -= s->pos + (uint32_t)i; - instr >>= 2; - instr = - ((uint32_t)0x40000000 - (instr & 0x400000)) | 0x40000000 | (instr & 0x3FFFFF); - put_unaligned_be32(instr, buf + i); - } - } - - return i; -} -#endif - -/* - * Apply the selected BCJ filter. Update *pos and s->pos to match the amount - * of data that got filtered. - * - * NOTE: This is implemented as a switch statement to avoid using function - * pointers, which could be problematic in the kernel boot code, which must - * avoid pointers to static data (at least on x86). - */ -static void bcj_apply(struct xz_dec_bcj *s, uint8_t *buf, size_t *pos, size_t size) -{ - size_t filtered; - - buf += *pos; - size -= *pos; - - switch (s->type) - { -#ifdef XZ_DEC_X86 - case BCJ_X86: - filtered = bcj_x86(s, buf, size); - break; -#endif -#ifdef XZ_DEC_POWERPC - case BCJ_POWERPC: - filtered = bcj_powerpc(s, buf, size); - break; -#endif -#ifdef XZ_DEC_IA64 - case BCJ_IA64: - filtered = bcj_ia64(s, buf, size); - break; -#endif -#ifdef XZ_DEC_ARM - case BCJ_ARM: - filtered = bcj_arm(s, buf, size); - break; -#endif -#ifdef XZ_DEC_ARMTHUMB - case BCJ_ARMTHUMB: - filtered = bcj_armthumb(s, buf, size); - break; -#endif -#ifdef XZ_DEC_SPARC - case BCJ_SPARC: - filtered = bcj_sparc(s, buf, size); - break; -#endif - default: - /* Never reached but silence compiler warnings. */ - filtered = 0; - break; - } - - *pos += filtered; - s->pos += filtered; -} - -/* - * Flush pending filtered data from temp to the output buffer. - * Move the remaining mixture of possibly filtered and unfiltered - * data to the beginning of temp. - */ -static void bcj_flush(struct xz_dec_bcj *s, struct xz_buf *b) -{ - size_t copy_size; - - copy_size = min_t(size_t, s->temp.filtered, b->out_size - b->out_pos); - memcpy(b->out + b->out_pos, s->temp.buf, copy_size); - b->out_pos += copy_size; - - s->temp.filtered -= copy_size; - s->temp.size -= copy_size; - memmove(s->temp.buf, s->temp.buf + copy_size, s->temp.size); -} - -/* - * The BCJ filter functions are primitive in sense that they process the - * data in chunks of 1-16 bytes. To hide this issue, this function does - * some buffering. - */ -XZ_EXTERN enum xz_ret xz_dec_bcj_run(struct xz_dec_bcj *s, struct xz_dec_lzma2 *lzma2, - struct xz_buf *b) -{ - size_t out_start; - - /* - * Flush pending already filtered data to the output buffer. Return - * immediatelly if we couldn't flush everything, or if the next - * filter in the chain had already returned XZ_STREAM_END. - */ - if (s->temp.filtered > 0) - { - bcj_flush(s, b); - if (s->temp.filtered > 0) - return XZ_OK; - - if (s->ret == XZ_STREAM_END) - return XZ_STREAM_END; - } - - /* - * If we have more output space than what is currently pending in - * temp, copy the unfiltered data from temp to the output buffer - * and try to fill the output buffer by decoding more data from the - * next filter in the chain. Apply the BCJ filter on the new data - * in the output buffer. If everything cannot be filtered, copy it - * to temp and rewind the output buffer position accordingly. - * - * This needs to be always run when temp.size == 0 to handle a special - * case where the output buffer is full and the next filter has no - * more output coming but hasn't returned XZ_STREAM_END yet. - */ - if (s->temp.size < b->out_size - b->out_pos || s->temp.size == 0) - { - out_start = b->out_pos; - memcpy(b->out + b->out_pos, s->temp.buf, s->temp.size); - b->out_pos += s->temp.size; - - s->ret = xz_dec_lzma2_run(lzma2, b); - if (s->ret != XZ_STREAM_END && (s->ret != XZ_OK || s->single_call)) - return s->ret; - - bcj_apply(s, b->out, &out_start, b->out_pos); - - /* - * As an exception, if the next filter returned XZ_STREAM_END, - * we can do that too, since the last few bytes that remain - * unfiltered are meant to remain unfiltered. - */ - if (s->ret == XZ_STREAM_END) - return XZ_STREAM_END; - - s->temp.size = b->out_pos - out_start; - b->out_pos -= s->temp.size; - memcpy(s->temp.buf, b->out + b->out_pos, s->temp.size); - - /* - * If there wasn't enough input to the next filter to fill - * the output buffer with unfiltered data, there's no point - * to try decoding more data to temp. - */ - if (b->out_pos + s->temp.size < b->out_size) - return XZ_OK; - } - - /* - * We have unfiltered data in temp. If the output buffer isn't full - * yet, try to fill the temp buffer by decoding more data from the - * next filter. Apply the BCJ filter on temp. Then we hopefully can - * fill the actual output buffer by copying filtered data from temp. - * A mix of filtered and unfiltered data may be left in temp; it will - * be taken care on the next call to this function. - */ - if (b->out_pos < b->out_size) - { - /* Make b->out{,_pos,_size} temporarily point to s->temp. */ - s->out = b->out; - s->out_pos = b->out_pos; - s->out_size = b->out_size; - b->out = s->temp.buf; - b->out_pos = s->temp.size; - b->out_size = sizeof(s->temp.buf); - - s->ret = xz_dec_lzma2_run(lzma2, b); - - s->temp.size = b->out_pos; - b->out = s->out; - b->out_pos = s->out_pos; - b->out_size = s->out_size; - - if (s->ret != XZ_OK && s->ret != XZ_STREAM_END) - return s->ret; - - bcj_apply(s, s->temp.buf, &s->temp.filtered, s->temp.size); - - /* - * If the next filter returned XZ_STREAM_END, we mark that - * everything is filtered, since the last unfiltered bytes - * of the stream are meant to be left as is. - */ - if (s->ret == XZ_STREAM_END) - s->temp.filtered = s->temp.size; - - bcj_flush(s, b); - if (s->temp.filtered > 0) - return XZ_OK; - } - - return s->ret; -} - -XZ_EXTERN struct xz_dec_bcj *xz_dec_bcj_create(bool single_call) -{ - struct xz_dec_bcj *s = kmalloc(sizeof(*s), GFP_KERNEL); - if (s != NULL) - s->single_call = single_call; - - return s; -} - -XZ_EXTERN enum xz_ret xz_dec_bcj_reset(struct xz_dec_bcj *s, uint8_t id) -{ - switch (id) - { -#ifdef XZ_DEC_X86 - case BCJ_X86: -#endif -#ifdef XZ_DEC_POWERPC - case BCJ_POWERPC: -#endif -#ifdef XZ_DEC_IA64 - case BCJ_IA64: -#endif -#ifdef XZ_DEC_ARM - case BCJ_ARM: -#endif -#ifdef XZ_DEC_ARMTHUMB - case BCJ_ARMTHUMB: -#endif -#ifdef XZ_DEC_SPARC - case BCJ_SPARC: -#endif - break; - - default: - /* Unsupported Filter ID */ - return XZ_OPTIONS_ERROR; - } - - s->type = id; - s->ret = XZ_OK; - s->pos = 0; - s->x86_prev_mask = 0; - s->temp.filtered = 0; - s->temp.size = 0; - - return XZ_OK; -} - -#endif diff --git a/libraries/xz-embedded/src/xz_dec_lzma2.c b/libraries/xz-embedded/src/xz_dec_lzma2.c deleted file mode 100644 index 365ace2b..00000000 --- a/libraries/xz-embedded/src/xz_dec_lzma2.c +++ /dev/null @@ -1,1231 +0,0 @@ -/* - * LZMA2 decoder - * - * Authors: Lasse Collin - * Igor Pavlov - * - * This file has been put into the public domain. - * You can do whatever you want with this file. - */ - -#include "xz_private.h" -#include "xz_lzma2.h" - -/* - * Range decoder initialization eats the first five bytes of each LZMA chunk. - */ -#define RC_INIT_BYTES 5 - -/* - * Minimum number of usable input buffer to safely decode one LZMA symbol. - * The worst case is that we decode 22 bits using probabilities and 26 - * direct bits. This may decode at maximum of 20 bytes of input. However, - * lzma_main() does an extra normalization before returning, thus we - * need to put 21 here. - */ -#define LZMA_IN_REQUIRED 21 - -/* - * Dictionary (history buffer) - * - * These are always true: - * start <= pos <= full <= end - * pos <= limit <= end - * - * In multi-call mode, also these are true: - * end == size - * size <= size_max - * allocated <= size - * - * Most of these variables are size_t to support single-call mode, - * in which the dictionary variables address the actual output - * buffer directly. - */ -struct dictionary -{ - /* Beginning of the history buffer */ - uint8_t *buf; - - /* Old position in buf (before decoding more data) */ - size_t start; - - /* Position in buf */ - size_t pos; - - /* - * How full dictionary is. This is used to detect corrupt input that - * would read beyond the beginning of the uncompressed stream. - */ - size_t full; - - /* Write limit; we don't write to buf[limit] or later bytes. */ - size_t limit; - - /* - * End of the dictionary buffer. In multi-call mode, this is - * the same as the dictionary size. In single-call mode, this - * indicates the size of the output buffer. - */ - size_t end; - - /* - * Size of the dictionary as specified in Block Header. This is used - * together with "full" to detect corrupt input that would make us - * read beyond the beginning of the uncompressed stream. - */ - uint32_t size; - - /* - * Maximum allowed dictionary size in multi-call mode. - * This is ignored in single-call mode. - */ - uint32_t size_max; - - /* - * Amount of memory currently allocated for the dictionary. - * This is used only with XZ_DYNALLOC. (With XZ_PREALLOC, - * size_max is always the same as the allocated size.) - */ - uint32_t allocated; - - /* Operation mode */ - enum xz_mode mode; -}; - -/* Range decoder */ -struct rc_dec -{ - uint32_t range; - uint32_t code; - - /* - * Number of initializing bytes remaining to be read - * by rc_read_init(). - */ - uint32_t init_bytes_left; - - /* - * Buffer from which we read our input. It can be either - * temp.buf or the caller-provided input buffer. - */ - const uint8_t *in; - size_t in_pos; - size_t in_limit; -}; - -/* Probabilities for a length decoder. */ -struct lzma_len_dec -{ - /* Probability of match length being at least 10 */ - uint16_t choice; - - /* Probability of match length being at least 18 */ - uint16_t choice2; - - /* Probabilities for match lengths 2-9 */ - uint16_t low[POS_STATES_MAX][LEN_LOW_SYMBOLS]; - - /* Probabilities for match lengths 10-17 */ - uint16_t mid[POS_STATES_MAX][LEN_MID_SYMBOLS]; - - /* Probabilities for match lengths 18-273 */ - uint16_t high[LEN_HIGH_SYMBOLS]; -}; - -struct lzma_dec -{ - /* Distances of latest four matches */ - uint32_t rep0; - uint32_t rep1; - uint32_t rep2; - uint32_t rep3; - - /* Types of the most recently seen LZMA symbols */ - enum lzma_state state; - - /* - * Length of a match. This is updated so that dict_repeat can - * be called again to finish repeating the whole match. - */ - uint32_t len; - - /* - * LZMA properties or related bit masks (number of literal - * context bits, a mask dervied from the number of literal - * position bits, and a mask dervied from the number - * position bits) - */ - uint32_t lc; - uint32_t literal_pos_mask; /* (1 << lp) - 1 */ - uint32_t pos_mask; /* (1 << pb) - 1 */ - - /* If 1, it's a match. Otherwise it's a single 8-bit literal. */ - uint16_t is_match[STATES][POS_STATES_MAX]; - - /* If 1, it's a repeated match. The distance is one of rep0 .. rep3. */ - uint16_t is_rep[STATES]; - - /* - * If 0, distance of a repeated match is rep0. - * Otherwise check is_rep1. - */ - uint16_t is_rep0[STATES]; - - /* - * If 0, distance of a repeated match is rep1. - * Otherwise check is_rep2. - */ - uint16_t is_rep1[STATES]; - - /* If 0, distance of a repeated match is rep2. Otherwise it is rep3. */ - uint16_t is_rep2[STATES]; - - /* - * If 1, the repeated match has length of one byte. Otherwise - * the length is decoded from rep_len_decoder. - */ - uint16_t is_rep0_long[STATES][POS_STATES_MAX]; - - /* - * Probability tree for the highest two bits of the match - * distance. There is a separate probability tree for match - * lengths of 2 (i.e. MATCH_LEN_MIN), 3, 4, and [5, 273]. - */ - uint16_t dist_slot[DIST_STATES][DIST_SLOTS]; - - /* - * Probility trees for additional bits for match distance - * when the distance is in the range [4, 127]. - */ - uint16_t dist_special[FULL_DISTANCES - DIST_MODEL_END]; - - /* - * Probability tree for the lowest four bits of a match - * distance that is equal to or greater than 128. - */ - uint16_t dist_align[ALIGN_SIZE]; - - /* Length of a normal match */ - struct lzma_len_dec match_len_dec; - - /* Length of a repeated match */ - struct lzma_len_dec rep_len_dec; - - /* Probabilities of literals */ - uint16_t literal[LITERAL_CODERS_MAX][LITERAL_CODER_SIZE]; -}; - -struct lzma2_dec -{ - /* Position in xz_dec_lzma2_run(). */ - enum lzma2_seq - { - SEQ_CONTROL, - SEQ_UNCOMPRESSED_1, - SEQ_UNCOMPRESSED_2, - SEQ_COMPRESSED_0, - SEQ_COMPRESSED_1, - SEQ_PROPERTIES, - SEQ_LZMA_PREPARE, - SEQ_LZMA_RUN, - SEQ_COPY - } sequence; - - /* Next position after decoding the compressed size of the chunk. */ - enum lzma2_seq next_sequence; - - /* Uncompressed size of LZMA chunk (2 MiB at maximum) */ - uint32_t uncompressed; - - /* - * Compressed size of LZMA chunk or compressed/uncompressed - * size of uncompressed chunk (64 KiB at maximum) - */ - uint32_t compressed; - - /* - * True if dictionary reset is needed. This is false before - * the first chunk (LZMA or uncompressed). - */ - bool need_dict_reset; - - /* - * True if new LZMA properties are needed. This is false - * before the first LZMA chunk. - */ - bool need_props; -}; - -struct xz_dec_lzma2 -{ - /* - * The order below is important on x86 to reduce code size and - * it shouldn't hurt on other platforms. Everything up to and - * including lzma.pos_mask are in the first 128 bytes on x86-32, - * which allows using smaller instructions to access those - * variables. On x86-64, fewer variables fit into the first 128 - * bytes, but this is still the best order without sacrificing - * the readability by splitting the structures. - */ - struct rc_dec rc; - struct dictionary dict; - struct lzma2_dec lzma2; - struct lzma_dec lzma; - - /* - * Temporary buffer which holds small number of input bytes between - * decoder calls. See lzma2_lzma() for details. - */ - struct - { - uint32_t size; - uint8_t buf[3 * LZMA_IN_REQUIRED]; - } temp; -}; - -/************** - * Dictionary * - **************/ - -/* - * Reset the dictionary state. When in single-call mode, set up the beginning - * of the dictionary to point to the actual output buffer. - */ -static void dict_reset(struct dictionary *dict, struct xz_buf *b) -{ - if (DEC_IS_SINGLE(dict->mode)) - { - dict->buf = b->out + b->out_pos; - dict->end = b->out_size - b->out_pos; - } - - dict->start = 0; - dict->pos = 0; - dict->limit = 0; - dict->full = 0; -} - -/* Set dictionary write limit */ -static void dict_limit(struct dictionary *dict, size_t out_max) -{ - if (dict->end - dict->pos <= out_max) - dict->limit = dict->end; - else - dict->limit = dict->pos + out_max; -} - -/* Return true if at least one byte can be written into the dictionary. */ -static inline bool dict_has_space(const struct dictionary *dict) -{ - return dict->pos < dict->limit; -} - -/* - * Get a byte from the dictionary at the given distance. The distance is - * assumed to valid, or as a special case, zero when the dictionary is - * still empty. This special case is needed for single-call decoding to - * avoid writing a '\0' to the end of the destination buffer. - */ -static inline uint32_t dict_get(const struct dictionary *dict, uint32_t dist) -{ - size_t offset = dict->pos - dist - 1; - - if (dist >= dict->pos) - offset += dict->end; - - return dict->full > 0 ? dict->buf[offset] : 0; -} - -/* - * Put one byte into the dictionary. It is assumed that there is space for it. - */ -static inline void dict_put(struct dictionary *dict, uint8_t byte) -{ - dict->buf[dict->pos++] = byte; - - if (dict->full < dict->pos) - dict->full = dict->pos; -} - -/* - * Repeat given number of bytes from the given distance. If the distance is - * invalid, false is returned. On success, true is returned and *len is - * updated to indicate how many bytes were left to be repeated. - */ -static bool dict_repeat(struct dictionary *dict, uint32_t *len, uint32_t dist) -{ - size_t back; - uint32_t left; - - if (dist >= dict->full || dist >= dict->size) - return false; - - left = min_t(size_t, dict->limit - dict->pos, *len); - *len -= left; - - back = dict->pos - dist - 1; - if (dist >= dict->pos) - back += dict->end; - - do - { - dict->buf[dict->pos++] = dict->buf[back++]; - if (back == dict->end) - back = 0; - } while (--left > 0); - - if (dict->full < dict->pos) - dict->full = dict->pos; - - return true; -} - -/* Copy uncompressed data as is from input to dictionary and output buffers. */ -static void dict_uncompressed(struct dictionary *dict, struct xz_buf *b, uint32_t *left) -{ - size_t copy_size; - - while (*left > 0 && b->in_pos < b->in_size && b->out_pos < b->out_size) - { - copy_size = min(b->in_size - b->in_pos, b->out_size - b->out_pos); - if (copy_size > dict->end - dict->pos) - copy_size = dict->end - dict->pos; - if (copy_size > *left) - copy_size = *left; - - *left -= copy_size; - - memcpy(dict->buf + dict->pos, b->in + b->in_pos, copy_size); - dict->pos += copy_size; - - if (dict->full < dict->pos) - dict->full = dict->pos; - - if (DEC_IS_MULTI(dict->mode)) - { - if (dict->pos == dict->end) - dict->pos = 0; - - memcpy(b->out + b->out_pos, b->in + b->in_pos, copy_size); - } - - dict->start = dict->pos; - - b->out_pos += copy_size; - b->in_pos += copy_size; - } -} - -/* - * Flush pending data from dictionary to b->out. It is assumed that there is - * enough space in b->out. This is guaranteed because caller uses dict_limit() - * before decoding data into the dictionary. - */ -static uint32_t dict_flush(struct dictionary *dict, struct xz_buf *b) -{ - size_t copy_size = dict->pos - dict->start; - - if (DEC_IS_MULTI(dict->mode)) - { - if (dict->pos == dict->end) - dict->pos = 0; - - memcpy(b->out + b->out_pos, dict->buf + dict->start, copy_size); - } - - dict->start = dict->pos; - b->out_pos += copy_size; - return copy_size; -} - -/***************** - * Range decoder * - *****************/ - -/* Reset the range decoder. */ -static void rc_reset(struct rc_dec *rc) -{ - rc->range = (uint32_t) - 1; - rc->code = 0; - rc->init_bytes_left = RC_INIT_BYTES; -} - -/* - * Read the first five initial bytes into rc->code if they haven't been - * read already. (Yes, the first byte gets completely ignored.) - */ -static bool rc_read_init(struct rc_dec *rc, struct xz_buf *b) -{ - while (rc->init_bytes_left > 0) - { - if (b->in_pos == b->in_size) - return false; - - rc->code = (rc->code << 8) + b->in[b->in_pos++]; - --rc->init_bytes_left; - } - - return true; -} - -/* Return true if there may not be enough input for the next decoding loop. */ -static inline bool rc_limit_exceeded(const struct rc_dec *rc) -{ - return rc->in_pos > rc->in_limit; -} - -/* - * Return true if it is possible (from point of view of range decoder) that - * we have reached the end of the LZMA chunk. - */ -static inline bool rc_is_finished(const struct rc_dec *rc) -{ - return rc->code == 0; -} - -/* Read the next input byte if needed. */ -static __always_inline void rc_normalize(struct rc_dec *rc) -{ - if (rc->range < RC_TOP_VALUE) - { - rc->range <<= RC_SHIFT_BITS; - rc->code = (rc->code << RC_SHIFT_BITS) + rc->in[rc->in_pos++]; - } -} - -/* - * Decode one bit. In some versions, this function has been splitted in three - * functions so that the compiler is supposed to be able to more easily avoid - * an extra branch. In this particular version of the LZMA decoder, this - * doesn't seem to be a good idea (tested with GCC 3.3.6, 3.4.6, and 4.3.3 - * on x86). Using a non-splitted version results in nicer looking code too. - * - * NOTE: This must return an int. Do not make it return a bool or the speed - * of the code generated by GCC 3.x decreases 10-15 %. (GCC 4.3 doesn't care, - * and it generates 10-20 % faster code than GCC 3.x from this file anyway.) - */ -static __always_inline int rc_bit(struct rc_dec *rc, uint16_t *prob) -{ - uint32_t bound; - int bit; - - rc_normalize(rc); - bound = (rc->range >> RC_BIT_MODEL_TOTAL_BITS) * *prob; - if (rc->code < bound) - { - rc->range = bound; - *prob += (RC_BIT_MODEL_TOTAL - *prob) >> RC_MOVE_BITS; - bit = 0; - } - else - { - rc->range -= bound; - rc->code -= bound; - *prob -= *prob >> RC_MOVE_BITS; - bit = 1; - } - - return bit; -} - -/* Decode a bittree starting from the most significant bit. */ -static __always_inline uint32_t rc_bittree(struct rc_dec *rc, uint16_t *probs, uint32_t limit) -{ - uint32_t symbol = 1; - - do - { - if (rc_bit(rc, &probs[symbol])) - symbol = (symbol << 1) + 1; - else - symbol <<= 1; - } while (symbol < limit); - - return symbol; -} - -/* Decode a bittree starting from the least significant bit. */ -static __always_inline void rc_bittree_reverse(struct rc_dec *rc, uint16_t *probs, - uint32_t *dest, uint32_t limit) -{ - uint32_t symbol = 1; - uint32_t i = 0; - - do - { - if (rc_bit(rc, &probs[symbol])) - { - symbol = (symbol << 1) + 1; - *dest += 1 << i; - } - else - { - symbol <<= 1; - } - } while (++i < limit); -} - -/* Decode direct bits (fixed fifty-fifty probability) */ -static inline void rc_direct(struct rc_dec *rc, uint32_t *dest, uint32_t limit) -{ - uint32_t mask; - - do - { - rc_normalize(rc); - rc->range >>= 1; - rc->code -= rc->range; - mask = (uint32_t)0 - (rc->code >> 31); - rc->code += rc->range & mask; - *dest = (*dest << 1) + (mask + 1); - } while (--limit > 0); -} - -/******** - * LZMA * - ********/ - -/* Get pointer to literal coder probability array. */ -static uint16_t *lzma_literal_probs(struct xz_dec_lzma2 *s) -{ - uint32_t prev_byte = dict_get(&s->dict, 0); - uint32_t low = prev_byte >> (8 - s->lzma.lc); - uint32_t high = (s->dict.pos & s->lzma.literal_pos_mask) << s->lzma.lc; - return s->lzma.literal[low + high]; -} - -/* Decode a literal (one 8-bit byte) */ -static void lzma_literal(struct xz_dec_lzma2 *s) -{ - uint16_t *probs; - uint32_t symbol; - uint32_t match_byte; - uint32_t match_bit; - uint32_t offset; - uint32_t i; - - probs = lzma_literal_probs(s); - - if (lzma_state_is_literal(s->lzma.state)) - { - symbol = rc_bittree(&s->rc, probs, 0x100); - } - else - { - symbol = 1; - match_byte = dict_get(&s->dict, s->lzma.rep0) << 1; - offset = 0x100; - - do - { - match_bit = match_byte & offset; - match_byte <<= 1; - i = offset + match_bit + symbol; - - if (rc_bit(&s->rc, &probs[i])) - { - symbol = (symbol << 1) + 1; - offset &= match_bit; - } - else - { - symbol <<= 1; - offset &= ~match_bit; - } - } while (symbol < 0x100); - } - - dict_put(&s->dict, (uint8_t)symbol); - lzma_state_literal(&s->lzma.state); -} - -/* Decode the length of the match into s->lzma.len. */ -static void lzma_len(struct xz_dec_lzma2 *s, struct lzma_len_dec *l, uint32_t pos_state) -{ - uint16_t *probs; - uint32_t limit; - - if (!rc_bit(&s->rc, &l->choice)) - { - probs = l->low[pos_state]; - limit = LEN_LOW_SYMBOLS; - s->lzma.len = MATCH_LEN_MIN; - } - else - { - if (!rc_bit(&s->rc, &l->choice2)) - { - probs = l->mid[pos_state]; - limit = LEN_MID_SYMBOLS; - s->lzma.len = MATCH_LEN_MIN + LEN_LOW_SYMBOLS; - } - else - { - probs = l->high; - limit = LEN_HIGH_SYMBOLS; - s->lzma.len = MATCH_LEN_MIN + LEN_LOW_SYMBOLS + LEN_MID_SYMBOLS; - } - } - - s->lzma.len += rc_bittree(&s->rc, probs, limit) - limit; -} - -/* Decode a match. The distance will be stored in s->lzma.rep0. */ -static void lzma_match(struct xz_dec_lzma2 *s, uint32_t pos_state) -{ - uint16_t *probs; - uint32_t dist_slot; - uint32_t limit; - - lzma_state_match(&s->lzma.state); - - s->lzma.rep3 = s->lzma.rep2; - s->lzma.rep2 = s->lzma.rep1; - s->lzma.rep1 = s->lzma.rep0; - - lzma_len(s, &s->lzma.match_len_dec, pos_state); - - probs = s->lzma.dist_slot[lzma_get_dist_state(s->lzma.len)]; - dist_slot = rc_bittree(&s->rc, probs, DIST_SLOTS) - DIST_SLOTS; - - if (dist_slot < DIST_MODEL_START) - { - s->lzma.rep0 = dist_slot; - } - else - { - limit = (dist_slot >> 1) - 1; - s->lzma.rep0 = 2 + (dist_slot & 1); - - if (dist_slot < DIST_MODEL_END) - { - s->lzma.rep0 <<= limit; - probs = s->lzma.dist_special + s->lzma.rep0 - dist_slot - 1; - rc_bittree_reverse(&s->rc, probs, &s->lzma.rep0, limit); - } - else - { - rc_direct(&s->rc, &s->lzma.rep0, limit - ALIGN_BITS); - s->lzma.rep0 <<= ALIGN_BITS; - rc_bittree_reverse(&s->rc, s->lzma.dist_align, &s->lzma.rep0, ALIGN_BITS); - } - } -} - -/* - * Decode a repeated match. The distance is one of the four most recently - * seen matches. The distance will be stored in s->lzma.rep0. - */ -static void lzma_rep_match(struct xz_dec_lzma2 *s, uint32_t pos_state) -{ - uint32_t tmp; - - if (!rc_bit(&s->rc, &s->lzma.is_rep0[s->lzma.state])) - { - if (!rc_bit(&s->rc, &s->lzma.is_rep0_long[s->lzma.state][pos_state])) - { - lzma_state_short_rep(&s->lzma.state); - s->lzma.len = 1; - return; - } - } - else - { - if (!rc_bit(&s->rc, &s->lzma.is_rep1[s->lzma.state])) - { - tmp = s->lzma.rep1; - } - else - { - if (!rc_bit(&s->rc, &s->lzma.is_rep2[s->lzma.state])) - { - tmp = s->lzma.rep2; - } - else - { - tmp = s->lzma.rep3; - s->lzma.rep3 = s->lzma.rep2; - } - - s->lzma.rep2 = s->lzma.rep1; - } - - s->lzma.rep1 = s->lzma.rep0; - s->lzma.rep0 = tmp; - } - - lzma_state_long_rep(&s->lzma.state); - lzma_len(s, &s->lzma.rep_len_dec, pos_state); -} - -/* LZMA decoder core */ -static bool lzma_main(struct xz_dec_lzma2 *s) -{ - uint32_t pos_state; - - /* - * If the dictionary was reached during the previous call, try to - * finish the possibly pending repeat in the dictionary. - */ - if (dict_has_space(&s->dict) && s->lzma.len > 0) - dict_repeat(&s->dict, &s->lzma.len, s->lzma.rep0); - - /* - * Decode more LZMA symbols. One iteration may consume up to - * LZMA_IN_REQUIRED - 1 bytes. - */ - while (dict_has_space(&s->dict) && !rc_limit_exceeded(&s->rc)) - { - pos_state = s->dict.pos & s->lzma.pos_mask; - - if (!rc_bit(&s->rc, &s->lzma.is_match[s->lzma.state][pos_state])) - { - lzma_literal(s); - } - else - { - if (rc_bit(&s->rc, &s->lzma.is_rep[s->lzma.state])) - lzma_rep_match(s, pos_state); - else - lzma_match(s, pos_state); - - if (!dict_repeat(&s->dict, &s->lzma.len, s->lzma.rep0)) - return false; - } - } - - /* - * Having the range decoder always normalized when we are outside - * this function makes it easier to correctly handle end of the chunk. - */ - rc_normalize(&s->rc); - - return true; -} - -/* - * Reset the LZMA decoder and range decoder state. Dictionary is nore reset - * here, because LZMA state may be reset without resetting the dictionary. - */ -static void lzma_reset(struct xz_dec_lzma2 *s) -{ - uint16_t *probs; - size_t i; - - s->lzma.state = STATE_LIT_LIT; - s->lzma.rep0 = 0; - s->lzma.rep1 = 0; - s->lzma.rep2 = 0; - s->lzma.rep3 = 0; - - /* - * All probabilities are initialized to the same value. This hack - * makes the code smaller by avoiding a separate loop for each - * probability array. - * - * This could be optimized so that only that part of literal - * probabilities that are actually required. In the common case - * we would write 12 KiB less. - */ - probs = s->lzma.is_match[0]; - for (i = 0; i < PROBS_TOTAL; ++i) - probs[i] = RC_BIT_MODEL_TOTAL / 2; - - rc_reset(&s->rc); -} - -/* - * Decode and validate LZMA properties (lc/lp/pb) and calculate the bit masks - * from the decoded lp and pb values. On success, the LZMA decoder state is - * reset and true is returned. - */ -static bool lzma_props(struct xz_dec_lzma2 *s, uint8_t props) -{ - if (props > (4 * 5 + 4) * 9 + 8) - return false; - - s->lzma.pos_mask = 0; - while (props >= 9 * 5) - { - props -= 9 * 5; - ++s->lzma.pos_mask; - } - - s->lzma.pos_mask = (1 << s->lzma.pos_mask) - 1; - - s->lzma.literal_pos_mask = 0; - while (props >= 9) - { - props -= 9; - ++s->lzma.literal_pos_mask; - } - - s->lzma.lc = props; - - if (s->lzma.lc + s->lzma.literal_pos_mask > 4) - return false; - - s->lzma.literal_pos_mask = (1 << s->lzma.literal_pos_mask) - 1; - - lzma_reset(s); - - return true; -} - -/********* - * LZMA2 * - *********/ - -/* - * The LZMA decoder assumes that if the input limit (s->rc.in_limit) hasn't - * been exceeded, it is safe to read up to LZMA_IN_REQUIRED bytes. This - * wrapper function takes care of making the LZMA decoder's assumption safe. - * - * As long as there is plenty of input left to be decoded in the current LZMA - * chunk, we decode directly from the caller-supplied input buffer until - * there's LZMA_IN_REQUIRED bytes left. Those remaining bytes are copied into - * s->temp.buf, which (hopefully) gets filled on the next call to this - * function. We decode a few bytes from the temporary buffer so that we can - * continue decoding from the caller-supplied input buffer again. - */ -static bool lzma2_lzma(struct xz_dec_lzma2 *s, struct xz_buf *b) -{ - size_t in_avail; - uint32_t tmp; - - in_avail = b->in_size - b->in_pos; - if (s->temp.size > 0 || s->lzma2.compressed == 0) - { - tmp = 2 * LZMA_IN_REQUIRED - s->temp.size; - if (tmp > s->lzma2.compressed - s->temp.size) - tmp = s->lzma2.compressed - s->temp.size; - if (tmp > in_avail) - tmp = in_avail; - - memcpy(s->temp.buf + s->temp.size, b->in + b->in_pos, tmp); - - if (s->temp.size + tmp == s->lzma2.compressed) - { - memzero(s->temp.buf + s->temp.size + tmp, sizeof(s->temp.buf) - s->temp.size - tmp); - s->rc.in_limit = s->temp.size + tmp; - } - else if (s->temp.size + tmp < LZMA_IN_REQUIRED) - { - s->temp.size += tmp; - b->in_pos += tmp; - return true; - } - else - { - s->rc.in_limit = s->temp.size + tmp - LZMA_IN_REQUIRED; - } - - s->rc.in = s->temp.buf; - s->rc.in_pos = 0; - - if (!lzma_main(s) || s->rc.in_pos > s->temp.size + tmp) - return false; - - s->lzma2.compressed -= s->rc.in_pos; - - if (s->rc.in_pos < s->temp.size) - { - s->temp.size -= s->rc.in_pos; - memmove(s->temp.buf, s->temp.buf + s->rc.in_pos, s->temp.size); - return true; - } - - b->in_pos += s->rc.in_pos - s->temp.size; - s->temp.size = 0; - } - - in_avail = b->in_size - b->in_pos; - if (in_avail >= LZMA_IN_REQUIRED) - { - s->rc.in = b->in; - s->rc.in_pos = b->in_pos; - - if (in_avail >= s->lzma2.compressed + LZMA_IN_REQUIRED) - s->rc.in_limit = b->in_pos + s->lzma2.compressed; - else - s->rc.in_limit = b->in_size - LZMA_IN_REQUIRED; - - if (!lzma_main(s)) - return false; - - in_avail = s->rc.in_pos - b->in_pos; - if (in_avail > s->lzma2.compressed) - return false; - - s->lzma2.compressed -= in_avail; - b->in_pos = s->rc.in_pos; - } - - in_avail = b->in_size - b->in_pos; - if (in_avail < LZMA_IN_REQUIRED) - { - if (in_avail > s->lzma2.compressed) - in_avail = s->lzma2.compressed; - - memcpy(s->temp.buf, b->in + b->in_pos, in_avail); - s->temp.size = in_avail; - b->in_pos += in_avail; - } - - return true; -} - -/* - * Take care of the LZMA2 control layer, and forward the job of actual LZMA - * decoding or copying of uncompressed chunks to other functions. - */ -XZ_EXTERN enum xz_ret xz_dec_lzma2_run(struct xz_dec_lzma2 *s, struct xz_buf *b) -{ - uint32_t tmp; - - while (b->in_pos < b->in_size || s->lzma2.sequence == SEQ_LZMA_RUN) - { - switch (s->lzma2.sequence) - { - case SEQ_CONTROL: - /* - * LZMA2 control byte - * - * Exact values: - * 0x00 End marker - * 0x01 Dictionary reset followed by - * an uncompressed chunk - * 0x02 Uncompressed chunk (no dictionary reset) - * - * Highest three bits (s->control & 0xE0): - * 0xE0 Dictionary reset, new properties and state - * reset, followed by LZMA compressed chunk - * 0xC0 New properties and state reset, followed - * by LZMA compressed chunk (no dictionary - * reset) - * 0xA0 State reset using old properties, - * followed by LZMA compressed chunk (no - * dictionary reset) - * 0x80 LZMA chunk (no dictionary or state reset) - * - * For LZMA compressed chunks, the lowest five bits - * (s->control & 1F) are the highest bits of the - * uncompressed size (bits 16-20). - * - * A new LZMA2 stream must begin with a dictionary - * reset. The first LZMA chunk must set new - * properties and reset the LZMA state. - * - * Values that don't match anything described above - * are invalid and we return XZ_DATA_ERROR. - */ - tmp = b->in[b->in_pos++]; - - if (tmp == 0x00) - return XZ_STREAM_END; - - if (tmp >= 0xE0 || tmp == 0x01) - { - s->lzma2.need_props = true; - s->lzma2.need_dict_reset = false; - dict_reset(&s->dict, b); - } - else if (s->lzma2.need_dict_reset) - { - return XZ_DATA_ERROR; - } - - if (tmp >= 0x80) - { - s->lzma2.uncompressed = (tmp & 0x1F) << 16; - s->lzma2.sequence = SEQ_UNCOMPRESSED_1; - - if (tmp >= 0xC0) - { - /* - * When there are new properties, - * state reset is done at - * SEQ_PROPERTIES. - */ - s->lzma2.need_props = false; - s->lzma2.next_sequence = SEQ_PROPERTIES; - } - else if (s->lzma2.need_props) - { - return XZ_DATA_ERROR; - } - else - { - s->lzma2.next_sequence = SEQ_LZMA_PREPARE; - if (tmp >= 0xA0) - lzma_reset(s); - } - } - else - { - if (tmp > 0x02) - return XZ_DATA_ERROR; - - s->lzma2.sequence = SEQ_COMPRESSED_0; - s->lzma2.next_sequence = SEQ_COPY; - } - - break; - - case SEQ_UNCOMPRESSED_1: - s->lzma2.uncompressed += (uint32_t)b->in[b->in_pos++] << 8; - s->lzma2.sequence = SEQ_UNCOMPRESSED_2; - break; - - case SEQ_UNCOMPRESSED_2: - s->lzma2.uncompressed += (uint32_t)b->in[b->in_pos++] + 1; - s->lzma2.sequence = SEQ_COMPRESSED_0; - break; - - case SEQ_COMPRESSED_0: - s->lzma2.compressed = (uint32_t)b->in[b->in_pos++] << 8; - s->lzma2.sequence = SEQ_COMPRESSED_1; - break; - - case SEQ_COMPRESSED_1: - s->lzma2.compressed += (uint32_t)b->in[b->in_pos++] + 1; - s->lzma2.sequence = s->lzma2.next_sequence; - break; - - case SEQ_PROPERTIES: - if (!lzma_props(s, b->in[b->in_pos++])) - return XZ_DATA_ERROR; - - s->lzma2.sequence = SEQ_LZMA_PREPARE; - - case SEQ_LZMA_PREPARE: - if (s->lzma2.compressed < RC_INIT_BYTES) - return XZ_DATA_ERROR; - - if (!rc_read_init(&s->rc, b)) - return XZ_OK; - - s->lzma2.compressed -= RC_INIT_BYTES; - s->lzma2.sequence = SEQ_LZMA_RUN; - - case SEQ_LZMA_RUN: - /* - * Set dictionary limit to indicate how much we want - * to be encoded at maximum. Decode new data into the - * dictionary. Flush the new data from dictionary to - * b->out. Check if we finished decoding this chunk. - * In case the dictionary got full but we didn't fill - * the output buffer yet, we may run this loop - * multiple times without changing s->lzma2.sequence. - */ - dict_limit(&s->dict, - min_t(size_t, b->out_size - b->out_pos, s->lzma2.uncompressed)); - if (!lzma2_lzma(s, b)) - return XZ_DATA_ERROR; - - s->lzma2.uncompressed -= dict_flush(&s->dict, b); - - if (s->lzma2.uncompressed == 0) - { - if (s->lzma2.compressed > 0 || s->lzma.len > 0 || !rc_is_finished(&s->rc)) - return XZ_DATA_ERROR; - - rc_reset(&s->rc); - s->lzma2.sequence = SEQ_CONTROL; - } - else if (b->out_pos == b->out_size || - (b->in_pos == b->in_size && s->temp.size < s->lzma2.compressed)) - { - return XZ_OK; - } - - break; - - case SEQ_COPY: - dict_uncompressed(&s->dict, b, &s->lzma2.compressed); - if (s->lzma2.compressed > 0) - return XZ_OK; - - s->lzma2.sequence = SEQ_CONTROL; - break; - } - } - - return XZ_OK; -} - -XZ_EXTERN struct xz_dec_lzma2 *xz_dec_lzma2_create(enum xz_mode mode, uint32_t dict_max) -{ - struct xz_dec_lzma2 *s = kmalloc(sizeof(*s), GFP_KERNEL); - if (s == NULL) - return NULL; - - s->dict.mode = mode; - s->dict.size_max = dict_max; - - if (DEC_IS_PREALLOC(mode)) - { - s->dict.buf = vmalloc(dict_max); - if (s->dict.buf == NULL) - { - kfree(s); - return NULL; - } - } - else if (DEC_IS_DYNALLOC(mode)) - { - s->dict.buf = NULL; - s->dict.allocated = 0; - } - - return s; -} - -XZ_EXTERN enum xz_ret xz_dec_lzma2_reset(struct xz_dec_lzma2 *s, uint8_t props) -{ - /* This limits dictionary size to 3 GiB to keep parsing simpler. */ - if (props > 39) - return XZ_OPTIONS_ERROR; - - s->dict.size = 2 + (props & 1); - s->dict.size <<= (props >> 1) + 11; - - if (DEC_IS_MULTI(s->dict.mode)) - { - if (s->dict.size > s->dict.size_max) - return XZ_MEMLIMIT_ERROR; - - s->dict.end = s->dict.size; - - if (DEC_IS_DYNALLOC(s->dict.mode)) - { - if (s->dict.allocated < s->dict.size) - { - vfree(s->dict.buf); - s->dict.buf = vmalloc(s->dict.size); - if (s->dict.buf == NULL) - { - s->dict.allocated = 0; - return XZ_MEM_ERROR; - } - } - } - } - - s->lzma.len = 0; - - s->lzma2.sequence = SEQ_CONTROL; - s->lzma2.need_dict_reset = true; - - s->temp.size = 0; - - return XZ_OK; -} - -XZ_EXTERN void xz_dec_lzma2_end(struct xz_dec_lzma2 *s) -{ - if (DEC_IS_MULTI(s->dict.mode)) - vfree(s->dict.buf); - - kfree(s); -} diff --git a/libraries/xz-embedded/src/xz_dec_stream.c b/libraries/xz-embedded/src/xz_dec_stream.c deleted file mode 100644 index f8d7be05..00000000 --- a/libraries/xz-embedded/src/xz_dec_stream.c +++ /dev/null @@ -1,860 +0,0 @@ -/* - * .xz Stream decoder - * - * Author: Lasse Collin - * - * This file has been put into the public domain. - * You can do whatever you want with this file. - */ - -#include "xz_private.h" -#include "xz_stream.h" - -#ifdef XZ_USE_CRC64 -#define IS_CRC64(check_type) ((check_type) == XZ_CHECK_CRC64) -#else -#define IS_CRC64(check_type) false -#endif - -/* Hash used to validate the Index field */ -struct xz_dec_hash -{ - vli_type unpadded; - vli_type uncompressed; - uint32_t crc32; -}; - -struct xz_dec -{ - /* Position in dec_main() */ - enum - { - SEQ_STREAM_HEADER, - SEQ_BLOCK_START, - SEQ_BLOCK_HEADER, - SEQ_BLOCK_UNCOMPRESS, - SEQ_BLOCK_PADDING, - SEQ_BLOCK_CHECK, - SEQ_INDEX, - SEQ_INDEX_PADDING, - SEQ_INDEX_CRC32, - SEQ_STREAM_FOOTER - } sequence; - - /* Position in variable-length integers and Check fields */ - uint32_t pos; - - /* Variable-length integer decoded by dec_vli() */ - vli_type vli; - - /* Saved in_pos and out_pos */ - size_t in_start; - size_t out_start; - -#ifdef XZ_USE_CRC64 - /* CRC32 or CRC64 value in Block or CRC32 value in Index */ - uint64_t crc; -#else - /* CRC32 value in Block or Index */ - uint32_t crc; -#endif - - /* Type of the integrity check calculated from uncompressed data */ - enum xz_check check_type; - - /* Operation mode */ - enum xz_mode mode; - - /* - * True if the next call to xz_dec_run() is allowed to return - * XZ_BUF_ERROR. - */ - bool allow_buf_error; - - /* Information stored in Block Header */ - struct - { - /* - * Value stored in the Compressed Size field, or - * VLI_UNKNOWN if Compressed Size is not present. - */ - vli_type compressed; - - /* - * Value stored in the Uncompressed Size field, or - * VLI_UNKNOWN if Uncompressed Size is not present. - */ - vli_type uncompressed; - - /* Size of the Block Header field */ - uint32_t size; - } block_header; - - /* Information collected when decoding Blocks */ - struct - { - /* Observed compressed size of the current Block */ - vli_type compressed; - - /* Observed uncompressed size of the current Block */ - vli_type uncompressed; - - /* Number of Blocks decoded so far */ - vli_type count; - - /* - * Hash calculated from the Block sizes. This is used to - * validate the Index field. - */ - struct xz_dec_hash hash; - } block; - - /* Variables needed when verifying the Index field */ - struct - { - /* Position in dec_index() */ - enum - { - SEQ_INDEX_COUNT, - SEQ_INDEX_UNPADDED, - SEQ_INDEX_UNCOMPRESSED - } sequence; - - /* Size of the Index in bytes */ - vli_type size; - - /* Number of Records (matches block.count in valid files) */ - vli_type count; - - /* - * Hash calculated from the Records (matches block.hash in - * valid files). - */ - struct xz_dec_hash hash; - } index; - - /* - * Temporary buffer needed to hold Stream Header, Block Header, - * and Stream Footer. The Block Header is the biggest (1 KiB) - * so we reserve space according to that. buf[] has to be aligned - * to a multiple of four bytes; the size_t variables before it - * should guarantee this. - */ - struct - { - size_t pos; - size_t size; - uint8_t buf[1024]; - } temp; - - struct xz_dec_lzma2 *lzma2; - -#ifdef XZ_DEC_BCJ - struct xz_dec_bcj *bcj; - bool bcj_active; -#endif -}; - -#ifdef XZ_DEC_ANY_CHECK -/* Sizes of the Check field with different Check IDs */ -static const uint8_t check_sizes[16] = {0, 4, 4, 4, 8, 8, 8, 16, - 16, 16, 32, 32, 32, 64, 64, 64}; -#endif - -/* - * Fill s->temp by copying data starting from b->in[b->in_pos]. Caller - * must have set s->temp.pos to indicate how much data we are supposed - * to copy into s->temp.buf. Return true once s->temp.pos has reached - * s->temp.size. - */ -static bool fill_temp(struct xz_dec *s, struct xz_buf *b) -{ - size_t copy_size = min_t(size_t, b->in_size - b->in_pos, s->temp.size - s->temp.pos); - - memcpy(s->temp.buf + s->temp.pos, b->in + b->in_pos, copy_size); - b->in_pos += copy_size; - s->temp.pos += copy_size; - - if (s->temp.pos == s->temp.size) - { - s->temp.pos = 0; - return true; - } - - return false; -} - -/* Decode a variable-length integer (little-endian base-128 encoding) */ -static enum xz_ret dec_vli(struct xz_dec *s, const uint8_t *in, size_t *in_pos, size_t in_size) -{ - uint8_t byte; - - if (s->pos == 0) - s->vli = 0; - - while (*in_pos < in_size) - { - byte = in[*in_pos]; - ++*in_pos; - - s->vli |= (vli_type)(byte & 0x7F) << s->pos; - - if ((byte & 0x80) == 0) - { - /* Don't allow non-minimal encodings. */ - if (byte == 0 && s->pos != 0) - return XZ_DATA_ERROR; - - s->pos = 0; - return XZ_STREAM_END; - } - - s->pos += 7; - if (s->pos == 7 * VLI_BYTES_MAX) - return XZ_DATA_ERROR; - } - - return XZ_OK; -} - -/* - * Decode the Compressed Data field from a Block. Update and validate - * the observed compressed and uncompressed sizes of the Block so that - * they don't exceed the values possibly stored in the Block Header - * (validation assumes that no integer overflow occurs, since vli_type - * is normally uint64_t). Update the CRC32 or CRC64 value if presence of - * the CRC32 or CRC64 field was indicated in Stream Header. - * - * Once the decoding is finished, validate that the observed sizes match - * the sizes possibly stored in the Block Header. Update the hash and - * Block count, which are later used to validate the Index field. - */ -static enum xz_ret dec_block(struct xz_dec *s, struct xz_buf *b) -{ - enum xz_ret ret; - - s->in_start = b->in_pos; - s->out_start = b->out_pos; - -#ifdef XZ_DEC_BCJ - if (s->bcj_active) - ret = xz_dec_bcj_run(s->bcj, s->lzma2, b); - else -#endif - ret = xz_dec_lzma2_run(s->lzma2, b); - - s->block.compressed += b->in_pos - s->in_start; - s->block.uncompressed += b->out_pos - s->out_start; - - /* - * There is no need to separately check for VLI_UNKNOWN, since - * the observed sizes are always smaller than VLI_UNKNOWN. - */ - if (s->block.compressed > s->block_header.compressed || - s->block.uncompressed > s->block_header.uncompressed) - return XZ_DATA_ERROR; - - if (s->check_type == XZ_CHECK_CRC32) - s->crc = xz_crc32(b->out + s->out_start, b->out_pos - s->out_start, s->crc); -#ifdef XZ_USE_CRC64 - else if (s->check_type == XZ_CHECK_CRC64) - s->crc = xz_crc64(b->out + s->out_start, b->out_pos - s->out_start, s->crc); -#endif - - if (ret == XZ_STREAM_END) - { - if (s->block_header.compressed != VLI_UNKNOWN && - s->block_header.compressed != s->block.compressed) - return XZ_DATA_ERROR; - - if (s->block_header.uncompressed != VLI_UNKNOWN && - s->block_header.uncompressed != s->block.uncompressed) - return XZ_DATA_ERROR; - - s->block.hash.unpadded += s->block_header.size + s->block.compressed; - -#ifdef XZ_DEC_ANY_CHECK - s->block.hash.unpadded += check_sizes[s->check_type]; -#else - if (s->check_type == XZ_CHECK_CRC32) - s->block.hash.unpadded += 4; - else if (IS_CRC64(s->check_type)) - s->block.hash.unpadded += 8; -#endif - - s->block.hash.uncompressed += s->block.uncompressed; - s->block.hash.crc32 = xz_crc32((const uint8_t *)&s->block.hash, sizeof(s->block.hash), - s->block.hash.crc32); - - ++s->block.count; - } - - return ret; -} - -/* Update the Index size and the CRC32 value. */ -static void index_update(struct xz_dec *s, const struct xz_buf *b) -{ - size_t in_used = b->in_pos - s->in_start; - s->index.size += in_used; - s->crc = xz_crc32(b->in + s->in_start, in_used, s->crc); -} - -/* - * Decode the Number of Records, Unpadded Size, and Uncompressed Size - * fields from the Index field. That is, Index Padding and CRC32 are not - * decoded by this function. - * - * This can return XZ_OK (more input needed), XZ_STREAM_END (everything - * successfully decoded), or XZ_DATA_ERROR (input is corrupt). - */ -static enum xz_ret dec_index(struct xz_dec *s, struct xz_buf *b) -{ - enum xz_ret ret; - - do - { - ret = dec_vli(s, b->in, &b->in_pos, b->in_size); - if (ret != XZ_STREAM_END) - { - index_update(s, b); - return ret; - } - - switch (s->index.sequence) - { - case SEQ_INDEX_COUNT: - s->index.count = s->vli; - - /* - * Validate that the Number of Records field - * indicates the same number of Records as - * there were Blocks in the Stream. - */ - if (s->index.count != s->block.count) - return XZ_DATA_ERROR; - - s->index.sequence = SEQ_INDEX_UNPADDED; - break; - - case SEQ_INDEX_UNPADDED: - s->index.hash.unpadded += s->vli; - s->index.sequence = SEQ_INDEX_UNCOMPRESSED; - break; - - case SEQ_INDEX_UNCOMPRESSED: - s->index.hash.uncompressed += s->vli; - s->index.hash.crc32 = xz_crc32((const uint8_t *)&s->index.hash, - sizeof(s->index.hash), s->index.hash.crc32); - --s->index.count; - s->index.sequence = SEQ_INDEX_UNPADDED; - break; - } - } while (s->index.count > 0); - - return XZ_STREAM_END; -} - -/* - * Validate that the next four or eight input bytes match the value - * of s->crc. s->pos must be zero when starting to validate the first byte. - * The "bits" argument allows using the same code for both CRC32 and CRC64. - */ -static enum xz_ret crc_validate(struct xz_dec *s, struct xz_buf *b, uint32_t bits) -{ - do - { - if (b->in_pos == b->in_size) - return XZ_OK; - - if (((s->crc >> s->pos) & 0xFF) != b->in[b->in_pos++]) - return XZ_DATA_ERROR; - - s->pos += 8; - - } while (s->pos < bits); - - s->crc = 0; - s->pos = 0; - - return XZ_STREAM_END; -} - -#ifdef XZ_DEC_ANY_CHECK -/* - * Skip over the Check field when the Check ID is not supported. - * Returns true once the whole Check field has been skipped over. - */ -static bool check_skip(struct xz_dec *s, struct xz_buf *b) -{ - while (s->pos < check_sizes[s->check_type]) - { - if (b->in_pos == b->in_size) - return false; - - ++b->in_pos; - ++s->pos; - } - - s->pos = 0; - - return true; -} -#endif - -/* Decode the Stream Header field (the first 12 bytes of the .xz Stream). */ -static enum xz_ret dec_stream_header(struct xz_dec *s) -{ - if (!memeq(s->temp.buf, HEADER_MAGIC, HEADER_MAGIC_SIZE)) - return XZ_FORMAT_ERROR; - - if (xz_crc32(s->temp.buf + HEADER_MAGIC_SIZE, 2, 0) != - get_le32(s->temp.buf + HEADER_MAGIC_SIZE + 2)) - return XZ_DATA_ERROR; - - if (s->temp.buf[HEADER_MAGIC_SIZE] != 0) - return XZ_OPTIONS_ERROR; - - /* - * Of integrity checks, we support none (Check ID = 0), - * CRC32 (Check ID = 1), and optionally CRC64 (Check ID = 4). - * However, if XZ_DEC_ANY_CHECK is defined, we will accept other - * check types too, but then the check won't be verified and - * a warning (XZ_UNSUPPORTED_CHECK) will be given. - */ - s->check_type = s->temp.buf[HEADER_MAGIC_SIZE + 1]; - -#ifdef XZ_DEC_ANY_CHECK - if (s->check_type > XZ_CHECK_MAX) - return XZ_OPTIONS_ERROR; - - if (s->check_type > XZ_CHECK_CRC32 && !IS_CRC64(s->check_type)) - return XZ_UNSUPPORTED_CHECK; -#else - if (s->check_type > XZ_CHECK_CRC32 && !IS_CRC64(s->check_type)) - return XZ_OPTIONS_ERROR; -#endif - - return XZ_OK; -} - -/* Decode the Stream Footer field (the last 12 bytes of the .xz Stream) */ -static enum xz_ret dec_stream_footer(struct xz_dec *s) -{ - if (!memeq(s->temp.buf + 10, FOOTER_MAGIC, FOOTER_MAGIC_SIZE)) - return XZ_DATA_ERROR; - - if (xz_crc32(s->temp.buf + 4, 6, 0) != get_le32(s->temp.buf)) - return XZ_DATA_ERROR; - - /* - * Validate Backward Size. Note that we never added the size of the - * Index CRC32 field to s->index.size, thus we use s->index.size / 4 - * instead of s->index.size / 4 - 1. - */ - if ((s->index.size >> 2) != get_le32(s->temp.buf + 4)) - return XZ_DATA_ERROR; - - if (s->temp.buf[8] != 0 || s->temp.buf[9] != s->check_type) - return XZ_DATA_ERROR; - - /* - * Use XZ_STREAM_END instead of XZ_OK to be more convenient - * for the caller. - */ - return XZ_STREAM_END; -} - -/* Decode the Block Header and initialize the filter chain. */ -static enum xz_ret dec_block_header(struct xz_dec *s) -{ - enum xz_ret ret; - - /* - * Validate the CRC32. We know that the temp buffer is at least - * eight bytes so this is safe. - */ - s->temp.size -= 4; - if (xz_crc32(s->temp.buf, s->temp.size, 0) != get_le32(s->temp.buf + s->temp.size)) - return XZ_DATA_ERROR; - - s->temp.pos = 2; - -/* - * Catch unsupported Block Flags. We support only one or two filters - * in the chain, so we catch that with the same test. - */ -#ifdef XZ_DEC_BCJ - if (s->temp.buf[1] & 0x3E) -#else - if (s->temp.buf[1] & 0x3F) -#endif - return XZ_OPTIONS_ERROR; - - /* Compressed Size */ - if (s->temp.buf[1] & 0x40) - { - if (dec_vli(s, s->temp.buf, &s->temp.pos, s->temp.size) != XZ_STREAM_END) - return XZ_DATA_ERROR; - - s->block_header.compressed = s->vli; - } - else - { - s->block_header.compressed = VLI_UNKNOWN; - } - - /* Uncompressed Size */ - if (s->temp.buf[1] & 0x80) - { - if (dec_vli(s, s->temp.buf, &s->temp.pos, s->temp.size) != XZ_STREAM_END) - return XZ_DATA_ERROR; - - s->block_header.uncompressed = s->vli; - } - else - { - s->block_header.uncompressed = VLI_UNKNOWN; - } - -#ifdef XZ_DEC_BCJ - /* If there are two filters, the first one must be a BCJ filter. */ - s->bcj_active = s->temp.buf[1] & 0x01; - if (s->bcj_active) - { - if (s->temp.size - s->temp.pos < 2) - return XZ_OPTIONS_ERROR; - - ret = xz_dec_bcj_reset(s->bcj, s->temp.buf[s->temp.pos++]); - if (ret != XZ_OK) - return ret; - - /* - * We don't support custom start offset, - * so Size of Properties must be zero. - */ - if (s->temp.buf[s->temp.pos++] != 0x00) - return XZ_OPTIONS_ERROR; - } -#endif - - /* Valid Filter Flags always take at least two bytes. */ - if (s->temp.size - s->temp.pos < 2) - return XZ_DATA_ERROR; - - /* Filter ID = LZMA2 */ - if (s->temp.buf[s->temp.pos++] != 0x21) - return XZ_OPTIONS_ERROR; - - /* Size of Properties = 1-byte Filter Properties */ - if (s->temp.buf[s->temp.pos++] != 0x01) - return XZ_OPTIONS_ERROR; - - /* Filter Properties contains LZMA2 dictionary size. */ - if (s->temp.size - s->temp.pos < 1) - return XZ_DATA_ERROR; - - ret = xz_dec_lzma2_reset(s->lzma2, s->temp.buf[s->temp.pos++]); - if (ret != XZ_OK) - return ret; - - /* The rest must be Header Padding. */ - while (s->temp.pos < s->temp.size) - if (s->temp.buf[s->temp.pos++] != 0x00) - return XZ_OPTIONS_ERROR; - - s->temp.pos = 0; - s->block.compressed = 0; - s->block.uncompressed = 0; - - return XZ_OK; -} - -static enum xz_ret dec_main(struct xz_dec *s, struct xz_buf *b) -{ - enum xz_ret ret; - - /* - * Store the start position for the case when we are in the middle - * of the Index field. - */ - s->in_start = b->in_pos; - - while (true) - { - switch (s->sequence) - { - case SEQ_STREAM_HEADER: - /* - * Stream Header is copied to s->temp, and then - * decoded from there. This way if the caller - * gives us only little input at a time, we can - * still keep the Stream Header decoding code - * simple. Similar approach is used in many places - * in this file. - */ - if (!fill_temp(s, b)) - return XZ_OK; - - /* - * If dec_stream_header() returns - * XZ_UNSUPPORTED_CHECK, it is still possible - * to continue decoding if working in multi-call - * mode. Thus, update s->sequence before calling - * dec_stream_header(). - */ - s->sequence = SEQ_BLOCK_START; - - ret = dec_stream_header(s); - if (ret != XZ_OK) - return ret; - - case SEQ_BLOCK_START: - /* We need one byte of input to continue. */ - if (b->in_pos == b->in_size) - return XZ_OK; - - /* See if this is the beginning of the Index field. */ - if (b->in[b->in_pos] == 0) - { - s->in_start = b->in_pos++; - s->sequence = SEQ_INDEX; - break; - } - - /* - * Calculate the size of the Block Header and - * prepare to decode it. - */ - s->block_header.size = ((uint32_t)b->in[b->in_pos] + 1) * 4; - - s->temp.size = s->block_header.size; - s->temp.pos = 0; - s->sequence = SEQ_BLOCK_HEADER; - - case SEQ_BLOCK_HEADER: - if (!fill_temp(s, b)) - return XZ_OK; - - ret = dec_block_header(s); - if (ret != XZ_OK) - return ret; - - s->sequence = SEQ_BLOCK_UNCOMPRESS; - - case SEQ_BLOCK_UNCOMPRESS: - ret = dec_block(s, b); - if (ret != XZ_STREAM_END) - return ret; - - s->sequence = SEQ_BLOCK_PADDING; - - case SEQ_BLOCK_PADDING: - /* - * Size of Compressed Data + Block Padding - * must be a multiple of four. We don't need - * s->block.compressed for anything else - * anymore, so we use it here to test the size - * of the Block Padding field. - */ - while (s->block.compressed & 3) - { - if (b->in_pos == b->in_size) - return XZ_OK; - - if (b->in[b->in_pos++] != 0) - return XZ_DATA_ERROR; - - ++s->block.compressed; - } - - s->sequence = SEQ_BLOCK_CHECK; - - case SEQ_BLOCK_CHECK: - if (s->check_type == XZ_CHECK_CRC32) - { - ret = crc_validate(s, b, 32); - if (ret != XZ_STREAM_END) - return ret; - } - else if (IS_CRC64(s->check_type)) - { - ret = crc_validate(s, b, 64); - if (ret != XZ_STREAM_END) - return ret; - } -#ifdef XZ_DEC_ANY_CHECK - else if (!check_skip(s, b)) - { - return XZ_OK; - } -#endif - - s->sequence = SEQ_BLOCK_START; - break; - - case SEQ_INDEX: - ret = dec_index(s, b); - if (ret != XZ_STREAM_END) - return ret; - - s->sequence = SEQ_INDEX_PADDING; - - case SEQ_INDEX_PADDING: - while ((s->index.size + (b->in_pos - s->in_start)) & 3) - { - if (b->in_pos == b->in_size) - { - index_update(s, b); - return XZ_OK; - } - - if (b->in[b->in_pos++] != 0) - return XZ_DATA_ERROR; - } - - /* Finish the CRC32 value and Index size. */ - index_update(s, b); - - /* Compare the hashes to validate the Index field. */ - if (!memeq(&s->block.hash, &s->index.hash, sizeof(s->block.hash))) - return XZ_DATA_ERROR; - - s->sequence = SEQ_INDEX_CRC32; - - case SEQ_INDEX_CRC32: - ret = crc_validate(s, b, 32); - if (ret != XZ_STREAM_END) - return ret; - - s->temp.size = STREAM_HEADER_SIZE; - s->sequence = SEQ_STREAM_FOOTER; - - case SEQ_STREAM_FOOTER: - if (!fill_temp(s, b)) - return XZ_OK; - - return dec_stream_footer(s); - } - } - - /* Never reached */ -} - -/* - * xz_dec_run() is a wrapper for dec_main() to handle some special cases in - * multi-call and single-call decoding. - * - * In multi-call mode, we must return XZ_BUF_ERROR when it seems clear that we - * are not going to make any progress anymore. This is to prevent the caller - * from calling us infinitely when the input file is truncated or otherwise - * corrupt. Since zlib-style API allows that the caller fills the input buffer - * only when the decoder doesn't produce any new output, we have to be careful - * to avoid returning XZ_BUF_ERROR too easily: XZ_BUF_ERROR is returned only - * after the second consecutive call to xz_dec_run() that makes no progress. - * - * In single-call mode, if we couldn't decode everything and no error - * occurred, either the input is truncated or the output buffer is too small. - * Since we know that the last input byte never produces any output, we know - * that if all the input was consumed and decoding wasn't finished, the file - * must be corrupt. Otherwise the output buffer has to be too small or the - * file is corrupt in a way that decoding it produces too big output. - * - * If single-call decoding fails, we reset b->in_pos and b->out_pos back to - * their original values. This is because with some filter chains there won't - * be any valid uncompressed data in the output buffer unless the decoding - * actually succeeds (that's the price to pay of using the output buffer as - * the workspace). - */ -XZ_EXTERN enum xz_ret xz_dec_run(struct xz_dec *s, struct xz_buf *b) -{ - size_t in_start; - size_t out_start; - enum xz_ret ret; - - if (DEC_IS_SINGLE(s->mode)) - xz_dec_reset(s); - - in_start = b->in_pos; - out_start = b->out_pos; - ret = dec_main(s, b); - - if (DEC_IS_SINGLE(s->mode)) - { - if (ret == XZ_OK) - ret = b->in_pos == b->in_size ? XZ_DATA_ERROR : XZ_BUF_ERROR; - - if (ret != XZ_STREAM_END) - { - b->in_pos = in_start; - b->out_pos = out_start; - } - } - else if (ret == XZ_OK && in_start == b->in_pos && out_start == b->out_pos) - { - if (s->allow_buf_error) - ret = XZ_BUF_ERROR; - - s->allow_buf_error = true; - } - else - { - s->allow_buf_error = false; - } - - return ret; -} - -XZ_EXTERN struct xz_dec *xz_dec_init(enum xz_mode mode, uint32_t dict_max) -{ - struct xz_dec *s = kmalloc(sizeof(*s), GFP_KERNEL); - if (s == NULL) - return NULL; - - s->mode = mode; - -#ifdef XZ_DEC_BCJ - s->bcj = xz_dec_bcj_create(DEC_IS_SINGLE(mode)); - if (s->bcj == NULL) - goto error_bcj; -#endif - - s->lzma2 = xz_dec_lzma2_create(mode, dict_max); - if (s->lzma2 == NULL) - goto error_lzma2; - - xz_dec_reset(s); - return s; - -error_lzma2: -#ifdef XZ_DEC_BCJ - xz_dec_bcj_end(s->bcj); -error_bcj: -#endif - kfree(s); - return NULL; -} - -XZ_EXTERN void xz_dec_reset(struct xz_dec *s) -{ - s->sequence = SEQ_STREAM_HEADER; - s->allow_buf_error = false; - s->pos = 0; - s->crc = 0; - memzero(&s->block, sizeof(s->block)); - memzero(&s->index, sizeof(s->index)); - s->temp.pos = 0; - s->temp.size = STREAM_HEADER_SIZE; -} - -XZ_EXTERN void xz_dec_end(struct xz_dec *s) -{ - if (s != NULL) - { - xz_dec_lzma2_end(s->lzma2); -#ifdef XZ_DEC_BCJ - xz_dec_bcj_end(s->bcj); -#endif - kfree(s); - } -} diff --git a/libraries/xz-embedded/src/xz_lzma2.h b/libraries/xz-embedded/src/xz_lzma2.h deleted file mode 100644 index 82a425f2..00000000 --- a/libraries/xz-embedded/src/xz_lzma2.h +++ /dev/null @@ -1,204 +0,0 @@ -/* - * LZMA2 definitions - * - * Authors: Lasse Collin - * Igor Pavlov - * - * This file has been put into the public domain. - * You can do whatever you want with this file. - */ - -#ifndef XZ_LZMA2_H -#define XZ_LZMA2_H - -/* Range coder constants */ -#define RC_SHIFT_BITS 8 -#define RC_TOP_BITS 24 -#define RC_TOP_VALUE (1 << RC_TOP_BITS) -#define RC_BIT_MODEL_TOTAL_BITS 11 -#define RC_BIT_MODEL_TOTAL (1 << RC_BIT_MODEL_TOTAL_BITS) -#define RC_MOVE_BITS 5 - -/* - * Maximum number of position states. A position state is the lowest pb - * number of bits of the current uncompressed offset. In some places there - * are different sets of probabilities for different position states. - */ -#define POS_STATES_MAX (1 << 4) - -/* - * This enum is used to track which LZMA symbols have occurred most recently - * and in which order. This information is used to predict the next symbol. - * - * Symbols: - * - Literal: One 8-bit byte - * - Match: Repeat a chunk of data at some distance - * - Long repeat: Multi-byte match at a recently seen distance - * - Short repeat: One-byte repeat at a recently seen distance - * - * The symbol names are in from STATE_oldest_older_previous. REP means - * either short or long repeated match, and NONLIT means any non-literal. - */ -enum lzma_state -{ - STATE_LIT_LIT, - STATE_MATCH_LIT_LIT, - STATE_REP_LIT_LIT, - STATE_SHORTREP_LIT_LIT, - STATE_MATCH_LIT, - STATE_REP_LIT, - STATE_SHORTREP_LIT, - STATE_LIT_MATCH, - STATE_LIT_LONGREP, - STATE_LIT_SHORTREP, - STATE_NONLIT_MATCH, - STATE_NONLIT_REP -}; - -/* Total number of states */ -#define STATES 12 - -/* The lowest 7 states indicate that the previous state was a literal. */ -#define LIT_STATES 7 - -/* Indicate that the latest symbol was a literal. */ -static inline void lzma_state_literal(enum lzma_state *state) -{ - if (*state <= STATE_SHORTREP_LIT_LIT) - *state = STATE_LIT_LIT; - else if (*state <= STATE_LIT_SHORTREP) - *state -= 3; - else - *state -= 6; -} - -/* Indicate that the latest symbol was a match. */ -static inline void lzma_state_match(enum lzma_state *state) -{ - *state = *state < LIT_STATES ? STATE_LIT_MATCH : STATE_NONLIT_MATCH; -} - -/* Indicate that the latest state was a long repeated match. */ -static inline void lzma_state_long_rep(enum lzma_state *state) -{ - *state = *state < LIT_STATES ? STATE_LIT_LONGREP : STATE_NONLIT_REP; -} - -/* Indicate that the latest symbol was a short match. */ -static inline void lzma_state_short_rep(enum lzma_state *state) -{ - *state = *state < LIT_STATES ? STATE_LIT_SHORTREP : STATE_NONLIT_REP; -} - -/* Test if the previous symbol was a literal. */ -static inline bool lzma_state_is_literal(enum lzma_state state) -{ - return state < LIT_STATES; -} - -/* Each literal coder is divided in three sections: - * - 0x001-0x0FF: Without match byte - * - 0x101-0x1FF: With match byte; match bit is 0 - * - 0x201-0x2FF: With match byte; match bit is 1 - * - * Match byte is used when the previous LZMA symbol was something else than - * a literal (that is, it was some kind of match). - */ -#define LITERAL_CODER_SIZE 0x300 - -/* Maximum number of literal coders */ -#define LITERAL_CODERS_MAX (1 << 4) - -/* Minimum length of a match is two bytes. */ -#define MATCH_LEN_MIN 2 - -/* Match length is encoded with 4, 5, or 10 bits. - * - * Length Bits - * 2-9 4 = Choice=0 + 3 bits - * 10-17 5 = Choice=1 + Choice2=0 + 3 bits - * 18-273 10 = Choice=1 + Choice2=1 + 8 bits - */ -#define LEN_LOW_BITS 3 -#define LEN_LOW_SYMBOLS (1 << LEN_LOW_BITS) -#define LEN_MID_BITS 3 -#define LEN_MID_SYMBOLS (1 << LEN_MID_BITS) -#define LEN_HIGH_BITS 8 -#define LEN_HIGH_SYMBOLS (1 << LEN_HIGH_BITS) -#define LEN_SYMBOLS (LEN_LOW_SYMBOLS + LEN_MID_SYMBOLS + LEN_HIGH_SYMBOLS) - -/* - * Maximum length of a match is 273 which is a result of the encoding - * described above. - */ -#define MATCH_LEN_MAX (MATCH_LEN_MIN + LEN_SYMBOLS - 1) - -/* - * Different sets of probabilities are used for match distances that have - * very short match length: Lengths of 2, 3, and 4 bytes have a separate - * set of probabilities for each length. The matches with longer length - * use a shared set of probabilities. - */ -#define DIST_STATES 4 - -/* - * Get the index of the appropriate probability array for decoding - * the distance slot. - */ -static inline uint32_t lzma_get_dist_state(uint32_t len) -{ - return len < DIST_STATES + MATCH_LEN_MIN ? len - MATCH_LEN_MIN : DIST_STATES - 1; -} - -/* - * The highest two bits of a 32-bit match distance are encoded using six bits. - * This six-bit value is called a distance slot. This way encoding a 32-bit - * value takes 6-36 bits, larger values taking more bits. - */ -#define DIST_SLOT_BITS 6 -#define DIST_SLOTS (1 << DIST_SLOT_BITS) - -/* Match distances up to 127 are fully encoded using probabilities. Since - * the highest two bits (distance slot) are always encoded using six bits, - * the distances 0-3 don't need any additional bits to encode, since the - * distance slot itself is the same as the actual distance. DIST_MODEL_START - * indicates the first distance slot where at least one additional bit is - * needed. - */ -#define DIST_MODEL_START 4 - -/* - * Match distances greater than 127 are encoded in three pieces: - * - distance slot: the highest two bits - * - direct bits: 2-26 bits below the highest two bits - * - alignment bits: four lowest bits - * - * Direct bits don't use any probabilities. - * - * The distance slot value of 14 is for distances 128-191. - */ -#define DIST_MODEL_END 14 - -/* Distance slots that indicate a distance <= 127. */ -#define FULL_DISTANCES_BITS (DIST_MODEL_END / 2) -#define FULL_DISTANCES (1 << FULL_DISTANCES_BITS) - -/* - * For match distances greater than 127, only the highest two bits and the - * lowest four bits (alignment) is encoded using probabilities. - */ -#define ALIGN_BITS 4 -#define ALIGN_SIZE (1 << ALIGN_BITS) -#define ALIGN_MASK (ALIGN_SIZE - 1) - -/* Total number of all probability variables */ -#define PROBS_TOTAL (1846 + LITERAL_CODERS_MAX *LITERAL_CODER_SIZE) - -/* - * LZMA remembers the four most recent match distances. Reusing these - * distances tends to take less space than re-encoding the actual - * distance value. - */ -#define REPS 4 - -#endif diff --git a/libraries/xz-embedded/src/xz_private.h b/libraries/xz-embedded/src/xz_private.h deleted file mode 100644 index 1b616430..00000000 --- a/libraries/xz-embedded/src/xz_private.h +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Private includes and definitions - * - * Author: Lasse Collin - * - * This file has been put into the public domain. - * You can do whatever you want with this file. - */ - -#ifndef XZ_PRIVATE_H -#define XZ_PRIVATE_H - -#ifdef __KERNEL__ -#include -#include -#include -/* XZ_PREBOOT may be defined only via decompress_unxz.c. */ -#ifndef XZ_PREBOOT -#include -#include -#include -#ifdef CONFIG_XZ_DEC_X86 -#define XZ_DEC_X86 -#endif -#ifdef CONFIG_XZ_DEC_POWERPC -#define XZ_DEC_POWERPC -#endif -#ifdef CONFIG_XZ_DEC_IA64 -#define XZ_DEC_IA64 -#endif -#ifdef CONFIG_XZ_DEC_ARM -#define XZ_DEC_ARM -#endif -#ifdef CONFIG_XZ_DEC_ARMTHUMB -#define XZ_DEC_ARMTHUMB -#endif -#ifdef CONFIG_XZ_DEC_SPARC -#define XZ_DEC_SPARC -#endif -#define memeq(a, b, size) (memcmp(a, b, size) == 0) -#define memzero(buf, size) memset(buf, 0, size) -#endif -#define get_le32(p) le32_to_cpup((const uint32_t *)(p)) -#else -/* - * For userspace builds, use a separate header to define the required - * macros and functions. This makes it easier to adapt the code into - * different environments and avoids clutter in the Linux kernel tree. - */ -#include "xz_config.h" -#endif - -/* If no specific decoding mode is requested, enable support for all modes. */ -#if !defined(XZ_DEC_SINGLE) && !defined(XZ_DEC_PREALLOC) && !defined(XZ_DEC_DYNALLOC) -#define XZ_DEC_SINGLE -#define XZ_DEC_PREALLOC -#define XZ_DEC_DYNALLOC -#endif - -/* - * The DEC_IS_foo(mode) macros are used in "if" statements. If only some - * of the supported modes are enabled, these macros will evaluate to true or - * false at compile time and thus allow the compiler to omit unneeded code. - */ -#ifdef XZ_DEC_SINGLE -#define DEC_IS_SINGLE(mode) ((mode) == XZ_SINGLE) -#else -#define DEC_IS_SINGLE(mode) (false) -#endif - -#ifdef XZ_DEC_PREALLOC -#define DEC_IS_PREALLOC(mode) ((mode) == XZ_PREALLOC) -#else -#define DEC_IS_PREALLOC(mode) (false) -#endif - -#ifdef XZ_DEC_DYNALLOC -#define DEC_IS_DYNALLOC(mode) ((mode) == XZ_DYNALLOC) -#else -#define DEC_IS_DYNALLOC(mode) (false) -#endif - -#if !defined(XZ_DEC_SINGLE) -#define DEC_IS_MULTI(mode) (true) -#elif defined(XZ_DEC_PREALLOC) || defined(XZ_DEC_DYNALLOC) -#define DEC_IS_MULTI(mode) ((mode) != XZ_SINGLE) -#else -#define DEC_IS_MULTI(mode) (false) -#endif - -/* - * If any of the BCJ filter decoders are wanted, define XZ_DEC_BCJ. - * XZ_DEC_BCJ is used to enable generic support for BCJ decoders. - */ -#ifndef XZ_DEC_BCJ -#if defined(XZ_DEC_X86) || defined(XZ_DEC_POWERPC) || defined(XZ_DEC_IA64) || \ - defined(XZ_DEC_ARM) || defined(XZ_DEC_ARM) || defined(XZ_DEC_ARMTHUMB) || \ - defined(XZ_DEC_SPARC) -#define XZ_DEC_BCJ -#endif -#endif - -/* - * Allocate memory for LZMA2 decoder. xz_dec_lzma2_reset() must be used - * before calling xz_dec_lzma2_run(). - */ -XZ_EXTERN struct xz_dec_lzma2 *xz_dec_lzma2_create(enum xz_mode mode, uint32_t dict_max); - -/* - * Decode the LZMA2 properties (one byte) and reset the decoder. Return - * XZ_OK on success, XZ_MEMLIMIT_ERROR if the preallocated dictionary is not - * big enough, and XZ_OPTIONS_ERROR if props indicates something that this - * decoder doesn't support. - */ -XZ_EXTERN enum xz_ret xz_dec_lzma2_reset(struct xz_dec_lzma2 *s, uint8_t props); - -/* Decode raw LZMA2 stream from b->in to b->out. */ -XZ_EXTERN enum xz_ret xz_dec_lzma2_run(struct xz_dec_lzma2 *s, struct xz_buf *b); - -/* Free the memory allocated for the LZMA2 decoder. */ -XZ_EXTERN void xz_dec_lzma2_end(struct xz_dec_lzma2 *s); - -#ifdef XZ_DEC_BCJ -/* - * Allocate memory for BCJ decoders. xz_dec_bcj_reset() must be used before - * calling xz_dec_bcj_run(). - */ -XZ_EXTERN struct xz_dec_bcj *xz_dec_bcj_create(bool single_call); - -/* - * Decode the Filter ID of a BCJ filter. This implementation doesn't - * support custom start offsets, so no decoding of Filter Properties - * is needed. Returns XZ_OK if the given Filter ID is supported. - * Otherwise XZ_OPTIONS_ERROR is returned. - */ -XZ_EXTERN enum xz_ret xz_dec_bcj_reset(struct xz_dec_bcj *s, uint8_t id); - -/* - * Decode raw BCJ + LZMA2 stream. This must be used only if there actually is - * a BCJ filter in the chain. If the chain has only LZMA2, xz_dec_lzma2_run() - * must be called directly. - */ -XZ_EXTERN enum xz_ret xz_dec_bcj_run(struct xz_dec_bcj *s, struct xz_dec_lzma2 *lzma2, - struct xz_buf *b); - -/* Free the memory allocated for the BCJ filters. */ -#define xz_dec_bcj_end(s) kfree(s) -#endif - -#endif diff --git a/libraries/xz-embedded/src/xz_stream.h b/libraries/xz-embedded/src/xz_stream.h deleted file mode 100644 index b3d2c9fd..00000000 --- a/libraries/xz-embedded/src/xz_stream.h +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Definitions for handling the .xz file format - * - * Author: Lasse Collin - * - * This file has been put into the public domain. - * You can do whatever you want with this file. - */ - -#ifndef XZ_STREAM_H -#define XZ_STREAM_H - -#if defined(__KERNEL__) && !XZ_INTERNAL_CRC32 -#include -#undef crc32 -#define xz_crc32(buf, size, crc) (~crc32_le(~(uint32_t)(crc), buf, size)) -#endif - -/* - * See the .xz file format specification at - * http://tukaani.org/xz/xz-file-format.txt - * to understand the container format. - */ - -#define STREAM_HEADER_SIZE 12 - -#define HEADER_MAGIC "\3757zXZ" -#define HEADER_MAGIC_SIZE 6 - -#define FOOTER_MAGIC "YZ" -#define FOOTER_MAGIC_SIZE 2 - -/* - * Variable-length integer can hold a 63-bit unsigned integer or a special - * value indicating that the value is unknown. - * - * Experimental: vli_type can be defined to uint32_t to save a few bytes - * in code size (no effect on speed). Doing so limits the uncompressed and - * compressed size of the file to less than 256 MiB and may also weaken - * error detection slightly. - */ -typedef uint64_t vli_type; - -#define VLI_MAX ((vli_type) - 1 / 2) -#define VLI_UNKNOWN ((vli_type) - 1) - -/* Maximum encoded size of a VLI */ -#define VLI_BYTES_MAX (sizeof(vli_type) * 8 / 7) - -/* Integrity Check types */ -enum xz_check -{ - XZ_CHECK_NONE = 0, - XZ_CHECK_CRC32 = 1, - XZ_CHECK_CRC64 = 4, - XZ_CHECK_SHA256 = 10 -}; - -/* Maximum possible Check ID */ -#define XZ_CHECK_MAX 15 - -#endif diff --git a/libraries/xz-embedded/xzminidec.c b/libraries/xz-embedded/xzminidec.c deleted file mode 100644 index 44f60602..00000000 --- a/libraries/xz-embedded/xzminidec.c +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Simple XZ decoder command line tool - * - * Author: Lasse Collin - * - * This file has been put into the public domain. - * You can do whatever you want with this file. - */ - -/* - * This is really limited: Not all filters from .xz format are supported, - * only CRC32 is supported as the integrity check, and decoding of - * concatenated .xz streams is not supported. Thus, you may want to look - * at xzdec from XZ Utils if a few KiB bigger tool is not a problem. - */ - -#include -#include -#include -#include "xz.h" - -static uint8_t in[BUFSIZ]; -static uint8_t out[BUFSIZ]; - -int main(int argc, char **argv) -{ - struct xz_buf b; - struct xz_dec *s; - enum xz_ret ret; - const char *msg; - - if (argc >= 2 && strcmp(argv[1], "--help") == 0) - { - fputs("Uncompress a .xz file from stdin to stdout.\n" - "Arguments other than `--help' are ignored.\n", - stdout); - return 0; - } - - xz_crc32_init(); -#ifdef XZ_USE_CRC64 - xz_crc64_init(); -#endif - - /* - * Support up to 64 MiB dictionary. The actually needed memory - * is allocated once the headers have been parsed. - */ - s = xz_dec_init(XZ_DYNALLOC, 1 << 26); - if (s == NULL) - { - msg = "Memory allocation failed\n"; - goto error; - } - - b.in = in; - b.in_pos = 0; - b.in_size = 0; - b.out = out; - b.out_pos = 0; - b.out_size = BUFSIZ; - - while (true) - { - if (b.in_pos == b.in_size) - { - b.in_size = fread(in, 1, sizeof(in), stdin); - b.in_pos = 0; - } - - ret = xz_dec_run(s, &b); - - if (b.out_pos == sizeof(out)) - { - if (fwrite(out, 1, b.out_pos, stdout) != b.out_pos) - { - msg = "Write error\n"; - goto error; - } - - b.out_pos = 0; - } - - if (ret == XZ_OK) - continue; - -#ifdef XZ_DEC_ANY_CHECK - if (ret == XZ_UNSUPPORTED_CHECK) - { - fputs(argv[0], stderr); - fputs(": ", stderr); - fputs("Unsupported check; not verifying " - "file integrity\n", - stderr); - continue; - } -#endif - - if (fwrite(out, 1, b.out_pos, stdout) != b.out_pos || fclose(stdout)) - { - msg = "Write error\n"; - goto error; - } - - switch (ret) - { - case XZ_STREAM_END: - xz_dec_end(s); - return 0; - - case XZ_MEM_ERROR: - msg = "Memory allocation failed\n"; - goto error; - - case XZ_MEMLIMIT_ERROR: - msg = "Memory usage limit reached\n"; - goto error; - - case XZ_FORMAT_ERROR: - msg = "Not a .xz file\n"; - goto error; - - case XZ_OPTIONS_ERROR: - msg = "Unsupported options in the .xz headers\n"; - goto error; - - case XZ_DATA_ERROR: - case XZ_BUF_ERROR: - msg = "File is corrupt\n"; - goto error; - - default: - msg = "Bug!\n"; - goto error; - } - } - -error: - xz_dec_end(s); - fputs(argv[0], stderr); - fputs(": ", stderr); - fputs(msg, stderr); - return 1; -} From ccf282593dcdbe189c99b81b8bc90cb203aed3ee Mon Sep 17 00:00:00 2001 From: LennyMcLennington Date: Mon, 17 Oct 2022 19:55:07 +0100 Subject: [PATCH 272/273] reclaim polymc from the leftoids Signed-off-by: LennyMcLennington --- CODE_OF_CONDUCT.md | 136 --------------------------------------------- 1 file changed, 136 deletions(-) delete mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md deleted file mode 100644 index 7bbd01da..00000000 --- a/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,136 +0,0 @@ -# Contributor Covenant Code of Conduct - -This is a modified version of the Contributor Covenant. -See commit history to see our changes. - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, caste, color, religion, or sexual -identity and orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the overall - community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or advances of - any kind -* Trolling (antagonistic, inflammatory, insincere behaviour), insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email address, - without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for moderation -decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail address, -posting via an official social media account, or acting as an appointed -representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement via email at -[polymc-enforcement@scrumplex.net](mailto:polymc-enforcement@scrumplex.net) (Email -address subject to change). -All complaints will be reviewed and investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series of -actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or permanent -ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited interaction -with those enforcing the Code of Conduct, is allowed during this period. -Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within the -community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], -version 2.1, available at -[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. - -Community Impact Guidelines were inspired by -[Mozilla's code of conduct enforcement ladder][Mozilla CoC]. - -For answers to common questions about this code of conduct, see the FAQ at -[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at -[https://www.contributor-covenant.org/translations][translations]. - -[homepage]: https://www.contributor-covenant.org -[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html -[Mozilla CoC]: https://github.com/mozilla/diversity -[FAQ]: https://www.contributor-covenant.org/faq -[translations]: https://www.contributor-covenant.org/translations From ebbc1f67e02e62b8024280232cfb936468afd395 Mon Sep 17 00:00:00 2001 From: LennyMcLennington Date: Tue, 18 Oct 2022 11:14:06 +0100 Subject: [PATCH 273/273] Update MSA Client ID Signed-off-by: LennyMcLennington --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index caecddbd..1b0ea849 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -123,7 +123,7 @@ set(Launcher_QT_VERSION_MAJOR "5" CACHE STRING "Major Qt version to build agains # By using this key in your builds you accept the terms of use laid down in # https://docs.microsoft.com/en-us/legal/microsoft-identity-platform/terms-of-use -set(Launcher_MSA_CLIENT_ID "549033b2-1532-4d4e-ae77-1bbaa46f9d74" CACHE STRING "Client ID you can get from Microsoft Identity Platform when you register an application") +set(Launcher_MSA_CLIENT_ID "6b329578-bfec-42a3-b503-303ab3f2ac96" CACHE STRING "Client ID you can get from Microsoft Identity Platform when you register an application") # By using this key in your builds you accept the terms and conditions laid down in # https://support.curseforge.com/en/support/solutions/articles/9000207405-curse-forge-3rd-party-api-terms-and-conditions