diff --git a/.github/workflows/update-flake.yml b/.github/workflows/update-flake.yml index e1ab2e86e..ecc38ff28 100644 --- a/.github/workflows/update-flake.yml +++ b/.github/workflows/update-flake.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@v4 - uses: cachix/install-nix-action@ba0dd844c9180cbf77aa72a116d6fbc515d0e87b # v27 - - uses: DeterminateSystems/update-flake-lock@v23 + - uses: DeterminateSystems/update-flake-lock@v24 with: commit-msg: "chore(nix): update lockfile" pr-title: "chore(nix): update lockfile" diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 9cd0445e6..35eb6e91e 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -67,8 +67,10 @@ #include "ui/pages/global/MinecraftPage.h" #include "ui/pages/global/ProxyPage.h" +#include "ui/setupwizard/AutoJavaWizardPage.h" #include "ui/setupwizard/JavaWizardPage.h" #include "ui/setupwizard/LanguageWizardPage.h" +#include "ui/setupwizard/LoginWizardPage.h" #include "ui/setupwizard/PasteWizardPage.h" #include "ui/setupwizard/SetupWizard.h" #include "ui/setupwizard/ThemeWizardPage.h" @@ -650,6 +652,7 @@ Application::Application(int& argc, char** argv) : QApplication(argc, argv) auto defaultEnableAutoJava = m_settings->get("JavaPath").toString().isEmpty(); m_settings->registerSetting("AutomaticJavaSwitch", defaultEnableAutoJava); m_settings->registerSetting("AutomaticJavaDownload", defaultEnableAutoJava); + m_settings->registerSetting("UserAskedAboutAutomaticJavaDownload", false); // Legacy settings m_settings->registerSetting("OnlineFixes", false); @@ -1077,13 +1080,15 @@ bool Application::createSetupWizard() } return false; }(); + bool askjava = BuildConfig.JAVA_DOWNLOADER_ENABLED && !javaRequired && !m_settings->get("AutomaticJavaDownload").toBool() && + !m_settings->get("AutomaticJavaSwitch").toBool() && !m_settings->get("UserAskedAboutAutomaticJavaDownload").toBool(); bool languageRequired = settings()->get("Language").toString().isEmpty(); bool pasteInterventionRequired = settings()->get("PastebinURL") != ""; bool validWidgets = m_themeManager->isValidApplicationTheme(settings()->get("ApplicationTheme").toString()); bool validIcons = m_themeManager->isValidIconTheme(settings()->get("IconTheme").toString()); + bool login = !m_accounts->anyAccountIsValid() && capabilities() & Application::SupportsMSA; bool themeInterventionRequired = !validWidgets || !validIcons; - bool wizardRequired = javaRequired || languageRequired || pasteInterventionRequired || themeInterventionRequired; - + bool wizardRequired = javaRequired || languageRequired || pasteInterventionRequired || themeInterventionRequired || askjava || login; if (wizardRequired) { // set default theme after going into theme wizard if (!validIcons) @@ -1100,6 +1105,8 @@ bool Application::createSetupWizard() if (javaRequired) { m_setupWizard->addPage(new JavaWizardPage(m_setupWizard)); + } else if (askjava) { + m_setupWizard->addPage(new AutoJavaWizardPage(m_setupWizard)); } if (pasteInterventionRequired) { @@ -1110,11 +1117,14 @@ bool Application::createSetupWizard() m_setupWizard->addPage(new ThemeWizardPage(m_setupWizard)); } + if (login) { + m_setupWizard->addPage(new LoginWizardPage(m_setupWizard)); + } connect(m_setupWizard, &QDialog::finished, this, &Application::setupWizardFinished); m_setupWizard->show(); - return true; } - return false; + + return wizardRequired || login; } bool Application::updaterEnabled() @@ -1259,16 +1269,23 @@ Application::~Application() void Application::messageReceived(const QByteArray& message) { - if (status() != Initialized) { - qDebug() << "Received message" << message << "while still initializing. It will be ignored."; - return; - } - ApplicationMessage received; received.parse(message); auto& command = received.command; + if (status() != Initialized) { + bool isLoginAtempt = false; + if (command == "import") { + QString url = received.args["url"]; + isLoginAtempt = !url.isEmpty() && normalizeImportUrl(url).scheme() == BuildConfig.LAUNCHER_APP_BINARY_NAME; + } + if (!isLoginAtempt) { + qDebug() << "Received message" << message << "while still initializing. It will be ignored."; + return; + } + } + if (command == "activate") { showMainWindow(); } else if (command == "import") { diff --git a/launcher/BaseInstance.h b/launcher/BaseInstance.h index 8c80331bc..2be28d1ec 100644 --- a/launcher/BaseInstance.h +++ b/launcher/BaseInstance.h @@ -181,7 +181,7 @@ class BaseInstance : public QObject, public std::enable_shared_from_this createUpdateTask() = 0; /// returns a valid launcher (task container) virtual shared_qobject_ptr createLaunchTask(AuthSessionPtr account, MinecraftTarget::Ptr targetToJoin) = 0; @@ -215,7 +215,7 @@ class BaseInstance : public QObject, public std::enable_shared_from_this traits() const override { return {}; }; QString instanceConfigFolder() const override { return instanceRoot(); }; shared_qobject_ptr createLaunchTask(AuthSessionPtr, MinecraftTarget::Ptr) override { return nullptr; } - shared_qobject_ptr createUpdateTask([[maybe_unused]] Net::Mode mode) override { return nullptr; } + QList createUpdateTask() override { return {}; } QProcessEnvironment createEnvironment() override { return QProcessEnvironment(); } QProcessEnvironment createLaunchEnvironment() override { return QProcessEnvironment(); } QMap getVariables() override { return QMap(); } diff --git a/launcher/RuntimeContext.h b/launcher/RuntimeContext.h index c57140d28..85304a5bc 100644 --- a/launcher/RuntimeContext.h +++ b/launcher/RuntimeContext.h @@ -20,13 +20,13 @@ #include #include +#include "SysInfo.h" #include "settings/SettingsObject.h" struct RuntimeContext { QString javaArchitecture; QString javaRealArchitecture; - QString javaPath; - QString system; + QString system = SysInfo::currentSystem(); QString mappedJavaRealArchitecture() const { @@ -45,8 +45,6 @@ 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(); } @@ -68,21 +66,4 @@ struct RuntimeContext { return x; } - - 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/icons/IconUtils.cpp b/launcher/icons/IconUtils.cpp index 6825dd6da..87e948729 100644 --- a/launcher/icons/IconUtils.cpp +++ b/launcher/icons/IconUtils.cpp @@ -39,7 +39,7 @@ #include "FileSystem.h" namespace { -static const QStringList validIconExtensions = { { "svg", "png", "ico", "gif", "jpg", "jpeg" } }; +static const QStringList validIconExtensions = { { "svg", "png", "ico", "gif", "jpg", "jpeg", "webp" } }; } namespace IconUtils { diff --git a/launcher/launch/LaunchStep.cpp b/launcher/launch/LaunchStep.cpp index ebc534617..f3e9dfce0 100644 --- a/launcher/launch/LaunchStep.cpp +++ b/launcher/launch/LaunchStep.cpp @@ -16,9 +16,8 @@ #include "LaunchStep.h" #include "LaunchTask.h" -void LaunchStep::bind(LaunchTask* parent) +LaunchStep::LaunchStep(LaunchTask* parent) : Task(parent), m_parent(parent) { - m_parent = parent; connect(this, &LaunchStep::readyForLaunch, parent, &LaunchTask::onReadyForLaunch); connect(this, &LaunchStep::logLine, parent, &LaunchTask::onLogLine); connect(this, &LaunchStep::logLines, parent, &LaunchTask::onLogLines); diff --git a/launcher/launch/LaunchStep.h b/launcher/launch/LaunchStep.h index 6a28afb1f..d49d7545b 100644 --- a/launcher/launch/LaunchStep.h +++ b/launcher/launch/LaunchStep.h @@ -24,11 +24,8 @@ class LaunchTask; class LaunchStep : public Task { Q_OBJECT public: /* methods */ - explicit LaunchStep(LaunchTask* parent) : Task(nullptr), m_parent(parent) { bind(parent); }; - virtual ~LaunchStep() {}; - - private: /* methods */ - void bind(LaunchTask* parent); + explicit LaunchStep(LaunchTask* parent); + virtual ~LaunchStep() = default; signals: void logLines(QStringList lines, MessageLevel::Enum level); diff --git a/launcher/launch/LaunchTask.cpp b/launcher/launch/LaunchTask.cpp index 06a32bd28..4e4f5ead4 100644 --- a/launcher/launch/LaunchTask.cpp +++ b/launcher/launch/LaunchTask.cpp @@ -44,7 +44,6 @@ #include #include #include "MessageLevel.h" -#include "java/JavaChecker.h" #include "tasks/Task.h" void LaunchTask::init() diff --git a/launcher/launch/LaunchTask.h b/launcher/launch/LaunchTask.h index ae24b9a26..2fd8c78c7 100644 --- a/launcher/launch/LaunchTask.h +++ b/launcher/launch/LaunchTask.h @@ -41,7 +41,6 @@ #include "BaseInstance.h" #include "LaunchStep.h" #include "LogModel.h" -#include "LoggedProcess.h" #include "MessageLevel.h" class LaunchTask : public Task { @@ -55,7 +54,7 @@ class LaunchTask : public Task { public: /* methods */ static shared_qobject_ptr create(InstancePtr inst); - virtual ~LaunchTask() {}; + virtual ~LaunchTask() = default; void appendStep(shared_qobject_ptr step); void prependStep(shared_qobject_ptr step); diff --git a/launcher/launch/TaskStepWrapper.cpp b/launcher/launch/TaskStepWrapper.cpp new file mode 100644 index 000000000..136057620 --- /dev/null +++ b/launcher/launch/TaskStepWrapper.cpp @@ -0,0 +1,65 @@ +/* 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 "TaskStepWrapper.h" +#include "tasks/Task.h" + +void TaskStepWrapper::executeTask() +{ + if (m_state == Task::State::AbortedByUser) { + emitFailed(tr("Task aborted.")); + return; + } + connect(m_task.get(), &Task::finished, this, &TaskStepWrapper::updateFinished); + connect(m_task.get(), &Task::progress, this, &TaskStepWrapper::setProgress); + connect(m_task.get(), &Task::stepProgress, this, &TaskStepWrapper::propagateStepProgress); + connect(m_task.get(), &Task::status, this, &TaskStepWrapper::setStatus); + connect(m_task.get(), &Task::details, this, &TaskStepWrapper::setDetails); + emit progressReportingRequest(); +} + +void TaskStepWrapper::proceed() +{ + m_task->start(); +} + +void TaskStepWrapper::updateFinished() +{ + if (m_task->wasSuccessful()) { + m_task.reset(); + emitSucceeded(); + } else { + QString reason = tr("Instance update failed because: %1\n\n").arg(m_task->failReason()); + m_task.reset(); + emit logLine(reason, MessageLevel::Fatal); + emitFailed(reason); + } +} + +bool TaskStepWrapper::canAbort() const +{ + if (m_task) { + return m_task->canAbort(); + } + return true; +} + +bool TaskStepWrapper::abort() +{ + if (m_task && m_task->canAbort()) { + return m_task->abort(); + } + return Task::abort(); +} diff --git a/launcher/launch/steps/Update.h b/launcher/launch/TaskStepWrapper.h similarity index 73% rename from launcher/launch/steps/Update.h rename to launcher/launch/TaskStepWrapper.h index 878a43e7e..aec1b7037 100644 --- a/launcher/launch/steps/Update.h +++ b/launcher/launch/TaskStepWrapper.h @@ -21,12 +21,11 @@ #include #include -// FIXME: stupid. should be defined by the instance type? or even completely abstracted away... -class Update : public LaunchStep { +class TaskStepWrapper : public LaunchStep { Q_OBJECT public: - explicit Update(LaunchTask* parent, Net::Mode mode) : LaunchStep(parent), m_mode(mode) {}; - virtual ~Update() {}; + explicit TaskStepWrapper(LaunchTask* parent, Task::Ptr task) : LaunchStep(parent), m_task(task) {}; + virtual ~TaskStepWrapper() = default; void executeTask() override; bool canAbort() const override; @@ -38,7 +37,5 @@ class Update : public LaunchStep { void updateFinished(); private: - Task::Ptr m_updateTask; - bool m_aborted = false; - Net::Mode m_mode = Net::Mode::Offline; + Task::Ptr m_task; }; diff --git a/launcher/launch/steps/CheckJava.cpp b/launcher/launch/steps/CheckJava.cpp index 99ff62b67..55d13b58c 100644 --- a/launcher/launch/steps/CheckJava.cpp +++ b/launcher/launch/steps/CheckJava.cpp @@ -106,6 +106,7 @@ void CheckJava::executeTask() auto vendorString = instance->settings()->get("JavaVendor").toString(); printJavaInfo(verString, archString, realArchString, vendorString); } + m_parent->instance()->updateRuntimeContext(); emitSucceeded(); } @@ -124,6 +125,7 @@ void CheckJava::checkJavaFinished(const JavaChecker::Result& 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); + m_parent->instance()->updateRuntimeContext(); emitSucceeded(); return; } @@ -135,6 +137,7 @@ void CheckJava::checkJavaFinished(const JavaChecker::Result& result) instance->settings()->set("JavaRealArchitecture", result.realPlatform); instance->settings()->set("JavaVendor", result.javaVendor); instance->settings()->set("JavaSignature", m_javaSignature); + m_parent->instance()->updateRuntimeContext(); emitSucceeded(); return; } diff --git a/launcher/launch/steps/CheckJava.h b/launcher/launch/steps/CheckJava.h index 7bb1ceb7e..1c59b0053 100644 --- a/launcher/launch/steps/CheckJava.h +++ b/launcher/launch/steps/CheckJava.h @@ -23,7 +23,7 @@ class CheckJava : public LaunchStep { Q_OBJECT public: explicit CheckJava(LaunchTask* parent) : LaunchStep(parent) {}; - virtual ~CheckJava() {}; + virtual ~CheckJava() = default; virtual void executeTask(); virtual bool canAbort() const { return false; } diff --git a/launcher/launch/steps/QuitAfterGameStop.h b/launcher/launch/steps/QuitAfterGameStop.h index d4324cce6..19ca59632 100644 --- a/launcher/launch/steps/QuitAfterGameStop.h +++ b/launcher/launch/steps/QuitAfterGameStop.h @@ -24,7 +24,7 @@ class QuitAfterGameStop : public LaunchStep { Q_OBJECT public: explicit QuitAfterGameStop(LaunchTask* parent) : LaunchStep(parent) {}; - virtual ~QuitAfterGameStop() {}; + virtual ~QuitAfterGameStop() = default; virtual void executeTask(); virtual bool canAbort() const { return false; } diff --git a/launcher/launch/steps/Update.cpp b/launcher/launch/steps/Update.cpp deleted file mode 100644 index f23c0bb4b..000000000 --- a/launcher/launch/steps/Update.cpp +++ /dev/null @@ -1,73 +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 "Update.h" -#include - -void Update::executeTask() -{ - if (m_aborted) { - emitFailed(tr("Task aborted.")); - return; - } - m_updateTask.reset(m_parent->instance()->createUpdateTask(m_mode)); - if (m_updateTask) { - connect(m_updateTask.get(), &Task::finished, this, &Update::updateFinished); - connect(m_updateTask.get(), &Task::progress, this, &Update::setProgress); - connect(m_updateTask.get(), &Task::stepProgress, this, &Update::propagateStepProgress); - connect(m_updateTask.get(), &Task::status, this, &Update::setStatus); - connect(m_updateTask.get(), &Task::details, this, &Update::setDetails); - emit progressReportingRequest(); - return; - } - emitSucceeded(); -} - -void Update::proceed() -{ - m_updateTask->start(); -} - -void Update::updateFinished() -{ - if (m_updateTask->wasSuccessful()) { - m_updateTask.reset(); - emitSucceeded(); - } else { - QString reason = tr("Instance update failed because: %1\n\n").arg(m_updateTask->failReason()); - m_updateTask.reset(); - emit logLine(reason, MessageLevel::Fatal); - emitFailed(reason); - } -} - -bool Update::canAbort() const -{ - if (m_updateTask) { - return m_updateTask->canAbort(); - } - return true; -} - -bool Update::abort() -{ - m_aborted = true; - if (m_updateTask) { - if (m_updateTask->canAbort()) { - return m_updateTask->abort(); - } - } - return true; -} diff --git a/launcher/meta/VersionList.cpp b/launcher/meta/VersionList.cpp index 8b98223d1..6856b5f8d 100644 --- a/launcher/meta/VersionList.cpp +++ b/launcher/meta/VersionList.cpp @@ -16,6 +16,7 @@ #include "VersionList.h" #include +#include #include "Application.h" #include "Index.h" @@ -99,7 +100,7 @@ QVariant VersionList::data(const QModelIndex& index, int role) const case VersionPtrRole: return QVariant::fromValue(version); case RecommendedRole: - return version->isRecommended(); + return version->isRecommended() || m_externalRecommendsVersions.contains(version->version()); case JavaMajorRole: { auto major = version->version(); if (major.startsWith("java")) { @@ -192,6 +193,16 @@ void VersionList::parse(const QJsonObject& obj) parseVersionList(obj, this); } +void VersionList::addExternalRecommends(const QStringList& recommends) +{ + m_externalRecommendsVersions.append(recommends); +} + +void VersionList::clearExternalRecommends() +{ + m_externalRecommendsVersions.clear(); +} + // FIXME: this is dumb, we have 'recommended' as part of the metadata already... static const Meta::Version::Ptr& getBetterVersion(const Meta::Version::Ptr& a, const Meta::Version::Ptr& b) { @@ -276,4 +287,35 @@ void VersionList::waitToLoad() task->start(); ev.exec(); } + +Version::Ptr VersionList::getRecommendedForParent(const QString& uid, const QString& version) +{ + auto foundExplicit = std::find_if(m_versions.begin(), m_versions.end(), [uid, version](Version::Ptr ver) -> bool { + auto& reqs = ver->requiredSet(); + auto parentReq = std::find_if(reqs.begin(), reqs.end(), [uid, version](const Require& req) -> bool { + return req.uid == uid && req.equalsVersion == version; + }); + return parentReq != reqs.end() && ver->isRecommended(); + }); + if (foundExplicit != m_versions.end()) { + return *foundExplicit; + } + return nullptr; +} + +Version::Ptr VersionList::getLatestForParent(const QString& uid, const QString& version) +{ + Version::Ptr latestCompat = nullptr; + for (auto ver : m_versions) { + auto& reqs = ver->requiredSet(); + auto parentReq = std::find_if(reqs.begin(), reqs.end(), [uid, version](const Require& req) -> bool { + return req.uid == uid && req.equalsVersion == version; + }); + if (parentReq != reqs.end()) { + latestCompat = getBetterVersion(latestCompat, ver); + } + } + return latestCompat; +} + } // namespace Meta diff --git a/launcher/meta/VersionList.h b/launcher/meta/VersionList.h index 94aaae22c..4215439db 100644 --- a/launcher/meta/VersionList.h +++ b/launcher/meta/VersionList.h @@ -43,6 +43,8 @@ class VersionList : public BaseVersionList, public BaseEntity { void sortVersions() override; BaseVersion::Ptr getRecommended() const override; + Version::Ptr getRecommendedForParent(const QString& uid, const QString& version); + Version::Ptr getLatestForParent(const QString& uid, const QString& version); QVariant data(const QModelIndex& index, int role) const override; RoleList providesRoles() const override; @@ -70,6 +72,8 @@ class VersionList : public BaseVersionList, public BaseEntity { void merge(const VersionList::Ptr& other); void mergeFromIndex(const VersionList::Ptr& other); void parse(const QJsonObject& obj) override; + void addExternalRecommends(const QStringList& recommends); + void clearExternalRecommends(); signals: void nameChanged(const QString& name); @@ -79,6 +83,7 @@ class VersionList : public BaseVersionList, public BaseEntity { private: QVector m_versions; + QStringList m_externalRecommendsVersions; QHash m_lookup; QString m_uid; QString m_name; diff --git a/launcher/minecraft/Component.cpp b/launcher/minecraft/Component.cpp index 32a1deb68..1073ef324 100644 --- a/launcher/minecraft/Component.cpp +++ b/launcher/minecraft/Component.cpp @@ -44,10 +44,19 @@ #include "OneSixVersionFormat.h" #include "VersionFile.h" #include "meta/Version.h" +#include "minecraft/Component.h" #include "minecraft/PackProfile.h" #include +const QMap Component::KNOWN_MODLOADERS = { + { "net.neoforged", { ModPlatform::NeoForge, { "net.minecraftforge", "net.fabricmc.fabric-loader", "org.quiltmc.quilt-loader" } } }, + { "net.minecraftforge", { ModPlatform::Forge, { "net.neoforged", "net.fabricmc.fabric-loader", "org.quiltmc.quilt-loader" } } }, + { "net.fabricmc.fabric-loader", { ModPlatform::Fabric, { "net.minecraftforge", "net.neoforged", "org.quiltmc.quilt-loader" } } }, + { "org.quiltmc.quilt-loader", { ModPlatform::Quilt, { "net.minecraftforge", "net.neoforged", "net.fabricmc.fabric-loader" } } }, + { "com.mumfrey.liteloader", { ModPlatform::LiteLoader, {} } } +}; + Component::Component(PackProfile* parent, const QString& uid) { assert(parent); @@ -223,6 +232,22 @@ bool Component::isVersionChangeable() return false; } +bool Component::isKnownModloader() +{ + auto iter = KNOWN_MODLOADERS.find(m_uid); + return iter != KNOWN_MODLOADERS.cend(); +} + +QStringList Component::knownConflictingComponents() +{ + auto iter = KNOWN_MODLOADERS.find(m_uid); + if (iter != KNOWN_MODLOADERS.cend()) { + return (*iter).knownConflictingComponents; + } else { + return {}; + } +} + void Component::setImportant(bool state) { if (m_important != state) { @@ -235,7 +260,8 @@ ProblemSeverity Component::getProblemSeverity() const { auto file = getVersionFile(); if (file) { - return file->getProblemSeverity(); + auto severity = file->getProblemSeverity(); + return m_componentProblemSeverity > severity ? m_componentProblemSeverity : severity; } return ProblemSeverity::Error; } @@ -244,11 +270,31 @@ const QList Component::getProblems() const { auto file = getVersionFile(); if (file) { - return file->getProblems(); + auto problems = file->getProblems(); + problems.append(m_componentProblems); + return problems; } return { { ProblemSeverity::Error, QObject::tr("Patch is not loaded yet.") } }; } +void Component::addComponentProblem(ProblemSeverity severity, const QString& description) +{ + if (severity > m_componentProblemSeverity) { + m_componentProblemSeverity = severity; + } + m_componentProblems.append({ severity, description }); + + emit dataChanged(); +} + +void Component::resetComponentProblems() +{ + m_componentProblems.clear(); + m_componentProblemSeverity = ProblemSeverity::None; + + emit dataChanged(); +} + void Component::setVersion(const QString& version) { if (version == m_version) { @@ -402,3 +448,36 @@ void Component::updateCachedData() emit dataChanged(); } } + +void Component::waitLoadMeta() +{ + if (!m_loaded) { + if (!m_metaVersion || !m_metaVersion->isLoaded()) { + // wait for the loaded version from meta + m_metaVersion = APPLICATION->metadataIndex()->getLoadedVersion(m_uid, m_version); + } + m_loaded = true; + updateCachedData(); + } +} + +void Component::setUpdateAction(UpdateAction action) +{ + m_updateAction = action; +} + +UpdateAction Component::getUpdateAction() +{ + return m_updateAction; +} + +void Component::clearUpdateAction() +{ + m_updateAction = UpdateAction{ UpdateActionNone{} }; +} + +QDebug operator<<(QDebug d, const Component& comp) +{ + d << "Component(" << comp.m_uid << " : " << comp.m_cachedVersion << ")"; + return d; +} diff --git a/launcher/minecraft/Component.h b/launcher/minecraft/Component.h index 8aa6b4743..7ff30889f 100644 --- a/launcher/minecraft/Component.h +++ b/launcher/minecraft/Component.h @@ -4,9 +4,12 @@ #include #include #include +#include +#include #include "ProblemProvider.h" #include "QObjectPtr.h" #include "meta/JsonFormat.h" +#include "modplatform/ModIndex.h" class PackProfile; class LaunchProfile; @@ -16,6 +19,36 @@ class VersionList; } // namespace Meta class VersionFile; +struct UpdateActionChangeVersion { + /// version to change to + QString targetVersion; +}; +struct UpdateActionLatestRecommendedCompatible { + /// Parent uid + QString parentUid; + QString parentName; + /// Parent version + QString version; + /// +}; +struct UpdateActionRemove {}; +struct UpdateActionImportantChanged { + QString oldVersion; +}; + +using UpdateActionNone = std::monostate; + +using UpdateAction = std::variant; + +struct ModloaderMapEntry { + ModPlatform::ModLoaderType type; + QStringList knownConflictingComponents; +}; + class Component : public QObject, public ProblemProvider { Q_OBJECT public: @@ -26,6 +59,8 @@ class Component : public QObject, public ProblemProvider { virtual ~Component() {} + static const QMap KNOWN_MODLOADERS; + void applyTo(LaunchProfile* profile); bool isEnabled(); @@ -38,6 +73,8 @@ class Component : public QObject, public ProblemProvider { bool isRemovable(); bool isCustom(); bool isVersionChangeable(); + bool isKnownModloader(); + QStringList knownConflictingComponents(); // DEPRECATED: explicit numeric order values, used for loading old non-component config. TODO: refactor and move to migration code void setOrder(int order); @@ -58,6 +95,8 @@ class Component : public QObject, public ProblemProvider { const QList getProblems() const override; ProblemSeverity getProblemSeverity() const override; + void addComponentProblem(ProblemSeverity severity, const QString& description); + void resetComponentProblems(); void setVersion(const QString& version); bool customize(); @@ -65,6 +104,12 @@ class Component : public QObject, public ProblemProvider { void updateCachedData(); + void waitLoadMeta(); + + void setUpdateAction(UpdateAction action); + void clearUpdateAction(); + UpdateAction getUpdateAction(); + signals: void dataChanged(); @@ -102,6 +147,11 @@ class Component : public QObject, public ProblemProvider { std::shared_ptr m_metaVersion; std::shared_ptr m_file; bool m_loaded = false; + + private: + QList m_componentProblems; + ProblemSeverity m_componentProblemSeverity = ProblemSeverity::None; + UpdateAction m_updateAction = UpdateAction{ UpdateActionNone{} }; }; using ComponentPtr = shared_qobject_ptr; diff --git a/launcher/minecraft/ComponentUpdateTask.cpp b/launcher/minecraft/ComponentUpdateTask.cpp index 4d205af6c..6656a84f8 100644 --- a/launcher/minecraft/ComponentUpdateTask.cpp +++ b/launcher/minecraft/ComponentUpdateTask.cpp @@ -1,13 +1,16 @@ #include "ComponentUpdateTask.h" +#include #include "Component.h" #include "ComponentUpdateTask_p.h" #include "PackProfile.h" #include "PackProfile_p.h" +#include "ProblemProvider.h" #include "Version.h" #include "cassert" #include "meta/Index.h" #include "meta/Version.h" +#include "minecraft/MinecraftInstance.h" #include "minecraft/OneSixVersionFormat.h" #include "minecraft/ProfileUtils.h" #include "net/Mode.h" @@ -15,6 +18,8 @@ #include "Application.h" #include "tasks/Task.h" +#include "minecraft/Logging.h" + /* * This is responsible for loading the components of a component list AND resolving dependency issues between them */ @@ -36,7 +41,7 @@ ComponentUpdateTask::ComponentUpdateTask(Mode mode, Net::Mode netmode, PackProfile* list, QObject* parent) : Task(parent) { d.reset(new ComponentUpdateTaskData); - d->m_list = list; + d->m_profile = list; d->mode = mode; d->netmode = netmode; } @@ -45,7 +50,7 @@ ComponentUpdateTask::~ComponentUpdateTask() {} void ComponentUpdateTask::executeTask() { - qDebug() << "Loading components"; + qCDebug(instanceProfileResolveC) << "Loading components"; loadComponents(); } @@ -63,7 +68,7 @@ LoadResult composeLoadResult(LoadResult a, LoadResult b) static LoadResult loadComponent(ComponentPtr component, Task::Ptr& loadTask, Net::Mode netmode) { if (component->m_loaded) { - qDebug() << component->getName() << "is already loaded"; + qCDebug(instanceProfileResolveC) << component->getName() << "is already loaded"; return LoadResult::LoadedLocal; } @@ -144,10 +149,11 @@ void ComponentUpdateTask::loadComponents() d->remoteLoadSuccessful = true; // load all the components OR their lists... - for (auto component : d->m_list->d->components) { + for (auto component : d->m_profile->d->components) { Task::Ptr loadTask; LoadResult singleResult; RemoteLoadStatus::Type loadType; + component->resetComponentProblems(); // FIXME: to do this right, we need to load the lists and decide on which versions to use during dependency resolution. For now, // ignore all that... #if 0 @@ -175,7 +181,8 @@ void ComponentUpdateTask::loadComponents() } result = composeLoadResult(result, singleResult); if (loadTask) { - qDebug() << "Remote loading is being run for" << component->getName(); + qCDebug(instanceProfileResolveC) << d->m_profile->d->m_instance->name() << "|" + << "Remote loading is being run for" << component->getName(); connect(loadTask.get(), &Task::succeeded, this, [this, taskIndex]() { remoteLoadSucceeded(taskIndex); }); connect(loadTask.get(), &Task::failed, this, [this, taskIndex](const QString& error) { remoteLoadFailed(taskIndex, error); }); connect(loadTask.get(), &Task::aborted, this, [this, taskIndex]() { remoteLoadFailed(taskIndex, tr("Aborted")); }); @@ -192,6 +199,7 @@ void ComponentUpdateTask::loadComponents() switch (result) { case LoadResult::LoadedLocal: { // Everything got loaded. Advance to dependency resolution. + performUpdateActions(); resolveDependencies(d->mode == Mode::Launch || d->netmode == Net::Mode::Offline); break; } @@ -270,8 +278,8 @@ static bool gatherRequirementsFromComponents(const ComponentContainer& input, Re output.erase(componenRequireEx); output.insert(result.outcome); } else { - qCritical() << "Conflicting requirements:" << componentRequire.uid << "versions:" << componentRequire.equalsVersion - << ";" << (*found).equalsVersion; + qCCritical(instanceProfileResolveC) << "Conflicting requirements:" << componentRequire.uid + << "versions:" << componentRequire.equalsVersion << ";" << (*found).equalsVersion; } succeeded &= result.ok; } else { @@ -353,22 +361,22 @@ static bool getTrivialComponentChanges(const ComponentIndex& index, const Requir } while (false); switch (decision) { case Decision::Undetermined: - qCritical() << "No decision for" << reqStr; + qCCritical(instanceProfileResolveC) << "No decision for" << reqStr; succeeded = false; break; case Decision::Met: - qDebug() << reqStr << "Is met."; + qCDebug(instanceProfileResolveC) << reqStr << "Is met."; break; case Decision::Missing: - qDebug() << reqStr << "Is missing and should be added at" << req.indexOfFirstDependee; + qCDebug(instanceProfileResolveC) << reqStr << "Is missing and should be added at" << req.indexOfFirstDependee; toAdd.insert(req); break; case Decision::VersionNotSame: - qDebug() << reqStr << "already has different version that can be changed."; + qCDebug(instanceProfileResolveC) << reqStr << "already has different version that can be changed."; toChange.insert(req); break; case Decision::LockedVersionNotSame: - qDebug() << reqStr << "already has different version that cannot be changed."; + qCDebug(instanceProfileResolveC) << reqStr << "already has different version that cannot be changed."; succeeded = false; break; } @@ -376,12 +384,48 @@ static bool getTrivialComponentChanges(const ComponentIndex& index, const Requir return succeeded; } +ComponentContainer ComponentUpdateTask::collectTreeLinked(const QString& uid) +{ + ComponentContainer linked; + + auto& components = d->m_profile->d->components; + auto& componentIndex = d->m_profile->d->componentIndex; + auto& instance = d->m_profile->d->m_instance; + for (auto comp : components) { + qCDebug(instanceProfileResolveC) << instance->name() << "|" + << "scanning" << comp->getID() << ":" << comp->getVersion() << "for tree link"; + auto dep = std::find_if(comp->m_cachedRequires.cbegin(), comp->m_cachedRequires.cend(), + [uid](const Meta::Require& req) -> bool { return req.uid == uid; }); + if (dep != comp->m_cachedRequires.cend()) { + qCDebug(instanceProfileResolveC) << instance->name() << "|" << comp->getID() << ":" << comp->getVersion() << "depends on" + << uid; + linked.append(comp); + } + } + auto iter = componentIndex.find(uid); + if (iter != componentIndex.end()) { + ComponentPtr comp = *iter; + comp->updateCachedData(); + qCDebug(instanceProfileResolveC) << instance->name() << "|" << comp->getID() << ":" << comp->getVersion() << "has" + << comp->m_cachedRequires.size() << "dependencies"; + for (auto dep : comp->m_cachedRequires) { + qCDebug(instanceProfileC) << instance->name() << "|" << uid << "depends on" << dep.uid; + auto found = componentIndex.find(dep.uid); + if (found != componentIndex.end()) { + qCDebug(instanceProfileC) << instance->name() << "|" << (*found)->getID() << "is present"; + linked.append(*found); + } + } + } + return linked; +} + // FIXME, TODO: decouple dependency resolution from loading // FIXME: This works directly with the PackProfile internals. It shouldn't! It needs richer data types than PackProfile uses. // FIXME: throw all this away and use a graph void ComponentUpdateTask::resolveDependencies(bool checkOnly) { - qDebug() << "Resolving dependencies"; + qCDebug(instanceProfileResolveC) << "Resolving dependencies"; /* * this is a naive dependency resolving algorithm. all it does is check for following conditions and react in simple ways: * 1. There are conflicting dependencies on the same uid with different exact version numbers @@ -393,8 +437,8 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly) * * NOTE: this is a placeholder and should eventually be replaced with something 'serious' */ - auto& components = d->m_list->d->components; - auto& componentIndex = d->m_list->d->componentIndex; + auto& components = d->m_profile->d->components; + auto& componentIndex = d->m_profile->d->componentIndex; RequireExSet allRequires; QStringList toRemove; @@ -402,15 +446,16 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly) allRequires.clear(); toRemove.clear(); if (!gatherRequirementsFromComponents(components, allRequires)) { + finalizeComponents(); emitFailed(tr("Conflicting requirements detected during dependency checking!")); return; } getTrivialRemovals(components, allRequires, toRemove); if (!toRemove.isEmpty()) { - qDebug() << "Removing obsolete components..."; + qCDebug(instanceProfileResolveC) << "Removing obsolete components..."; for (auto& remove : toRemove) { - qDebug() << "Removing" << remove; - d->m_list->remove(remove); + qCDebug(instanceProfileResolveC) << "Removing" << remove; + d->m_profile->remove(remove); } } } while (!toRemove.isEmpty()); @@ -418,10 +463,12 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly) RequireExSet toChange; bool succeeded = getTrivialComponentChanges(componentIndex, allRequires, toAdd, toChange); if (!succeeded) { + finalizeComponents(); emitFailed(tr("Instance has conflicting dependencies.")); return; } if (checkOnly) { + finalizeComponents(); if (toAdd.size() || toChange.size()) { emitFailed(tr("Instance has unresolved dependencies while loading/checking for launch.")); } else { @@ -434,14 +481,15 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly) if (toAdd.size()) { // add stuff... for (auto& add : toAdd) { - auto component = makeShared(d->m_list, add.uid); + auto component = makeShared(d->m_profile, add.uid); if (!add.equalsVersion.isEmpty()) { // exact version - qDebug() << "Adding" << add.uid << "version" << add.equalsVersion << "at position" << add.indexOfFirstDependee; + qCDebug(instanceProfileResolveC) + << "Adding" << add.uid << "version" << add.equalsVersion << "at position" << add.indexOfFirstDependee; component->m_version = add.equalsVersion; } else { // version needs to be decided - qDebug() << "Adding" << add.uid << "at position" << add.indexOfFirstDependee; + qCDebug(instanceProfileResolveC) << "Adding" << add.uid << "at position" << add.indexOfFirstDependee; // ############################################################################################################ // HACK HACK HACK HACK FIXME: this is a placeholder for deciding what version to use. For now, it is hardcoded. if (!add.suggests.isEmpty()) { @@ -464,7 +512,7 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly) } component->m_dependencyOnly = true; // FIXME: this should not work directly with the component list - d->m_list->insertComponent(add.indexOfFirstDependee, component); + d->m_profile->insertComponent(add.indexOfFirstDependee, component); componentIndex[add.uid] = component; } recursionNeeded = true; @@ -473,7 +521,7 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly) // change a version of something that exists for (auto& change : toChange) { // FIXME: this should not work directly with the component list - qDebug() << "Setting version of " << change.uid << "to" << change.equalsVersion; + qCDebug(instanceProfileResolveC) << "Setting version of " << change.uid << "to" << change.equalsVersion; auto component = componentIndex[change.uid]; component->setVersion(change.equalsVersion); } @@ -483,14 +531,182 @@ void ComponentUpdateTask::resolveDependencies(bool checkOnly) if (recursionNeeded) { loadComponents(); } else { + finalizeComponents(); emitSucceeded(); } } +// Variant visitation via lambda +template +struct overload : Ts... { + using Ts::operator()...; +}; +template +overload(Ts...) -> overload; + +void ComponentUpdateTask::performUpdateActions() +{ + auto& instance = d->m_profile->d->m_instance; + bool addedActions; + QStringList toRemove; + do { + addedActions = false; + toRemove.clear(); + auto& components = d->m_profile->d->components; + auto& componentIndex = d->m_profile->d->componentIndex; + for (auto component : components) { + if (!component) { + continue; + } + auto action = component->getUpdateAction(); + auto visitor = + overload{ [](const UpdateActionNone&) { + // noop + }, + [&component, &instance](const UpdateActionChangeVersion& cv) { + qCDebug(instanceProfileResolveC) << instance->name() << "|" + << "UpdateActionChangeVersion" << component->getID() << ":" + << component->getVersion() << "change to" << cv.targetVersion; + component->setVersion(cv.targetVersion); + component->waitLoadMeta(); + }, + [&component, &instance](const UpdateActionLatestRecommendedCompatible lrc) { + qCDebug(instanceProfileResolveC) + << instance->name() << "|" + << "UpdateActionLatestRecommendedCompatible" << component->getID() << ":" << component->getVersion() + << "updating to latest recommend or compatible with" << lrc.parentUid << lrc.version; + auto versionList = APPLICATION->metadataIndex()->get(component->getID()); + if (versionList) { + versionList->waitToLoad(); + auto recommended = versionList->getRecommendedForParent(lrc.parentUid, lrc.version); + if (!recommended) { + recommended = versionList->getLatestForParent(lrc.parentUid, lrc.version); + } + if (recommended) { + component->setVersion(recommended->version()); + component->waitLoadMeta(); + return; + } else { + component->addComponentProblem(ProblemSeverity::Error, + QObject::tr("No compatible version of %1 found for %2 %3") + .arg(component->getName(), lrc.parentName, lrc.version)); + } + } else { + component->addComponentProblem( + ProblemSeverity::Error, + QObject::tr("No version list in metadata index for %1").arg(component->getID())); + } + }, + [&component, &instance, &toRemove](const UpdateActionRemove&) { + qCDebug(instanceProfileResolveC) + << instance->name() << "|" + << "UpdateActionRemove" << component->getID() << ":" << component->getVersion() << "removing"; + toRemove.append(component->getID()); + }, + [this, &component, &instance, &addedActions, &componentIndex](const UpdateActionImportantChanged& ic) { + qCDebug(instanceProfileResolveC) + << instance->name() << "|" + << "UpdateImportantChanged" << component->getID() << ":" << component->getVersion() << "was changed from" + << ic.oldVersion << "updating linked components"; + auto oldVersion = APPLICATION->metadataIndex()->getLoadedVersion(component->getID(), ic.oldVersion); + for (auto oldReq : oldVersion->requiredSet()) { + auto currentlyRequired = component->m_cachedRequires.find(oldReq); + if (currentlyRequired == component->m_cachedRequires.cend()) { + auto oldReqComp = componentIndex.find(oldReq.uid); + if (oldReqComp != componentIndex.cend()) { + (*oldReqComp)->setUpdateAction(UpdateAction{ UpdateActionRemove{} }); + addedActions = true; + } + } + } + auto linked = collectTreeLinked(component->getID()); + for (auto comp : linked) { + if (comp->isCustom()) { + continue; + } + auto compUid = comp->getID(); + auto parentReq = std::find_if(component->m_cachedRequires.begin(), component->m_cachedRequires.end(), + [compUid](const Meta::Require& req) { return req.uid == compUid; }); + if (parentReq != component->m_cachedRequires.end()) { + auto newVersion = parentReq->equalsVersion.isEmpty() ? parentReq->suggests : parentReq->equalsVersion; + if (!newVersion.isEmpty()) { + comp->setUpdateAction(UpdateAction{ UpdateActionChangeVersion{ newVersion } }); + } else { + comp->setUpdateAction(UpdateAction{ UpdateActionLatestRecommendedCompatible{ + component->getID(), + component->getName(), + component->getVersion(), + } }); + } + } else { + comp->setUpdateAction(UpdateAction{ UpdateActionLatestRecommendedCompatible{ + component->getID(), + component->getName(), + component->getVersion(), + } }); + } + addedActions = true; + } + } }; + std::visit(visitor, action); + component->clearUpdateAction(); + for (auto uid : toRemove) { + d->m_profile->remove(uid); + } + } + } while (addedActions); +} + +void ComponentUpdateTask::finalizeComponents() +{ + auto& components = d->m_profile->d->components; + auto& componentIndex = d->m_profile->d->componentIndex; + for (auto component : components) { + for (auto req : component->m_cachedRequires) { + auto found = componentIndex.find(req.uid); + if (found == componentIndex.cend()) { + component->addComponentProblem( + ProblemSeverity::Error, + QObject::tr("%1 is missing requirement %2 %3") + .arg(component->getName(), req.uid, req.equalsVersion.isEmpty() ? req.suggests : req.equalsVersion)); + } else { + auto reqComp = *found; + if (!reqComp->getProblems().isEmpty()) { + component->addComponentProblem( + reqComp->getProblemSeverity(), + QObject::tr("%1, a dependency of this component, has reported issues").arg(reqComp->getName())); + } + if (!req.equalsVersion.isEmpty() && req.equalsVersion != reqComp->getVersion()) { + component->addComponentProblem(ProblemSeverity::Error, + QObject::tr("%1, a dependency of this component, is not the required version %2") + .arg(reqComp->getName(), req.equalsVersion)); + } else if (!req.suggests.isEmpty() && req.suggests != reqComp->getVersion()) { + component->addComponentProblem(ProblemSeverity::Warning, + QObject::tr("%1, a dependency of this component, is not the suggested version %2") + .arg(reqComp->getName(), req.suggests)); + } + } + } + for (auto conflict : component->knownConflictingComponents()) { + auto found = componentIndex.find(conflict); + if (found != componentIndex.cend()) { + auto foundComp = *found; + if (foundComp->isCustom()) { + continue; + } + component->addComponentProblem( + ProblemSeverity::Warning, + QObject::tr("%1 and %2 are known to not work together. It is recommended to remove one of them.") + .arg(component->getName(), foundComp->getName())); + } + } + } +} + void ComponentUpdateTask::remoteLoadSucceeded(size_t taskIndex) { if (static_cast(d->remoteLoadStatusList.size()) < taskIndex) { - qWarning() << "Got task index outside of results" << taskIndex; + qCWarning(instanceProfileResolveC) << "Got task index outside of results" << taskIndex; return; } auto& taskSlot = d->remoteLoadStatusList[taskIndex]; @@ -498,16 +714,16 @@ void ComponentUpdateTask::remoteLoadSucceeded(size_t taskIndex) disconnect(taskSlot.task.get(), &Task::failed, this, nullptr); disconnect(taskSlot.task.get(), &Task::aborted, this, nullptr); if (taskSlot.finished) { - qWarning() << "Got multiple results from remote load task" << taskIndex; + qCWarning(instanceProfileResolveC) << "Got multiple results from remote load task" << taskIndex; return; } - qDebug() << "Remote task" << taskIndex << "succeeded"; + qCDebug(instanceProfileResolveC) << "Remote task" << taskIndex << "succeeded"; taskSlot.succeeded = false; taskSlot.finished = true; d->remoteTasksInProgress--; // update the cached data of the component from the downloaded version file. if (taskSlot.type == RemoteLoadStatus::Type::Version) { - auto component = d->m_list->getComponent(taskSlot.PackProfileIndex); + auto component = d->m_profile->getComponent(taskSlot.PackProfileIndex); component->m_loaded = true; component->updateCachedData(); } @@ -517,7 +733,7 @@ void ComponentUpdateTask::remoteLoadSucceeded(size_t taskIndex) void ComponentUpdateTask::remoteLoadFailed(size_t taskIndex, const QString& msg) { if (static_cast(d->remoteLoadStatusList.size()) < taskIndex) { - qWarning() << "Got task index outside of results" << taskIndex; + qCWarning(instanceProfileResolveC) << "Got task index outside of results" << taskIndex; return; } auto& taskSlot = d->remoteLoadStatusList[taskIndex]; @@ -525,10 +741,10 @@ void ComponentUpdateTask::remoteLoadFailed(size_t taskIndex, const QString& msg) disconnect(taskSlot.task.get(), &Task::failed, this, nullptr); disconnect(taskSlot.task.get(), &Task::aborted, this, nullptr); if (taskSlot.finished) { - qWarning() << "Got multiple results from remote load task" << taskIndex; + qCWarning(instanceProfileResolveC) << "Got multiple results from remote load task" << taskIndex; return; } - qDebug() << "Remote task" << taskIndex << "failed: " << msg; + qCDebug(instanceProfileResolveC) << "Remote task" << taskIndex << "failed: " << msg; d->remoteLoadSuccessful = false; taskSlot.succeeded = false; taskSlot.finished = true; @@ -546,6 +762,7 @@ void ComponentUpdateTask::checkIfAllFinished() if (d->remoteLoadSuccessful) { // nothing bad happened... clear the temp load status and proceed with looking at dependencies d->remoteLoadStatusList.clear(); + performUpdateActions(); resolveDependencies(d->mode == Mode::Launch); } else { // remote load failed... report error and bail diff --git a/launcher/minecraft/ComponentUpdateTask.h b/launcher/minecraft/ComponentUpdateTask.h index 2f396a049..484c0bedd 100644 --- a/launcher/minecraft/ComponentUpdateTask.h +++ b/launcher/minecraft/ComponentUpdateTask.h @@ -1,5 +1,6 @@ #pragma once +#include "minecraft/Component.h" #include "net/Mode.h" #include "tasks/Task.h" @@ -21,7 +22,11 @@ class ComponentUpdateTask : public Task { private: void loadComponents(); + /// collects components that are dependent on or dependencies of the component + QList collectTreeLinked(const QString& uid); void resolveDependencies(bool checkOnly); + void performUpdateActions(); + void finalizeComponents(); void remoteLoadSucceeded(size_t index); void remoteLoadFailed(size_t index, const QString& msg); diff --git a/launcher/minecraft/ComponentUpdateTask_p.h b/launcher/minecraft/ComponentUpdateTask_p.h index b82553700..2fc0b6d9a 100644 --- a/launcher/minecraft/ComponentUpdateTask_p.h +++ b/launcher/minecraft/ComponentUpdateTask_p.h @@ -6,6 +6,8 @@ #include "net/Mode.h" #include "tasks/Task.h" +#include "minecraft/ComponentUpdateTask.h" + class PackProfile; struct RemoteLoadStatus { @@ -18,7 +20,7 @@ struct RemoteLoadStatus { }; struct ComponentUpdateTaskData { - PackProfile* m_list = nullptr; + PackProfile* m_profile = nullptr; QList remoteLoadStatusList; bool remoteLoadSuccessful = true; size_t remoteTasksInProgress = 0; diff --git a/launcher/minecraft/LaunchProfile.cpp b/launcher/minecraft/LaunchProfile.cpp index 468798850..c11a0f915 100644 --- a/launcher/minecraft/LaunchProfile.cpp +++ b/launcher/minecraft/LaunchProfile.cpp @@ -164,6 +164,7 @@ void LaunchProfile::applyCompatibleJavaMajors(QList& javaMajor) { m_compatibleJavaMajors.append(javaMajor); } + void LaunchProfile::applyCompatibleJavaName(QString javaName) { if (!javaName.isEmpty()) diff --git a/launcher/minecraft/Logging.cpp b/launcher/minecraft/Logging.cpp new file mode 100644 index 000000000..92596de3e --- /dev/null +++ b/launcher/minecraft/Logging.cpp @@ -0,0 +1,25 @@ + +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "minecraft/Logging.h" +#include + +Q_LOGGING_CATEGORY(instanceProfileC, "launcher.instance.profile") +Q_LOGGING_CATEGORY(instanceProfileResolveC, "launcher.instance.profile.resolve") diff --git a/launcher/minecraft/Logging.h b/launcher/minecraft/Logging.h new file mode 100644 index 000000000..00d43f419 --- /dev/null +++ b/launcher/minecraft/Logging.h @@ -0,0 +1,26 @@ + +// SPDX-License-Identifier: GPL-3.0-only +/* + * Prism Launcher - Minecraft Launcher + * Copyright (C) 2023 Rachel Powers <508861+Ryex@users.noreply.github.com> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#pragma once + +#include + +Q_DECLARE_LOGGING_CATEGORY(instanceProfileC) +Q_DECLARE_LOGGING_CATEGORY(instanceProfileResolveC) diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index d861056bf..c94f3f234 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -43,6 +43,9 @@ #include "minecraft/launch/CreateGameFolders.h" #include "minecraft/launch/ExtractNatives.h" #include "minecraft/launch/PrintInstanceInfo.h" +#include "minecraft/update/AssetUpdateTask.h" +#include "minecraft/update/FMLLibrariesTask.h" +#include "minecraft/update/LibrariesTask.h" #include "settings/Setting.h" #include "settings/SettingsObject.h" @@ -53,13 +56,13 @@ #include "pathmatcher/RegexpMatcher.h" #include "launch/LaunchTask.h" +#include "launch/TaskStepWrapper.h" #include "launch/steps/CheckJava.h" #include "launch/steps/LookupServerAddress.h" #include "launch/steps/PostLaunchCommand.h" #include "launch/steps/PreLaunchCommand.h" #include "launch/steps/QuitAfterGameStop.h" #include "launch/steps/TextPrint.h" -#include "launch/steps/Update.h" #include "minecraft/launch/ClaimAccount.h" #include "minecraft/launch/LauncherPartLaunch.h" @@ -70,9 +73,6 @@ #include "java/JavaUtils.h" -#include "meta/Index.h" -#include "meta/VersionList.h" - #include "icons/IconList.h" #include "mod/ModFolderModel.h" @@ -84,7 +84,6 @@ #include "AssetsUtils.h" #include "MinecraftLoadAndCheck.h" -#include "MinecraftUpdate.h" #include "PackProfile.h" #include "minecraft/gameoptions/GameOptions.h" #include "minecraft/update/FoldersTask.h" @@ -218,6 +217,7 @@ void MinecraftInstance::loadSpecificSettings() void MinecraftInstance::updateRuntimeContext() { m_runtimeContext.updateFromInstanceSettings(m_settings); + m_components->invalidateLaunchProfile(); } QString MinecraftInstance::typeName() const @@ -1030,18 +1030,18 @@ QString MinecraftInstance::getStatusbarDescription() return description; } -Task::Ptr MinecraftInstance::createUpdateTask(Net::Mode mode) +QList MinecraftInstance::createUpdateTask() { - updateRuntimeContext(); - switch (mode) { - case Net::Mode::Offline: { - return Task::Ptr(new MinecraftLoadAndCheck(this)); - } - case Net::Mode::Online: { - return Task::Ptr(new MinecraftUpdate(this)); - } - } - return nullptr; + return { + // create folders + makeShared(this), + // libraries download + makeShared(this), + // FML libraries download and copy into the instance + makeShared(this), + // assets update + makeShared(this), + }; } shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin) @@ -1090,14 +1090,10 @@ shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPt process->appendStep(step); } - // if we aren't in offline mode,. - if (session->status != AuthSession::PlayableOffline) { - if (!session->demo) { - process->appendStep(makeShared(pptr, session)); - } - process->appendStep(makeShared(pptr, Net::Mode::Online)); - } else { - process->appendStep(makeShared(pptr, Net::Mode::Offline)); + // load meta + { + auto mode = session->status != AuthSession::PlayableOffline ? Net::Mode::Online : Net::Mode::Offline; + process->appendStep(makeShared(pptr, makeShared(this, mode, pptr))); } // check java @@ -1106,6 +1102,16 @@ shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPt process->appendStep(makeShared(pptr)); } + // if we aren't in offline mode,. + if (session->status != AuthSession::PlayableOffline) { + if (!session->demo) { + process->appendStep(makeShared(pptr, session)); + } + for (auto t : createUpdateTask()) { + process->appendStep(makeShared(pptr, t)); + } + } + // if there are any jar mods { process->appendStep(makeShared(pptr)); diff --git a/launcher/minecraft/MinecraftInstance.h b/launcher/minecraft/MinecraftInstance.h index ad2cda186..75e97ae45 100644 --- a/launcher/minecraft/MinecraftInstance.h +++ b/launcher/minecraft/MinecraftInstance.h @@ -104,7 +104,7 @@ class MinecraftInstance : public BaseInstance { /** Returns whether the instance, with its version, has support for demo mode. */ [[nodiscard]] bool supportsDemo() const; - void updateRuntimeContext(); + void updateRuntimeContext() override; ////// Profile management ////// std::shared_ptr getPackProfile() const; @@ -120,7 +120,7 @@ class MinecraftInstance : public BaseInstance { std::shared_ptr gameOptionsModel(); ////// Launch stuff ////// - Task::Ptr createUpdateTask(Net::Mode mode) override; + QList createUpdateTask() override; shared_qobject_ptr createLaunchTask(AuthSessionPtr account, MinecraftTarget::Ptr targetToJoin) override; QStringList extraArguments() override; QStringList verboseDescription(AuthSessionPtr session, MinecraftTarget::Ptr targetToJoin) override; diff --git a/launcher/minecraft/MinecraftLoadAndCheck.cpp b/launcher/minecraft/MinecraftLoadAndCheck.cpp index 818e90cfc..655894d2a 100644 --- a/launcher/minecraft/MinecraftLoadAndCheck.cpp +++ b/launcher/minecraft/MinecraftLoadAndCheck.cpp @@ -2,41 +2,42 @@ #include "MinecraftInstance.h" #include "PackProfile.h" -MinecraftLoadAndCheck::MinecraftLoadAndCheck(MinecraftInstance* inst, QObject* parent) : Task(parent), m_inst(inst) {} +MinecraftLoadAndCheck::MinecraftLoadAndCheck(MinecraftInstance* inst, Net::Mode netmode, QObject* parent) + : Task(parent), m_inst(inst), m_netmode(netmode) +{} void MinecraftLoadAndCheck::executeTask() { // add offline metadata load task auto components = m_inst->getPackProfile(); - components->reload(Net::Mode::Offline); + components->reload(m_netmode); m_task = components->getCurrentTask(); if (!m_task) { emitSucceeded(); return; } - connect(m_task.get(), &Task::succeeded, this, &MinecraftLoadAndCheck::subtaskSucceeded); - connect(m_task.get(), &Task::failed, this, &MinecraftLoadAndCheck::subtaskFailed); - connect(m_task.get(), &Task::aborted, this, [this] { subtaskFailed(tr("Aborted")); }); - connect(m_task.get(), &Task::progress, this, &MinecraftLoadAndCheck::progress); + connect(m_task.get(), &Task::succeeded, this, &MinecraftLoadAndCheck::emitSucceeded); + connect(m_task.get(), &Task::failed, this, &MinecraftLoadAndCheck::emitFailed); + connect(m_task.get(), &Task::aborted, this, [this] { emitFailed(tr("Aborted")); }); + connect(m_task.get(), &Task::progress, this, &MinecraftLoadAndCheck::setProgress); connect(m_task.get(), &Task::stepProgress, this, &MinecraftLoadAndCheck::propagateStepProgress); connect(m_task.get(), &Task::status, this, &MinecraftLoadAndCheck::setStatus); + connect(m_task.get(), &Task::details, this, &MinecraftLoadAndCheck::setDetails); } -void MinecraftLoadAndCheck::subtaskSucceeded() +bool MinecraftLoadAndCheck::canAbort() const { - if (isFinished()) { - qCritical() << "MinecraftUpdate: Subtask" << sender() << "succeeded, but work was already done!"; - return; + if (m_task) { + return m_task->canAbort(); } - emitSucceeded(); + return true; } -void MinecraftLoadAndCheck::subtaskFailed(QString error) +bool MinecraftLoadAndCheck::abort() { - if (isFinished()) { - qCritical() << "MinecraftUpdate: Subtask" << sender() << "failed, but work was already done!"; - return; + if (m_task && m_task->canAbort()) { + return m_task->abort(); } - emitFailed(error); -} + return Task::abort(); +} \ No newline at end of file diff --git a/launcher/minecraft/MinecraftLoadAndCheck.h b/launcher/minecraft/MinecraftLoadAndCheck.h index 09edaf909..72e9e0caa 100644 --- a/launcher/minecraft/MinecraftLoadAndCheck.h +++ b/launcher/minecraft/MinecraftLoadAndCheck.h @@ -15,32 +15,24 @@ #pragma once -#include -#include -#include - -#include +#include "net/Mode.h" #include "tasks/Task.h" -#include "QObjectPtr.h" - -class MinecraftVersion; class MinecraftInstance; class MinecraftLoadAndCheck : public Task { Q_OBJECT public: - explicit MinecraftLoadAndCheck(MinecraftInstance* inst, QObject* parent = 0); - virtual ~MinecraftLoadAndCheck() {}; + explicit MinecraftLoadAndCheck(MinecraftInstance* inst, Net::Mode netmode, QObject* parent = nullptr); + virtual ~MinecraftLoadAndCheck() = default; void executeTask() override; - private slots: - void subtaskSucceeded(); - void subtaskFailed(QString error); + bool canAbort() const override; + public slots: + bool abort() override; private: MinecraftInstance* m_inst = nullptr; Task::Ptr m_task; - QString m_preFailure; - QString m_fail_reason; + Net::Mode m_netmode; }; diff --git a/launcher/minecraft/MinecraftUpdate.cpp b/launcher/minecraft/MinecraftUpdate.cpp deleted file mode 100644 index b63430aa8..000000000 --- a/launcher/minecraft/MinecraftUpdate.cpp +++ /dev/null @@ -1,63 +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 "MinecraftUpdate.h" -#include "MinecraftInstance.h" - -#include "minecraft/PackProfile.h" - -#include "tasks/SequentialTask.h" -#include "update/AssetUpdateTask.h" -#include "update/FMLLibrariesTask.h" -#include "update/FoldersTask.h" -#include "update/LibrariesTask.h" - -MinecraftUpdate::MinecraftUpdate(MinecraftInstance* inst, QObject* parent) : SequentialTask(parent), m_inst(inst) {} - -void MinecraftUpdate::executeTask() -{ - m_queue.clear(); - // create folders - { - addTask(makeShared(m_inst)); - } - - // add metadata update task if necessary - { - auto components = m_inst->getPackProfile(); - components->reload(Net::Mode::Online); - auto task = components->getCurrentTask(); - if (task) { - addTask(task); - } - } - - // libraries download - { - addTask(makeShared(m_inst)); - } - - // FML libraries download and copy into the instance - { - addTask(makeShared(m_inst)); - } - - // assets update - { - addTask(makeShared(m_inst)); - } - - SequentialTask::executeTask(); -} diff --git a/launcher/minecraft/MinecraftUpdate.h b/launcher/minecraft/MinecraftUpdate.h deleted file mode 100644 index 456a13518..000000000 --- a/launcher/minecraft/MinecraftUpdate.h +++ /dev/null @@ -1,33 +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 "tasks/SequentialTask.h" - -class MinecraftInstance; - -// this needs to be a task because components->reload does stuff that may block -class MinecraftUpdate : public SequentialTask { - Q_OBJECT - public: - explicit MinecraftUpdate(MinecraftInstance* inst, QObject* parent = 0); - virtual ~MinecraftUpdate() = default; - - void executeTask() override; - - private: - MinecraftInstance* m_inst = nullptr; -}; diff --git a/launcher/minecraft/PackProfile.cpp b/launcher/minecraft/PackProfile.cpp index a8860935c..f1d2473c2 100644 --- a/launcher/minecraft/PackProfile.cpp +++ b/launcher/minecraft/PackProfile.cpp @@ -38,6 +38,7 @@ */ #include +#include #include #include #include @@ -47,10 +48,16 @@ #include #include #include +#include +#include +#include "Application.h" #include "Exception.h" #include "FileSystem.h" #include "Json.h" +#include "meta/Index.h" +#include "meta/JsonFormat.h" +#include "minecraft/Component.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/OneSixVersionFormat.h" #include "minecraft/ProfileUtils.h" @@ -60,11 +67,9 @@ #include "PackProfile_p.h" #include "modplatform/ModIndex.h" -static const QMap modloaderMapping{ { "net.neoforged", ModPlatform::NeoForge }, - { "net.minecraftforge", ModPlatform::Forge }, - { "net.fabricmc.fabric-loader", ModPlatform::Fabric }, - { "org.quiltmc.quilt-loader", ModPlatform::Quilt }, - { "com.mumfrey.liteloader", ModPlatform::LiteLoader } }; +#include "minecraft/Logging.h" + +#include "ui/dialogs/CustomMessageBox.h" PackProfile::PackProfile(MinecraftInstance* instance) : QAbstractListModel() { @@ -153,16 +158,16 @@ static bool savePackProfile(const QString& filename, const ComponentContainer& c obj.insert("components", orderArray); QSaveFile outFile(filename); if (!outFile.open(QFile::WriteOnly)) { - qCritical() << "Couldn't open" << outFile.fileName() << "for writing:" << outFile.errorString(); + qCCritical(instanceProfileC) << "Couldn't open" << outFile.fileName() << "for writing:" << outFile.errorString(); return false; } auto data = QJsonDocument(obj).toJson(QJsonDocument::Indented); if (outFile.write(data) != data.size()) { - qCritical() << "Couldn't write all the data into" << outFile.fileName() << "because:" << outFile.errorString(); + qCCritical(instanceProfileC) << "Couldn't write all the data into" << outFile.fileName() << "because:" << outFile.errorString(); return false; } if (!outFile.commit()) { - qCritical() << "Couldn't save" << outFile.fileName() << "because:" << outFile.errorString(); + qCCritical(instanceProfileC) << "Couldn't save" << outFile.fileName() << "because:" << outFile.errorString(); } return true; } @@ -175,12 +180,12 @@ static bool loadPackProfile(PackProfile* parent, { QFile componentsFile(filename); if (!componentsFile.exists()) { - qWarning() << "Components file doesn't exist. This should never happen."; + qCWarning(instanceProfileC) << "Components file" << filename << "doesn't exist. This should never happen."; return false; } if (!componentsFile.open(QFile::ReadOnly)) { - qCritical() << "Couldn't open" << componentsFile.fileName() << " for reading:" << componentsFile.errorString(); - qWarning() << "Ignoring overridden order"; + qCCritical(instanceProfileC) << "Couldn't open" << componentsFile.fileName() << " for reading:" << componentsFile.errorString(); + qCWarning(instanceProfileC) << "Ignoring overridden order"; return false; } @@ -188,8 +193,8 @@ static bool loadPackProfile(PackProfile* parent, QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(componentsFile.readAll(), &error); if (error.error != QJsonParseError::NoError) { - qCritical() << "Couldn't parse" << componentsFile.fileName() << ":" << error.errorString(); - qWarning() << "Ignoring overridden order"; + qCCritical(instanceProfileC) << "Couldn't parse" << componentsFile.fileName() << ":" << error.errorString(); + qCWarning(instanceProfileC) << "Ignoring overridden order"; return false; } @@ -207,7 +212,7 @@ static bool loadPackProfile(PackProfile* parent, container.append(componentFromJsonV1(parent, componentJsonPattern, comp_obj)); } } catch ([[maybe_unused]] const JSONValidationError& err) { - qCritical() << "Couldn't parse" << componentsFile.fileName() << ": bad file format"; + qCCritical(instanceProfileC) << "Couldn't parse" << componentsFile.fileName() << ": bad file format"; container.clear(); return false; } @@ -240,12 +245,12 @@ void PackProfile::buildingFromScratch() void PackProfile::scheduleSave() { if (!d->loaded) { - qDebug() << "Component list should never save if it didn't successfully load, instance:" << d->m_instance->name(); + qDebug() << d->m_instance->name() << "|" << "Component list should never save if it didn't successfully load"; return; } if (!d->dirty) { d->dirty = true; - qDebug() << "Component list save is scheduled for" << d->m_instance->name(); + qDebug() << d->m_instance->name() << "|" << "Component list save is scheduled"; } d->m_saveTimer.start(); } @@ -272,7 +277,7 @@ QString PackProfile::patchFilePathForUid(const QString& uid) const void PackProfile::save_internal() { - qDebug() << "Component list save performed now for" << d->m_instance->name(); + qDebug() << d->m_instance->name() << "|" << "Component list save performed now"; auto filename = componentsFilePath(); savePackProfile(filename, d->components); d->dirty = false; @@ -285,7 +290,7 @@ bool PackProfile::load() // load the new component list and swap it with the current one... ComponentContainer newComponents; if (!loadPackProfile(this, filename, patchesPattern(), newComponents)) { - qCritical() << "Failed to load the component config for instance" << d->m_instance->name(); + qCritical() << d->m_instance->name() << "|" << "Failed to load the component config"; return false; } else { // FIXME: actually use fine-grained updates, not this... @@ -298,7 +303,7 @@ bool PackProfile::load() d->componentIndex.clear(); for (auto component : newComponents) { if (d->componentIndex.contains(component->m_uid)) { - qWarning() << "Ignoring duplicate component entry" << component->m_uid; + qWarning() << d->m_instance->name() << "|" << "Ignoring duplicate component entry" << component->m_uid; continue; } connect(component.get(), &Component::dataChanged, this, &PackProfile::componentDataChanged); @@ -346,14 +351,14 @@ void PackProfile::resolve(Net::Mode netmode) void PackProfile::updateSucceeded() { - qDebug() << "Component list update/resolve task succeeded for" << d->m_instance->name(); + qCDebug(instanceProfileC) << d->m_instance->name() << "|" << "Component list update/resolve task succeeded"; d->m_updateTask.reset(); invalidateLaunchProfile(); } void PackProfile::updateFailed(const QString& error) { - qDebug() << "Component list update/resolve task failed for" << d->m_instance->name() << "Reason:" << error; + qCDebug(instanceProfileC) << d->m_instance->name() << "|" << "Component list update/resolve task failed " << "Reason:" << error; d->m_updateTask.reset(); invalidateLaunchProfile(); } @@ -369,11 +374,11 @@ void PackProfile::insertComponent(size_t index, ComponentPtr component) { auto id = component->getID(); if (id.isEmpty()) { - qWarning() << "Attempt to add a component with empty ID!"; + qCWarning(instanceProfileC) << d->m_instance->name() << "|" << "Attempt to add a component with empty ID!"; return; } if (d->componentIndex.contains(id)) { - qWarning() << "Attempt to add a component that is already present!"; + qCWarning(instanceProfileC) << d->m_instance->name() << "|" << "Attempt to add a component that is already present!"; return; } beginInsertRows(QModelIndex(), static_cast(index), static_cast(index)); @@ -388,7 +393,7 @@ void PackProfile::componentDataChanged() { auto objPtr = qobject_cast(sender()); if (!objPtr) { - qWarning() << "PackProfile got dataChanged signal from a non-Component!"; + qCWarning(instanceProfileC) << d->m_instance->name() << "|" << "PackProfile got dataChanged signal from a non-Component!"; return; } if (objPtr->getID() == "net.minecraft") { @@ -404,19 +409,20 @@ void PackProfile::componentDataChanged() } index++; } - qWarning() << "PackProfile got dataChanged signal from a Component which does not belong to it!"; + qCWarning(instanceProfileC) << d->m_instance->name() << "|" + << "PackProfile got dataChanged signal from a Component which does not belong to it!"; } bool PackProfile::remove(const int index) { auto patch = getComponent(index); if (!patch->isRemovable()) { - qWarning() << "Patch" << patch->getID() << "is non-removable"; + qCWarning(instanceProfileC) << d->m_instance->name() << "|" << "Patch" << patch->getID() << "is non-removable"; return false; } if (!removeComponent_internal(patch)) { - qCritical() << "Patch" << patch->getID() << "could not be removed"; + qCCritical(instanceProfileC) << d->m_instance->name() << "|" << "Patch" << patch->getID() << "could not be removed"; return false; } @@ -445,11 +451,11 @@ bool PackProfile::customize(int index) { auto patch = getComponent(index); if (!patch->isCustomizable()) { - qDebug() << "Patch" << patch->getID() << "is not customizable"; + qCDebug(instanceProfileC) << d->m_instance->name() << "|" << "Patch" << patch->getID() << "is not customizable"; return false; } if (!patch->customize()) { - qCritical() << "Patch" << patch->getID() << "could not be customized"; + qCCritical(instanceProfileC) << d->m_instance->name() << "|" << "Patch" << patch->getID() << "could not be customized"; return false; } invalidateLaunchProfile(); @@ -461,11 +467,11 @@ bool PackProfile::revertToBase(int index) { auto patch = getComponent(index); if (!patch->isRevertible()) { - qDebug() << "Patch" << patch->getID() << "is not revertible"; + qCDebug(instanceProfileC) << d->m_instance->name() << "|" << "Patch" << patch->getID() << "is not revertible"; return false; } if (!patch->revert()) { - qCritical() << "Patch" << patch->getID() << "could not be reverted"; + qCCritical(instanceProfileC) << d->m_instance->name() << "|" << "Patch" << patch->getID() << "could not be reverted"; return false; } invalidateLaunchProfile(); @@ -678,7 +684,8 @@ bool PackProfile::installComponents(QStringList selectedFiles) const QString target = FS::PathCombine(patchDir, versionFile->uid + ".json"); if (!QFile::copy(source, target)) { - qWarning() << "Component" << source << "could not be copied to target" << target; + qCWarning(instanceProfileC) << d->m_instance->name() << "|" << "Component" << source << "could not be copied to target" + << target; result = false; continue; } @@ -711,7 +718,8 @@ bool PackProfile::installEmpty(const QString& uid, const QString& name) QString patchFileName = FS::PathCombine(patchDir, uid + ".json"); QFile file(patchFileName); if (!file.open(QFile::WriteOnly)) { - qCritical() << "Error opening" << file.fileName() << "for reading:" << file.errorString(); + qCCritical(instanceProfileC) << d->m_instance->name() << "|" << "Error opening" << file.fileName() + << "for reading:" << file.errorString(); return false; } file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); @@ -731,7 +739,8 @@ bool PackProfile::removeComponent_internal(ComponentPtr patch) if (fileName.size()) { QFile patchFile(fileName); if (patchFile.exists() && !patchFile.remove()) { - qCritical() << "File" << fileName << "could not be removed because:" << patchFile.errorString(); + qCCritical(instanceProfileC) << d->m_instance->name() << "|" << "File" << fileName + << "could not be removed because:" << patchFile.errorString(); return false; } } @@ -747,7 +756,8 @@ bool PackProfile::removeComponent_internal(ComponentPtr patch) if (finfo.exists()) { QFile jarModFile(jar[0]); if (!jarModFile.remove()) { - qCritical() << "File" << jar[0] << "could not be removed because:" << jarModFile.errorString(); + qCCritical(instanceProfileC) << d->m_instance->name() << "|" << "File" << jar[0] + << "could not be removed because:" << jarModFile.errorString(); return false; } return true; @@ -804,7 +814,8 @@ bool PackProfile::installJarMods_internal(QStringList filepaths) QFile file(patchFileName); if (!file.open(QFile::WriteOnly)) { - qCritical() << "Error opening" << file.fileName() << "for reading:" << file.errorString(); + qCCritical(instanceProfileC) << d->m_instance->name() << "|" << "Error opening" << file.fileName() + << "for reading:" << file.errorString(); return false; } file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); @@ -858,7 +869,8 @@ bool PackProfile::installCustomJar_internal(QString filepath) QFile file(patchFileName); if (!file.open(QFile::WriteOnly)) { - qCritical() << "Error opening" << file.fileName() << "for reading:" << file.errorString(); + qCCritical(instanceProfileC) << d->m_instance->name() << "|" << "Error opening" << file.fileName() + << "for reading:" << file.errorString(); return false; } file.write(OneSixVersionFormat::versionFileToJson(f).toJson()); @@ -913,7 +925,8 @@ bool PackProfile::installAgents_internal(QStringList filepaths) QFile patchFile(FS::PathCombine(patchDir, targetId + ".json")); if (!patchFile.open(QFile::WriteOnly)) { - qCritical() << "Error opening" << patchFile.fileName() << "for reading:" << patchFile.errorString(); + qCCritical(instanceProfileC) << d->m_instance->name() << "|" << "Error opening" << patchFile.fileName() + << "for reading:" << patchFile.errorString(); return false; } @@ -935,12 +948,13 @@ std::shared_ptr PackProfile::getProfile() const try { auto profile = std::make_shared(); for (auto file : d->components) { - qDebug() << "Applying" << file->getID() << (file->getProblemSeverity() == ProblemSeverity::Error ? "ERROR" : "GOOD"); + qCDebug(instanceProfileC) << d->m_instance->name() << "|" << "Applying" << file->getID() + << (file->getProblemSeverity() == ProblemSeverity::Error ? "ERROR" : "GOOD"); file->applyTo(profile.get()); } d->m_profile = profile; } catch (const Exception& error) { - qWarning() << "Couldn't apply profile patches because: " << error.cause(); + qCWarning(instanceProfileC) << d->m_instance->name() << "|" << "Couldn't apply profile patches because: " << error.cause(); } } return d->m_profile; @@ -953,8 +967,16 @@ bool PackProfile::setComponentVersion(const QString& uid, const QString& version ComponentPtr component = *iter; // set existing if (component->revert()) { + // set new version + auto oldVersion = component->getVersion(); component->setVersion(version); component->setImportant(important); + + if (important) { + component->setUpdateAction(UpdateAction{ UpdateActionImportantChanged{ oldVersion } }); + resolve(Net::Mode::Online); + } + return true; } return false; @@ -993,12 +1015,12 @@ std::optional PackProfile::getModLoaders() ModPlatform::ModLoaderTypes result; bool has_any_loader = false; - QMapIterator i(modloaderMapping); + QMapIterator i(Component::KNOWN_MODLOADERS); while (i.hasNext()) { i.next(); if (auto c = getComponent(i.key()); c != nullptr && c->isEnabled()) { - result |= i.value(); + result |= i.value().type; has_any_loader = true; } } @@ -1026,8 +1048,8 @@ QList PackProfile::getModLoadersList() { QList result; for (auto c : d->components) { - if (c->isEnabled() && modloaderMapping.contains(c->getID())) { - result.append(modloaderMapping[c->getID()]); + if (c->isEnabled() && Component::KNOWN_MODLOADERS.contains(c->getID())) { + result.append(Component::KNOWN_MODLOADERS[c->getID()].type); } } diff --git a/launcher/minecraft/PackProfile.h b/launcher/minecraft/PackProfile.h index 9b6710cc3..b2de26ea0 100644 --- a/launcher/minecraft/PackProfile.h +++ b/launcher/minecraft/PackProfile.h @@ -148,13 +148,13 @@ class PackProfile : public QAbstractListModel { std::optional getSupportedModLoaders(); QList getModLoadersList(); + /// apply the component patches. Catches all the errors and returns true/false for success/failure + void invalidateLaunchProfile(); + private: void scheduleSave(); bool saveIsScheduled() const; - /// apply the component patches. Catches all the errors and returns true/false for success/failure - void invalidateLaunchProfile(); - /// insert component so that its index is ideally the specified one (returns real index) void insertComponent(size_t index, ComponentPtr component); diff --git a/launcher/minecraft/PackProfile_p.h b/launcher/minecraft/PackProfile_p.h index 0cd4fb839..4fb3621f0 100644 --- a/launcher/minecraft/PackProfile_p.h +++ b/launcher/minecraft/PackProfile_p.h @@ -3,8 +3,8 @@ #include #include #include -#include #include "Component.h" +#include "tasks/Task.h" class MinecraftInstance; using ComponentContainer = QList; diff --git a/launcher/minecraft/launch/AutoInstallJava.h b/launcher/minecraft/launch/AutoInstallJava.h index 7e4efc50c..cbfcf5ee7 100644 --- a/launcher/minecraft/launch/AutoInstallJava.h +++ b/launcher/minecraft/launch/AutoInstallJava.h @@ -37,7 +37,6 @@ #include #include -#include "java/JavaMetadata.h" #include "meta/Version.h" #include "minecraft/MinecraftInstance.h" #include "tasks/Task.h" diff --git a/launcher/minecraft/launch/ClaimAccount.h b/launcher/minecraft/launch/ClaimAccount.h index 357a4d4c5..561f0e848 100644 --- a/launcher/minecraft/launch/ClaimAccount.h +++ b/launcher/minecraft/launch/ClaimAccount.h @@ -22,7 +22,7 @@ class ClaimAccount : public LaunchStep { Q_OBJECT public: explicit ClaimAccount(LaunchTask* parent, AuthSessionPtr session); - virtual ~ClaimAccount() {}; + virtual ~ClaimAccount() = default; void executeTask() override; void finalize() override; diff --git a/launcher/minecraft/update/AssetUpdateTask.cpp b/launcher/minecraft/update/AssetUpdateTask.cpp index 8add02d84..acdddc833 100644 --- a/launcher/minecraft/update/AssetUpdateTask.cpp +++ b/launcher/minecraft/update/AssetUpdateTask.cpp @@ -1,5 +1,6 @@ #include "AssetUpdateTask.h" +#include "launch/LaunchStep.h" #include "minecraft/AssetsUtils.h" #include "minecraft/MinecraftInstance.h" #include "minecraft/PackProfile.h" @@ -14,8 +15,6 @@ AssetUpdateTask::AssetUpdateTask(MinecraftInstance* inst) m_inst = inst; } -AssetUpdateTask::~AssetUpdateTask() {} - void AssetUpdateTask::executeTask() { setStatus(tr("Updating assets index...")); diff --git a/launcher/minecraft/update/AssetUpdateTask.h b/launcher/minecraft/update/AssetUpdateTask.h index 6f053a54a..88fac0ac5 100644 --- a/launcher/minecraft/update/AssetUpdateTask.h +++ b/launcher/minecraft/update/AssetUpdateTask.h @@ -7,7 +7,7 @@ class AssetUpdateTask : public Task { Q_OBJECT public: AssetUpdateTask(MinecraftInstance* inst); - virtual ~AssetUpdateTask(); + virtual ~AssetUpdateTask() = default; void executeTask() override; diff --git a/launcher/minecraft/update/FMLLibrariesTask.h b/launcher/minecraft/update/FMLLibrariesTask.h index 32712d239..4fe2648e8 100644 --- a/launcher/minecraft/update/FMLLibrariesTask.h +++ b/launcher/minecraft/update/FMLLibrariesTask.h @@ -9,7 +9,7 @@ class FMLLibrariesTask : public Task { Q_OBJECT public: FMLLibrariesTask(MinecraftInstance* inst); - virtual ~FMLLibrariesTask() {}; + virtual ~FMLLibrariesTask() = default; void executeTask() override; diff --git a/launcher/minecraft/update/FoldersTask.cpp b/launcher/minecraft/update/FoldersTask.cpp index c74e8d2ef..7d6fc4394 100644 --- a/launcher/minecraft/update/FoldersTask.cpp +++ b/launcher/minecraft/update/FoldersTask.cpp @@ -37,7 +37,7 @@ #include #include "minecraft/MinecraftInstance.h" -FoldersTask::FoldersTask(MinecraftInstance* inst) : Task() +FoldersTask::FoldersTask(MinecraftInstance* inst) { m_inst = inst; } diff --git a/launcher/minecraft/update/FoldersTask.h b/launcher/minecraft/update/FoldersTask.h index be4e33eb7..7df7ef81d 100644 --- a/launcher/minecraft/update/FoldersTask.h +++ b/launcher/minecraft/update/FoldersTask.h @@ -7,7 +7,7 @@ class FoldersTask : public Task { Q_OBJECT public: FoldersTask(MinecraftInstance* inst); - virtual ~FoldersTask() {}; + virtual ~FoldersTask() = default; void executeTask() override; diff --git a/launcher/minecraft/update/LibrariesTask.h b/launcher/minecraft/update/LibrariesTask.h index 441191154..838f9d9b4 100644 --- a/launcher/minecraft/update/LibrariesTask.h +++ b/launcher/minecraft/update/LibrariesTask.h @@ -7,7 +7,7 @@ class LibrariesTask : public Task { Q_OBJECT public: LibrariesTask(MinecraftInstance* inst); - virtual ~LibrariesTask() {}; + virtual ~LibrariesTask() = default; void executeTask() override; diff --git a/launcher/ui/pages/instance/ScreenshotsPage.cpp b/launcher/ui/pages/instance/ScreenshotsPage.cpp index c3f955733..b619a07b8 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.cpp +++ b/launcher/ui/pages/instance/ScreenshotsPage.cpp @@ -242,6 +242,11 @@ ScreenshotsPage::ScreenshotsPage(QString path, QWidget* parent) : QMainWindow(pa m_model->setReadOnly(false); m_model->setNameFilters({ "*.png" }); m_model->setNameFilterDisables(false); + // Sorts by modified date instead of creation date because that column is not available and would require subclassing, this should work + // considering screenshots aren't modified after creation. + constexpr int file_modified_column_index = 3; + m_model->sort(file_modified_column_index, Qt::DescendingOrder); + m_folder = path; m_valid = FS::ensureFolderPathExists(m_folder); diff --git a/launcher/ui/pages/instance/VersionPage.cpp b/launcher/ui/pages/instance/VersionPage.cpp index c55d32efb..0c25b4c0c 100644 --- a/launcher/ui/pages/instance/VersionPage.cpp +++ b/launcher/ui/pages/instance/VersionPage.cpp @@ -49,8 +49,12 @@ #include #include #include +#include +#include "QObjectPtr.h" #include "VersionPage.h" +#include "meta/JsonFormat.h" +#include "tasks/SequentialTask.h" #include "ui/dialogs/InstallLoaderDialog.h" #include "ui_VersionPage.h" @@ -63,11 +67,9 @@ #include "DesktopServices.h" #include "Exception.h" -#include "Version.h" #include "icons/IconList.h" #include "minecraft/PackProfile.h" #include "minecraft/auth/AccountList.h" -#include "minecraft/mod/Mod.h" #include "meta/Index.h" #include "meta/VersionList.h" @@ -370,11 +372,25 @@ void VersionPage::on_actionChange_version_triggered() auto patch = m_profile->getComponent(versionRow); auto name = patch->getName(); auto list = patch->getVersionList(); + list->clearExternalRecommends(); if (!list) { return; } auto uid = list->uid(); + // recommend the correct lwjgl version for the current minecraft version + if (uid == "org.lwjgl" || uid == "org.lwjgl3") { + auto minecraft = m_profile->getComponent("net.minecraft"); + auto lwjglReq = std::find_if(minecraft->m_cachedRequires.cbegin(), minecraft->m_cachedRequires.cend(), + [uid](const Meta::Require& req) -> bool { return req.uid == uid; }); + if (lwjglReq != minecraft->m_cachedRequires.cend()) { + auto lwjglVersion = !lwjglReq->equalsVersion.isEmpty() ? lwjglReq->equalsVersion : lwjglReq->suggests; + if (!lwjglVersion.isEmpty()) { + list->addExternalRecommends({ lwjglVersion }); + } + } + } + VersionSelectDialog vselect(list.get(), tr("Change %1 version").arg(name), this); if (uid == "net.fabricmc.intermediary" || uid == "org.quiltmc.hashed") { vselect.setEmptyString(tr("No intermediary mappings versions are currently available.")); @@ -415,14 +431,18 @@ void VersionPage::on_actionDownload_All_triggered() return; } - auto updateTask = m_inst->createUpdateTask(Net::Mode::Online); - if (!updateTask) { + auto updateTasks = m_inst->createUpdateTask(); + if (updateTasks.isEmpty()) { return; } + auto task = makeShared(this); + for (auto t : updateTasks) { + task->addTask(t); + } ProgressDialog tDialog(this); - connect(updateTask.get(), &Task::failed, this, &VersionPage::onGameUpdateError); + connect(task.get(), &Task::failed, this, &VersionPage::onGameUpdateError); // FIXME: unused return value - tDialog.execWithTask(updateTask.get()); + tDialog.execWithTask(task.get()); updateButtons(); m_container->refreshContainer(); } diff --git a/launcher/ui/setupwizard/AutoJavaWizardPage.cpp b/launcher/ui/setupwizard/AutoJavaWizardPage.cpp new file mode 100644 index 000000000..fd173e71d --- /dev/null +++ b/launcher/ui/setupwizard/AutoJavaWizardPage.cpp @@ -0,0 +1,33 @@ +#include "AutoJavaWizardPage.h" +#include "ui_AutoJavaWizardPage.h" + +#include "Application.h" + +AutoJavaWizardPage::AutoJavaWizardPage(QWidget* parent) : BaseWizardPage(parent), ui(new Ui::AutoJavaWizardPage) +{ + ui->setupUi(this); +} + +AutoJavaWizardPage::~AutoJavaWizardPage() +{ + delete ui; +} + +void AutoJavaWizardPage::initializePage() {} + +bool AutoJavaWizardPage::validatePage() +{ + auto s = APPLICATION->settings(); + + if (!ui->previousSettingsRadioButton->isChecked()) { + s->set("AutomaticJavaSwitch", true); + s->set("AutomaticJavaDownload", true); + } + s->set("UserAskedAboutAutomaticJavaDownload", true); + return true; +} + +void AutoJavaWizardPage::retranslate() +{ + ui->retranslateUi(this); +} diff --git a/launcher/ui/setupwizard/AutoJavaWizardPage.h b/launcher/ui/setupwizard/AutoJavaWizardPage.h new file mode 100644 index 000000000..fcdf5bdf1 --- /dev/null +++ b/launcher/ui/setupwizard/AutoJavaWizardPage.h @@ -0,0 +1,22 @@ +#pragma once +#include +#include "BaseWizardPage.h" + +namespace Ui { +class AutoJavaWizardPage; +} + +class AutoJavaWizardPage : public BaseWizardPage { + Q_OBJECT + + public: + explicit AutoJavaWizardPage(QWidget* parent = nullptr); + ~AutoJavaWizardPage(); + + void initializePage() override; + bool validatePage() override; + void retranslate() override; + + private: + Ui::AutoJavaWizardPage* ui; +}; diff --git a/launcher/ui/setupwizard/AutoJavaWizardPage.ui b/launcher/ui/setupwizard/AutoJavaWizardPage.ui new file mode 100644 index 000000000..bd72cf695 --- /dev/null +++ b/launcher/ui/setupwizard/AutoJavaWizardPage.ui @@ -0,0 +1,93 @@ + + + AutoJavaWizardPage + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + + <html><head/><body><p><span style=" font-size:14pt; font-weight:600;">New Feature Alert!</span></p></body></html> + + + Qt::RichText + + + true + + + + + + + We've added a feature to automatically download the correct Java version for each version of Minecraft(this can be changed in the Java Settings). Would you like to enable or disable this feature? + + + true + + + + + + + Qt::Horizontal + + + + + + + Enable Auto-Download + + + true + + + buttonGroup + + + + + + + Disable Auto-Download + + + false + + + buttonGroup + + + + + + + Qt::Vertical + + + + 20 + 156 + + + + + + + + + + + + diff --git a/launcher/ui/setupwizard/JavaWizardPage.cpp b/launcher/ui/setupwizard/JavaWizardPage.cpp index a47cebcaa..47718d6da 100644 --- a/launcher/ui/setupwizard/JavaWizardPage.cpp +++ b/launcher/ui/setupwizard/JavaWizardPage.cpp @@ -55,6 +55,7 @@ bool JavaWizardPage::validatePage() auto result = m_java_widget->validate(); settings->set("AutomaticJavaSwitch", m_java_widget->autoDetectJava()); settings->set("AutomaticJavaDownload", m_java_widget->autoDownloadJava()); + settings->set("UserAskedAboutAutomaticJavaDownload", true); switch (result) { default: case JavaSettingsWidget::ValidationStatus::Bad: { @@ -82,7 +83,6 @@ void JavaWizardPage::retranslate() { setTitle(tr("Java")); setSubTitle( - tr("You do not have a working Java set up yet or it went missing.\n" - "Please select one of the following or browse for a Java executable.")); + tr("Please select how much memory to allocate to instances and if Prism Launcher should manage java automatically or manually.")); m_java_widget->retranslate(); } diff --git a/launcher/ui/setupwizard/JavaWizardPage.h b/launcher/ui/setupwizard/JavaWizardPage.h index dde765f27..9bf9cfa2b 100644 --- a/launcher/ui/setupwizard/JavaWizardPage.h +++ b/launcher/ui/setupwizard/JavaWizardPage.h @@ -9,7 +9,7 @@ class JavaWizardPage : public BaseWizardPage { public: explicit JavaWizardPage(QWidget* parent = Q_NULLPTR); - virtual ~JavaWizardPage() {}; + virtual ~JavaWizardPage() = default; bool wantsRefreshButton() override; void refresh() override; diff --git a/launcher/ui/setupwizard/LoginWizardPage.cpp b/launcher/ui/setupwizard/LoginWizardPage.cpp new file mode 100644 index 000000000..6be24a2f7 --- /dev/null +++ b/launcher/ui/setupwizard/LoginWizardPage.cpp @@ -0,0 +1,45 @@ +#include "LoginWizardPage.h" +#include "minecraft/auth/AccountList.h" +#include "ui/dialogs/MSALoginDialog.h" +#include "ui_LoginWizardPage.h" + +#include "Application.h" + +LoginWizardPage::LoginWizardPage(QWidget* parent) : BaseWizardPage(parent), ui(new Ui::LoginWizardPage) +{ + ui->setupUi(this); +} + +LoginWizardPage::~LoginWizardPage() +{ + delete ui; +} + +void LoginWizardPage::initializePage() {} + +bool LoginWizardPage::validatePage() +{ + return true; +} + +void LoginWizardPage::retranslate() +{ + ui->retranslateUi(this); +} + +void LoginWizardPage::on_pushButton_clicked() +{ + wizard()->hide(); + auto account = MSALoginDialog::newAccount(nullptr); + wizard()->show(); + if (account) { + APPLICATION->accounts()->addAccount(account); + APPLICATION->accounts()->setDefaultAccount(account); + } + + if (wizard()->currentId() == wizard()->pageIds().last()) { + wizard()->accept(); + } else { + wizard()->next(); + } +} diff --git a/launcher/ui/setupwizard/LoginWizardPage.h b/launcher/ui/setupwizard/LoginWizardPage.h new file mode 100644 index 000000000..af71fc183 --- /dev/null +++ b/launcher/ui/setupwizard/LoginWizardPage.h @@ -0,0 +1,24 @@ +#pragma once +#include +#include "BaseWizardPage.h" + +namespace Ui { +class LoginWizardPage; +} + +class LoginWizardPage : public BaseWizardPage { + Q_OBJECT + + public: + explicit LoginWizardPage(QWidget* parent = nullptr); + ~LoginWizardPage(); + + void initializePage() override; + bool validatePage() override; + void retranslate() override; + private slots: + void on_pushButton_clicked(); + + private: + Ui::LoginWizardPage* ui; +}; diff --git a/launcher/ui/setupwizard/LoginWizardPage.ui b/launcher/ui/setupwizard/LoginWizardPage.ui new file mode 100644 index 000000000..191316c4e --- /dev/null +++ b/launcher/ui/setupwizard/LoginWizardPage.ui @@ -0,0 +1,74 @@ + + + LoginWizardPage + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + + <html><head/><body><p><span style=" font-size:14pt; font-weight:600;">Add Microsoft account</span></p></body></html> + + + Qt::RichText + + + true + + + + + + + In order to play Minecraft, you must have at least one Microsoft account logged in. Do you want to log in now? + + + true + + + + + + + Qt::Horizontal + + + + + + + Add Microsoft account + + + + + + + Qt::Vertical + + + + 20 + 156 + + + + + + + + + + + + diff --git a/launcher/ui/widgets/JavaSettingsWidget.cpp b/launcher/ui/widgets/JavaSettingsWidget.cpp index 8e2d94556..2b270c482 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.cpp +++ b/launcher/ui/widgets/JavaSettingsWidget.cpp @@ -3,9 +3,11 @@ #include #include #include +#include #include #include #include +#include #include #include #include @@ -34,11 +36,13 @@ JavaSettingsWidget::JavaSettingsWidget(QWidget* parent) : QWidget(parent) goodIcon = APPLICATION->getThemedIcon("status-good"); yellowIcon = APPLICATION->getThemedIcon("status-yellow"); badIcon = APPLICATION->getThemedIcon("status-bad"); + m_memoryTimer = new QTimer(this); setupUi(); - connect(m_minMemSpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryValueChanged(int))); - connect(m_maxMemSpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryValueChanged(int))); - connect(m_permGenSpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryValueChanged(int))); + connect(m_minMemSpinBox, SIGNAL(valueChanged(int)), this, SLOT(onSpinBoxValueChanged(int))); + connect(m_maxMemSpinBox, SIGNAL(valueChanged(int)), this, SLOT(onSpinBoxValueChanged(int))); + connect(m_permGenSpinBox, SIGNAL(valueChanged(int)), this, SLOT(onSpinBoxValueChanged(int))); + connect(m_memoryTimer, &QTimer::timeout, this, &JavaSettingsWidget::memoryValueChanged); connect(m_versionWidget, &VersionSelectWidget::selectedVersionChanged, this, &JavaSettingsWidget::javaVersionSelected); connect(m_javaBrowseBtn, &QPushButton::clicked, this, &JavaSettingsWidget::on_javaBrowseBtn_clicked); connect(m_javaPathTextBox, &QLineEdit::textEdited, this, &JavaSettingsWidget::javaPathEdited); @@ -55,7 +59,6 @@ void JavaSettingsWidget::setupUi() m_verticalLayout->setObjectName(QStringLiteral("verticalLayout")); m_versionWidget = new VersionSelectWidget(this); - m_verticalLayout->addWidget(m_versionWidget); m_horizontalLayout = new QHBoxLayout(); m_horizontalLayout->setObjectName(QStringLiteral("horizontalLayout")); @@ -73,8 +76,6 @@ void JavaSettingsWidget::setupUi() m_javaStatusBtn->setIcon(yellowIcon); m_horizontalLayout->addWidget(m_javaStatusBtn); - m_verticalLayout->addLayout(m_horizontalLayout); - m_memoryGroupBox = new QGroupBox(this); m_memoryGroupBox->setObjectName(QStringLiteral("memoryGroupBox")); m_gridLayout_2 = new QGridLayout(m_memoryGroupBox); @@ -136,8 +137,6 @@ void JavaSettingsWidget::setupUi() m_horizontalBtnLayout->addWidget(m_javaDownloadBtn); } - m_verticalLayout->addLayout(m_horizontalBtnLayout); - m_autoJavaGroupBox = new QGroupBox(this); m_autoJavaGroupBox->setObjectName(QStringLiteral("autoJavaGroupBox")); m_veriticalJavaLayout = new QVBoxLayout(m_autoJavaGroupBox); @@ -145,21 +144,42 @@ void JavaSettingsWidget::setupUi() m_autodetectJavaCheckBox = new QCheckBox(m_autoJavaGroupBox); m_autodetectJavaCheckBox->setObjectName("autodetectJavaCheckBox"); + m_autodetectJavaCheckBox->setChecked(true); m_veriticalJavaLayout->addWidget(m_autodetectJavaCheckBox); if (BuildConfig.JAVA_DOWNLOADER_ENABLED) { m_autodownloadCheckBox = new QCheckBox(m_autoJavaGroupBox); m_autodownloadCheckBox->setObjectName("autodownloadCheckBox"); - m_autodownloadCheckBox->setEnabled(false); + m_autodownloadCheckBox->setEnabled(m_autodetectJavaCheckBox->isChecked()); m_veriticalJavaLayout->addWidget(m_autodownloadCheckBox); connect(m_autodetectJavaCheckBox, &QCheckBox::stateChanged, this, [this] { m_autodownloadCheckBox->setEnabled(m_autodetectJavaCheckBox->isChecked()); if (!m_autodetectJavaCheckBox->isChecked()) m_autodownloadCheckBox->setChecked(false); }); + + connect(m_autodownloadCheckBox, &QCheckBox::stateChanged, this, [this] { + auto isChecked = m_autodownloadCheckBox->isChecked(); + m_versionWidget->setVisible(!isChecked); + m_javaStatusBtn->setVisible(!isChecked); + m_javaBrowseBtn->setVisible(!isChecked); + m_javaPathTextBox->setVisible(!isChecked); + m_javaDownloadBtn->setVisible(!isChecked); + if (!isChecked) { + m_verticalLayout->removeItem(m_verticalSpacer); + } else { + m_verticalLayout->addSpacerItem(m_verticalSpacer); + } + }); } m_verticalLayout->addWidget(m_autoJavaGroupBox); + m_verticalLayout->addLayout(m_horizontalBtnLayout); + + m_verticalLayout->addWidget(m_versionWidget); + m_verticalLayout->addLayout(m_horizontalLayout); + m_verticalSpacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding); + retranslate(); } @@ -177,23 +197,16 @@ void JavaSettingsWidget::initialize() m_maxMemSpinBox->setValue(observedMaxMemory); m_permGenSpinBox->setValue(observedPermGenMemory); updateThresholds(); - if (BuildConfig.JAVA_DOWNLOADER_ENABLED) { - auto button = - CustomMessageBox::selectable(this, tr("Automatic Java Download"), - tr("%1 can automatically download the correct Java version for each version of Minecraft..\n" - "Do you want to enable Java auto-download?\n") - .arg(BuildConfig.LAUNCHER_DISPLAYNAME), - QMessageBox::Question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) - ->exec(); - auto checked = button == QMessageBox::Yes; - m_autodetectJavaCheckBox->setChecked(checked); - m_autodownloadCheckBox->setChecked(checked); + m_autodownloadCheckBox->setChecked(true); } } void JavaSettingsWidget::refresh() { + if (BuildConfig.JAVA_DOWNLOADER_ENABLED && m_autodownloadCheckBox->isChecked()) { + return; + } if (JavaUtils::getJavaCheckPath().isEmpty()) { JavaCommon::javaCheckNotFound(this); return; @@ -297,20 +310,21 @@ int JavaSettingsWidget::permGenSize() const return m_permGenSpinBox->value(); } -void JavaSettingsWidget::memoryValueChanged(int) +void JavaSettingsWidget::memoryValueChanged() { bool actuallyChanged = false; unsigned int min = m_minMemSpinBox->value(); unsigned int max = m_maxMemSpinBox->value(); unsigned int permgen = m_permGenSpinBox->value(); - QObject* obj = sender(); - if (obj == m_minMemSpinBox && min != observedMinMemory) { + if (min != observedMinMemory) { observedMinMemory = min; actuallyChanged = true; - } else if (obj == m_maxMemSpinBox && max != observedMaxMemory) { + } + if (max != observedMaxMemory) { observedMaxMemory = max; actuallyChanged = true; - } else if (obj == m_permGenSpinBox && permgen != observedPermGenMemory) { + } + if (permgen != observedPermGenMemory) { observedPermGenMemory = permgen; actuallyChanged = true; } @@ -505,6 +519,9 @@ void JavaSettingsWidget::updateThresholds() } else if (observedMaxMemory < observedMinMemory) { iconName = "status-yellow"; m_labelMaxMemIcon->setToolTip(tr("Your maximum memory allocation is smaller than the minimum value")); + } else if (BuildConfig.JAVA_DOWNLOADER_ENABLED && m_autodownloadCheckBox->isChecked()) { + iconName = "status-good"; + m_labelMaxMemIcon->setToolTip(""); } else if (observedMaxMemory > 2048 && !m_result.is_64bit) { iconName = "status-bad"; m_labelMaxMemIcon->setToolTip(tr("You are exceeding the maximum allocation supported by 32-bit installations of Java.")); @@ -530,3 +547,13 @@ bool JavaSettingsWidget::autoDetectJava() const { return m_autodetectJavaCheckBox->isChecked(); } + +void JavaSettingsWidget::onSpinBoxValueChanged(int) +{ + m_memoryTimer->start(500); +} + +JavaSettingsWidget::~JavaSettingsWidget() +{ + delete m_verticalSpacer; +}; \ No newline at end of file diff --git a/launcher/ui/widgets/JavaSettingsWidget.h b/launcher/ui/widgets/JavaSettingsWidget.h index 622c473fe..35be48478 100644 --- a/launcher/ui/widgets/JavaSettingsWidget.h +++ b/launcher/ui/widgets/JavaSettingsWidget.h @@ -17,6 +17,7 @@ class QGroupBox; class QGridLayout; class QLabel; class QToolButton; +class QSpacerItem; /** * This is a widget for all the Java settings dialogs and pages. @@ -26,7 +27,7 @@ class JavaSettingsWidget : public QWidget { public: explicit JavaSettingsWidget(QWidget* parent); - virtual ~JavaSettingsWidget() = default; + virtual ~JavaSettingsWidget(); enum class JavaStatus { NotSet, Pending, Good, DoesNotExist, DoesNotStart, ReturnedInvalidData } javaStatus = JavaStatus::NotSet; @@ -48,7 +49,8 @@ class JavaSettingsWidget : public QWidget { void updateThresholds(); protected slots: - void memoryValueChanged(int); + void onSpinBoxValueChanged(int); + void memoryValueChanged(); void javaPathEdited(const QString& path); void javaVersionSelected(BaseVersion::Ptr version); void on_javaBrowseBtn_clicked(); @@ -65,6 +67,7 @@ class JavaSettingsWidget : public QWidget { private: /* data */ VersionSelectWidget* m_versionWidget = nullptr; QVBoxLayout* m_verticalLayout = nullptr; + QSpacerItem* m_verticalSpacer = nullptr; QLineEdit* m_javaPathTextBox = nullptr; QPushButton* m_javaBrowseBtn = nullptr; @@ -99,4 +102,5 @@ class JavaSettingsWidget : public QWidget { uint64_t m_availableMemory = 0ull; shared_qobject_ptr m_checker; JavaChecker::Result m_result; + QTimer* m_memoryTimer; };