mod issues stuff

This commit is contained in:
HJfod 2024-05-07 17:32:14 +03:00
parent 6215787ff6
commit 196d50ee9b
8 changed files with 226 additions and 68 deletions

Binary file not shown.

After

(image error) Size: 3.7 KiB

View file

@ -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 }

View file

@ -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();
}

View file

@ -0,0 +1,6 @@
#pragma once
namespace internal {
void tryAutoFixModIssues();
bool hasTriedToFixIssues();
}

View file

@ -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;

View file

@ -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();

View file

@ -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();
}
}

View file

@ -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);