mirror of
https://github.com/geode-sdk/geode.git
synced 2025-03-24 03:39:56 -04:00
mod issues stuff
This commit is contained in:
parent
6215787ff6
commit
196d50ee9b
8 changed files with 226 additions and 68 deletions
loader
resources
src
hooks
internal
loader
ui/other
BIN
loader/resources/exclamation-red.png
Normal file
BIN
loader/resources/exclamation-red.png
Normal file
Binary file not shown.
After ![]() (image error) Size: 3.7 KiB |
|
@ -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 }
|
||||
|
|
187
loader/src/internal/FixModIssues.cpp
Normal file
187
loader/src/internal/FixModIssues.cpp
Normal 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();
|
||||
}
|
6
loader/src/internal/FixModIssues.hpp
Normal file
6
loader/src/internal/FixModIssues.hpp
Normal file
|
@ -0,0 +1,6 @@
|
|||
#pragma once
|
||||
|
||||
namespace internal {
|
||||
void tryAutoFixModIssues();
|
||||
bool hasTriedToFixIssues();
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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);
|
Loading…
Add table
Reference in a new issue