From 196d50ee9b0929a70c2fcbef594bcaf85ac1b533 Mon Sep 17 00:00:00 2001 From: HJfod <60038575+HJfod@users.noreply.github.com> Date: Tue, 7 May 2024 17:32:14 +0300 Subject: [PATCH] mod issues stuff --- loader/resources/exclamation-red.png | Bin 0 -> 3828 bytes loader/src/hooks/MenuLayer.cpp | 17 ++- loader/src/internal/FixModIssues.cpp | 187 +++++++++++++++++++++++++ loader/src/internal/FixModIssues.hpp | 6 + loader/src/loader/Loader.cpp | 7 - loader/src/loader/Mod.cpp | 30 ++-- loader/src/ui/other/FixIssuesPopup.cpp | 28 ---- loader/src/ui/other/FixIssuesPopup.hpp | 19 --- 8 files changed, 226 insertions(+), 68 deletions(-) create mode 100644 loader/resources/exclamation-red.png create mode 100644 loader/src/internal/FixModIssues.cpp create mode 100644 loader/src/internal/FixModIssues.hpp delete mode 100644 loader/src/ui/other/FixIssuesPopup.cpp delete mode 100644 loader/src/ui/other/FixIssuesPopup.hpp diff --git a/loader/resources/exclamation-red.png b/loader/resources/exclamation-red.png new file mode 100644 index 0000000000000000000000000000000000000000..fa265a254187825bc41110f76b5d33549369ea2e GIT binary patch literal 3828 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 literal 0 HcmV?d00001 diff --git a/loader/src/hooks/MenuLayer.cpp b/loader/src/hooks/MenuLayer.cpp index ce1819e7..26606bdb 100644 --- a/loader/src/hooks/MenuLayer.cpp +++ b/loader/src/hooks/MenuLayer.cpp @@ -14,7 +14,6 @@ #include <loader/updater.hpp> #include <Geode/binding/ButtonSprite.hpp> #include <Geode/modify/LevelSelectLayer.hpp> -#include <ui/other/FixIssuesPopup.hpp> using namespace geode::prelude; @@ -83,7 +82,20 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> { } // show if some mods failed to load - checkLoadingIssues(this); + if (Loader::get()->getProblems().size()) { + static bool shownProblemPopup = false; + if (!shownProblemPopup) { + shownProblemPopup = true; + Notification::create("There were errors - see Geode page!", NotificationIcon::Error)->show(); + } + + auto icon = CCSprite::createWithSpriteFrameName("exclamation-red.png"_spr); + icon->setPosition(m_fields->m_geodeButton->getContentSize() - ccp(10, 10)); + icon->setID("errors-found"); + icon->setZOrder(99); + icon->setScale(.8f); + m_fields->m_geodeButton->addChild(icon); + } // show if the user tried to be naughty and load arbitrary DLLs static bool shownTriedToLoadDlls = false; @@ -154,6 +166,7 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> { auto updatesFound = result->unwrap(); if (updatesFound.size() && !m_fields->m_geodeButton->getChildByID("updates-available")) { log::info("Found updates for mods: {}!", updatesFound); + auto icon = CCSprite::createWithSpriteFrameName("updates-available.png"_spr); icon->setPosition( m_fields->m_geodeButton->getContentSize() - CCSize { 10.f, 10.f } diff --git a/loader/src/internal/FixModIssues.cpp b/loader/src/internal/FixModIssues.cpp new file mode 100644 index 00000000..71cf326a --- /dev/null +++ b/loader/src/internal/FixModIssues.cpp @@ -0,0 +1,187 @@ +#include "FixModIssues.hpp" +#include <Geode/loader/Loader.hpp> +#include <server/DownloadManager.hpp> +#include <ui/mods/sources/ModSource.hpp> + +// TODO: UNFINISHED!!! +// If you want to bring this back, you are free to do so - +// I just didn't feel like the engineering effort is worth it. +// The point of this is to be a mod load issue auto-resolver + +using namespace geode::prelude; + +class AutoFixStatus final { +protected: + struct Question final { + std::string title; + std::string content; + std::string optionA; + std::string optionB; + MiniFunction<void(bool)> after; + }; + + EventListener<server::ModDownloadFilter> m_download; + std::vector<std::string> m_unsolved; + std::deque<Question> m_questionQueue; + + static ghc::filesystem::path getPath(LoadProblem const& problem) { + return std::visit(makeVisitor { + [](ghc::filesystem::path const& path) { + return path; + }, + [](ModMetadata const& meta) { + return meta.getPath(); + }, + [](Mod* mod) { + return mod->getPackagePath(); + }, + }, problem.cause); + } + static std::string getName(LoadProblem const& problem) { + return std::visit(makeVisitor { + [](ghc::filesystem::path const& path) { + return path.string(); + }, + [](ModMetadata const& meta) { + return meta.getID(); + }, + [](Mod* mod) { + return mod->getID(); + }, + }, problem.cause); + } + + void nextQuestion() { + auto& question = m_questionQueue.front(); + createQuickPopup( + question.title.c_str(), + question.content, + question.optionA.c_str(), question.optionB.c_str(), + [this, &question](auto*, bool btn2) { + question.after(btn2); + m_questionQueue.pop_front(); + if (!m_questionQueue.empty()) { + this->nextQuestion(); + } + } + ); + } + void ask(Question&& question) { + m_questionQueue.push_back(question); + // If this was the first question in the queue, start asking + if (m_questionQueue.size() == 1) { + this->nextQuestion(); + } + } + +public: + void start() { + for (auto problem : Loader::get()->getProblems()) { + switch (problem.type) { + // Errors where the correct solution is to just delete the invalid .geode package + case LoadProblem::Type::InvalidFile: + // todo: maybe duplicate should prompt which one to delete? + // or maybe the user can just figure that one out since that only happens + // on manual install (as server installs delete the old version using the + // real path and not the old index filename trickery) + case LoadProblem::Type::Duplicate: + case LoadProblem::Type::SetupFailed: + case LoadProblem::Type::LoadFailed: + case LoadProblem::Type::EnableFailed: + case LoadProblem::Type::UnsupportedGeodeVersion: + case LoadProblem::Type::NeedsNewerGeodeVersion: + case LoadProblem::Type::UnsupportedVersion: + { + auto path = getPath(problem); + std::error_code ec; + ghc::filesystem::remove(path, ec); + if (ec) { + m_unsolved.push_back(fmt::format("Failed to delete '{}'", path)); + } + } + break; + + // Missing / bad dependencies + case LoadProblem::Type::MissingDependency: + case LoadProblem::Type::OutdatedDependency: + { + // Parse the damn "{id} {version}" string to get the id + // God I wish this was Rust so the enum variant could just have the ID right there + auto id = problem.message.substr(0, problem.message.find(' ')); + + // If this mod is already installed, see if it can be updated + if (auto mod = Loader::get()->getInstalledMod(id)) { + // todo: after update check, if there are no updates, mark this as unsolved, otherwise start update + ModSource(mod).checkUpdates(); + } + // Otherwise try to install the mod + // todo: Check if the mod can be downloaded, and if not mark this as unsolved + else { + server::ModDownloadManager::get()->startDownload(id, std::nullopt); + } + } + break; + + // Enable the dependency duh + case LoadProblem::Type::DisabledDependency: + { + auto mod = std::get<Mod*>(problem.cause); + if (!mod->enable()) { + m_unsolved.push_back(fmt::format("Failed to enable '{}'", mod->getID())); + } + } + break; + + // Incompatabilities; the user should choose which to disable + case LoadProblem::Type::PresentIncompatibility: + case LoadProblem::Type::OutdatedIncompatibility: + case LoadProblem::Type::OutdatedConflict: + case LoadProblem::Type::Conflict: + { + auto modA = std::get<Mod*>(problem.cause)->getID(); + auto modB = problem.message; + this->ask(Question { + .title = "Select Mod", + .content = fmt::format( + "The mods <cy>'{}'</c> and <cp>{}</c> are <cr>incompatible</c>.\n" + "<cj>Please select which one to disable</c>.", + modA, modB + ), + .optionA = modA, + .optionB = modB, + .after = [modA, modB](bool b) { + + } + }); + } + break; + + // Errors we can't fix, or ones where you should probably just restart the game / PC + default: + case LoadProblem::Type::UnzipFailed: + case LoadProblem::Type::Unknown: + { + auto name = getName(problem); + m_unsolved.push_back(fmt::format( + "<cr>Unknown/unsolvable error</c> with <cg>'{}'</c>: {}\n" + "<cy>Maybe try restarting your computer?</c>", + name, problem.message + )); + } + break; + } + } + } +}; + +static std::optional<AutoFixStatus> STATUS = {}; + +void internal::tryAutoFixModIssues() { + if (!STATUS) { + STATUS.emplace(AutoFixStatus()); + STATUS->start(); + } +} +bool internal::hasTriedToFixIssues() { + return STATUS.has_value(); +} diff --git a/loader/src/internal/FixModIssues.hpp b/loader/src/internal/FixModIssues.hpp new file mode 100644 index 00000000..539fe991 --- /dev/null +++ b/loader/src/internal/FixModIssues.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace internal { + void tryAutoFixModIssues(); + bool hasTriedToFixIssues(); +} diff --git a/loader/src/loader/Loader.cpp b/loader/src/loader/Loader.cpp index ce46c9ad..3f1d59e8 100644 --- a/loader/src/loader/Loader.cpp +++ b/loader/src/loader/Loader.cpp @@ -79,13 +79,6 @@ std::vector<LoadProblem> Loader::getProblems() const { } } return result; - return ranges::filter( - m_impl->getProblems(), - [](auto const& problem) { - return problem.type != LoadProblem::Type::Recommendation && - problem.type != LoadProblem::Type::Suggestion; - } - ); } std::vector<LoadProblem> Loader::getRecommendations() const { std::vector<LoadProblem> result; diff --git a/loader/src/loader/Mod.cpp b/loader/src/loader/Mod.cpp index 695663c7..e701a9bc 100644 --- a/loader/src/loader/Mod.cpp +++ b/loader/src/loader/Mod.cpp @@ -252,22 +252,28 @@ std::vector<LoadProblem> Mod::getAllProblems() const { return m_impl->getProblems(); } std::vector<LoadProblem> Mod::getProblems() const { - return ranges::filter( - this->getAllProblems(), - [](auto const& problem) { - return problem.type != LoadProblem::Type::Recommendation && - problem.type != LoadProblem::Type::Suggestion; + std::vector<LoadProblem> result; + for (auto problem : this->getAllProblems()) { + if ( + problem.type != LoadProblem::Type::Recommendation && + problem.type != LoadProblem::Type::Suggestion + ) { + result.push_back(problem); } - ); + } + return result; } std::vector<LoadProblem> Mod::getRecommendations() const { - return ranges::filter( - this->getAllProblems(), - [](auto const& problem) { - return problem.type == LoadProblem::Type::Recommendation || - problem.type == LoadProblem::Type::Suggestion; + std::vector<LoadProblem> result; + for (auto problem : this->getAllProblems()) { + if ( + problem.type == LoadProblem::Type::Recommendation || + problem.type == LoadProblem::Type::Suggestion + ) { + result.push_back(problem); } - ); + } + return result; } bool Mod::shouldLoad() const { return m_impl->shouldLoad(); diff --git a/loader/src/ui/other/FixIssuesPopup.cpp b/loader/src/ui/other/FixIssuesPopup.cpp deleted file mode 100644 index 35e7cb67..00000000 --- a/loader/src/ui/other/FixIssuesPopup.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "FixIssuesPopup.hpp" -#include <Geode/loader/Loader.hpp> - -bool FixIssuesPopup::setup() { - m_noElasticity = true; - - this->setTitle("Problems Loading Mods"); - - return true; -} - -FixIssuesPopup* FixIssuesPopup::create() { - auto ret = new FixIssuesPopup(); - if (ret && ret->init(350, 280)) { - ret->autorelease(); - return ret; - } - CC_SAFE_DELETE(ret); - return nullptr; -} - -void checkLoadingIssues(CCNode* targetScene) { - if (Loader::get()->getProblems().size()) { - auto popup = FixIssuesPopup::create(); - popup->m_scene = targetScene; - popup->show(); - } -} diff --git a/loader/src/ui/other/FixIssuesPopup.hpp b/loader/src/ui/other/FixIssuesPopup.hpp deleted file mode 100644 index 8e0bae3a..00000000 --- a/loader/src/ui/other/FixIssuesPopup.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -#include <Geode/ui/Popup.hpp> -#include "../mods/GeodeStyle.hpp" -#include "../mods/list/ModProblemItemList.hpp" - -using namespace geode::prelude; - -class FixIssuesPopup : public GeodePopup<> { -protected: - ModProblemItemList* m_list; - - bool setup() override; - -public: - static FixIssuesPopup* create(); -}; - -void checkLoadingIssues(CCNode* targetScene = nullptr);