diff --git a/loader/include/Geode/loader/Mod.hpp b/loader/include/Geode/loader/Mod.hpp index 211615ed..0c006103 100644 --- a/loader/include/Geode/loader/Mod.hpp +++ b/loader/include/Geode/loader/Mod.hpp @@ -41,7 +41,8 @@ namespace geode { Enable, Disable, Uninstall, - UninstallWithSaveData + UninstallWithSaveData, + Update }; static constexpr bool modRequestedActionIsToggle(ModRequestedAction action) { diff --git a/loader/include/Geode/loader/ModMetadata.hpp b/loader/include/Geode/loader/ModMetadata.hpp index 435c4fca..af091d30 100644 --- a/loader/include/Geode/loader/ModMetadata.hpp +++ b/loader/include/Geode/loader/ModMetadata.hpp @@ -198,9 +198,19 @@ namespace geode { /** * Checks if mod can be installed on the current GD version. - * Returns Ok() if it can, Err otherwise. + * Returns Ok() if it can, Err explaining why not otherwise. */ Result<> checkGameVersion() const; + /** + * Checks if mod can be installed on the current Geode version. + * Returns Ok() if it can, Err explaining why not otherwise. + */ + Result<> checkGeodeVersion() const; + /** + * Checks if mod can be installed on the current GD & Geode version. + * Returns Ok() if it can, Err explaining why not otherwise. + */ + Result<> checkTargetVersions() const; #if defined(GEODE_EXPOSE_SECRET_INTERNALS_IN_HEADERS_DO_NOT_DEFINE_PLEASE) void setPath(std::filesystem::path const& value); diff --git a/loader/include/Geode/ui/GeodeUI.hpp b/loader/include/Geode/ui/GeodeUI.hpp index 32dbf3bd..0ac51b9b 100644 --- a/loader/include/Geode/ui/GeodeUI.hpp +++ b/loader/include/Geode/ui/GeodeUI.hpp @@ -155,6 +155,10 @@ namespace geode { * Create a logo sprite for a mod */ GEODE_DLL cocos2d::CCNode* createModLogo(Mod* mod); + /** + * Create a logo sprite for a mod from a .geode file + */ + GEODE_DLL cocos2d::CCNode* createModLogo(std::filesystem::path const& geodePackage); /** * Create a logo sprite for a mod downloaded from the Geode servers. The * logo is initially a loading circle, with the actual sprite downloaded diff --git a/loader/resources/file-add.png b/loader/resources/file-add.png new file mode 100644 index 00000000..bae8b7d4 Binary files /dev/null and b/loader/resources/file-add.png differ diff --git a/loader/src/loader/LoaderImpl.cpp b/loader/src/loader/LoaderImpl.cpp index 65ca49cc..c780221f 100644 --- a/loader/src/loader/LoaderImpl.cpp +++ b/loader/src/loader/LoaderImpl.cpp @@ -29,6 +29,9 @@ #include #include +#include +#include + using namespace geode::prelude; Loader::Impl* LoaderImpl::get() { @@ -405,17 +408,16 @@ void Loader::Impl::loadModGraph(Mod* node, bool early) { return; } - if (!this->isModVersionSupported(node->getMetadata().getGeodeVersion())) { + auto geodeVerRes = node->getMetadata().checkGeodeVersion(); + if (!geodeVerRes) { this->addProblem({ - node->getMetadata().getGeodeVersion() > this->getVersion() ? LoadProblem::Type::NeedsNewerGeodeVersion : LoadProblem::Type::UnsupportedGeodeVersion, + node->getMetadata().getGeodeVersion() > this->getVersion() ? + LoadProblem::Type::NeedsNewerGeodeVersion : + LoadProblem::Type::UnsupportedGeodeVersion, node, - fmt::format( - "Geode version {}\nis required to run this mod\n(installed: {})", - node->getMetadata().getGeodeVersion().toVString(), - this->getVersion().toVString() - ) + geodeVerRes.unwrapErr() }); - log::error("Unsupported Geode version: {}", node->getMetadata().getGeodeVersion()); + log::error("{}", geodeVerRes.unwrapErr()); log::popNest(); return; } @@ -977,4 +979,171 @@ bool Loader::Impl::isSafeMode() const { void Loader::Impl::forceSafeMode() { m_forceSafeMode = true; -} \ No newline at end of file +} + +void Loader::Impl::installModManuallyFromFile(std::filesystem::path const& path, std::function after) { + auto res = ModMetadata::createFromGeodeFile(path); + if (!res) { + FLAlertLayer::create( + "Invalid File", + fmt::format( + "The path '{}' is not a valid Geode mod: {}", + path.string(), + res.unwrapErr() + ), + "OK" + )->show(); + return; + } + auto meta = res.unwrap(); + + auto check = meta.checkTargetVersions(); + if (!check) { + FLAlertLayer::create( + "Invalid Mod Version", + fmt::format( + "The mod {} can not be installed: {}", + meta.getID(), + check.unwrapErr() + ), + "OK" + )->show(); + } + + auto doInstallModFromFile = [this, path, meta, after]() { + std::error_code ec; + + static size_t MAX_ATTEMPTS = 10; + + // Figure out a free path to install to + auto installTo = dirs::getModsDir() / fmt::format("{}.geode", meta.getID()); + size_t counter = 0; + while (std::filesystem::exists(installTo, ec) && counter < MAX_ATTEMPTS) { + installTo = dirs::getModsDir() / fmt::format("{}-{}.geode", meta.getID(), counter); + counter += 1; + } + + // This is incredibly unlikely but theoretically possible + if (counter >= MAX_ATTEMPTS) { + FLAlertLayer::create( + "Unable to Install", + fmt::format( + "Unable to install mod {}: Can't find a free filename!", + meta.getID() + ), + "OK" + )->show(); + return; + } + + // Actually copy the file over to the install directory + std::filesystem::copy_file(path, installTo, ec); + if (ec) { + FLAlertLayer::create( + "Unable to Install", + fmt::format( + "Unable to install mod {}: {} (Error code {})", + meta.getID(), ec.message(), ec.value() + ), + "OK" + )->show(); + return; + } + + // Mark an updated mod as updated or add to the mods list + if (m_mods.contains(meta.getID())) { + m_mods.at(meta.getID())->m_impl->m_requestedAction = ModRequestedAction::Update; + } + // Otherwise add a new Mod + // This should be safe as all of the scary stuff in setup() is only relevant + // for mods that are actually running + else { + auto mod = new Mod(meta); + auto res = mod->m_impl->setup(); + if (!res) { + log::error("Unable to set up manually installed mod: {}", res.unwrapErr()); + } + (void)mod->enable(); + m_mods.insert({ meta.getID(), mod }); + } + + if (after) after(); + + // No need for the user to go and manually clean up the file + createQuickPopup( + "Mod Installed", + fmt::format( + "Mod {} has been succesfully installed from file! " + "Do you want to delete the original file?", + meta.getName() + ), + "OK", "Delete File", + [path](auto, bool btn2) { + if (btn2) { + std::error_code ec; + std::filesystem::remove(path, ec); + if (ec) { + FLAlertLayer::create( + "Unable to Delete", + fmt::format( + "Unable to delete {}: {} (Error code {})", + path, ec.message(), ec.value() + ), + "OK" + )->show(); + } + // No need to show a confirmation popup if succesful since that's + // to be assumed via pressing the button on the previous popup + } + } + ); + }; + + if (auto existing = Loader::get()->getInstalledMod(meta.getID())) { + createQuickPopup( + "Already Installed", + fmt::format( + "The mod {} v{} has already been installed " + "as version {}. Do you want to replace the " + "installed version with the file?", + meta.getID(), meta.getVersion(), + existing->getVersion() + ), + "Cancel", "Replace", + [doInstallModFromFile, path, existing, meta](auto, bool btn2) mutable { + std::error_code ec; + std::filesystem::remove(existing->getPackagePath(), ec); + if (ec) { + FLAlertLayer::create( + "Unable to Uninstall", + fmt::format( + "Unable to uninstall {}: {} (Error code {})", + existing->getID(), ec.message(), ec.value() + ), + "OK" + )->show(); + return; + } + doInstallModFromFile(); + } + ); + return; + } + + doInstallModFromFile(); +} + +bool Loader::Impl::isRestartRequired() const { + for (auto mod : Loader::get()->getAllMods()) { + if (mod->getRequestedAction() != ModRequestedAction::None) { + return true; + } + if (ModSettingsManager::from(mod)->restartRequired()) { + return true; + } + } + if (server::ModDownloadManager::get()->wantsRestart()) { + return true; + } + return false; +} diff --git a/loader/src/loader/LoaderImpl.hpp b/loader/src/loader/LoaderImpl.hpp index b44b5d48..928dd7ca 100644 --- a/loader/src/loader/LoaderImpl.hpp +++ b/loader/src/loader/LoaderImpl.hpp @@ -138,6 +138,12 @@ namespace geode { bool isSafeMode() const; // enables safe mode, even if the launch arg wasnt provided void forceSafeMode(); + + // This will potentially start a whole sequence of popups that guide the + // user through installing the specific .geode file + void installModManuallyFromFile(std::filesystem::path const& path, std::function after); + + bool isRestartRequired() const; }; class LoaderImpl : public Loader::Impl { diff --git a/loader/src/loader/ModImpl.cpp b/loader/src/loader/ModImpl.cpp index 04b45487..acd09801 100644 --- a/loader/src/loader/ModImpl.cpp +++ b/loader/src/loader/ModImpl.cpp @@ -30,6 +30,7 @@ static constexpr const char* humanReadableDescForAction(ModRequestedAction actio case ModRequestedAction::Disable: return "Mod has been disabled"; case ModRequestedAction::Uninstall: return "Mod has been uninstalled"; case ModRequestedAction::UninstallWithSaveData: return "Mod has been uninstalled"; + case ModRequestedAction::Update: return "Mod has been updated"; } } diff --git a/loader/src/loader/ModMetadataImpl.cpp b/loader/src/loader/ModMetadataImpl.cpp index 8c048b8d..1b4b1cac 100644 --- a/loader/src/loader/ModMetadataImpl.cpp +++ b/loader/src/loader/ModMetadataImpl.cpp @@ -572,6 +572,29 @@ Result<> ModMetadata::checkGameVersion() const { } return Ok(); } +Result<> ModMetadata::checkGeodeVersion() const { + if (!LoaderImpl::get()->isModVersionSupported(m_impl->m_geodeVersion)) { + auto current = LoaderImpl::get()->getVersion(); + if (m_impl->m_geodeVersion > current) { + return Err( + "This mod was made for a newer version of Geode ({}). You currently have version {}.", + m_impl->m_geodeVersion, current + ); + } + else { + return Err( + "This mod was made for an older version of Geode ({}). You currently have version {}.", + m_impl->m_geodeVersion, current + ); + } + } + return Ok(); +} +Result<> ModMetadata::checkTargetVersions() const { + GEODE_UNWRAP(this->checkGameVersion()); + GEODE_UNWRAP(this->checkGeodeVersion()); + return Ok(); +} #if defined(GEODE_EXPOSE_SECRET_INTERNALS_IN_HEADERS_DO_NOT_DEFINE_PLEASE) void ModMetadata::setPath(std::filesystem::path const& value) { diff --git a/loader/src/server/DownloadManager.cpp b/loader/src/server/DownloadManager.cpp index 573881e9..545fe3ed 100644 --- a/loader/src/server/DownloadManager.cpp +++ b/loader/src/server/DownloadManager.cpp @@ -4,6 +4,7 @@ #include #include #include +#include using namespace server; @@ -124,6 +125,8 @@ public: .details = fmt::format("Unable to delete existing .geode package (code {})", ec), }; } + // Mark mod as updated + ModImpl::getImpl(mod)->m_requestedAction = ModRequestedAction::Update; } // If this was an update, delete the old file first if (!removingInstalledWasError) { diff --git a/loader/src/ui/GeodeUI.cpp b/loader/src/ui/GeodeUI.cpp index a7621074..c730699b 100644 --- a/loader/src/ui/GeodeUI.cpp +++ b/loader/src/ui/GeodeUI.cpp @@ -166,40 +166,55 @@ Popup* geode::openSettingsPopup(Mod* mod, bool disableGeodeTheme) { return nullptr; } +using ModLogoSrc = std::variant; + class ModLogoSprite : public CCNode { protected: std::string m_modID; CCNode* m_sprite = nullptr; EventListener> m_listener; - bool init(std::string const& id, bool fetch) { + bool init(ModLogoSrc&& src) { if (!CCNode::init()) return false; this->setAnchorPoint({ .5f, .5f }); this->setContentSize({ 50, 50 }); - // This is a default ID, nothing should ever rely on the ID of any ModLogoSprite being this - this->setID(std::string(Mod::get()->expandSpriteName(fmt::format("sprite-{}", id)))); - - m_modID = id; m_listener.bind(this, &ModLogoSprite::onFetch); + + std::visit(makeVisitor { + [this](Mod* mod) { + m_modID = mod->getID(); - // Load from Resources - if (!fetch) { - this->setSprite(id == "geode.loader" ? - CCSprite::createWithSpriteFrameName("geode-logo.png"_spr) : - CCSprite::create(fmt::format("{}/logo.png", id).c_str()), - false - ); - } - // Asynchronously fetch from server - else { - this->setSprite(createLoadingCircle(25), false); - m_listener.setFilter(server::getModLogo(id)); - } + // Load from Resources + this->setSprite(mod->isInternal() ? + CCSprite::createWithSpriteFrameName("geode-logo.png"_spr) : + CCSprite::create(fmt::format("{}/logo.png", mod->getID()).c_str()), + false + ); + }, + [this](std::string const& id) { + m_modID = id; + + // Asynchronously fetch from server + this->setSprite(createLoadingCircle(25), false); + m_listener.setFilter(server::getModLogo(id)); + }, + [this](std::filesystem::path const& path) { + this->setSprite(nullptr, false); + if (auto unzip = file::Unzip::create(path)) { + if (auto logo = unzip.unwrap().extract("logo.png")) { + this->setSprite(std::move(logo.unwrap()), false); + } + } + }, + }, src); - ModLogoUIEvent(std::make_unique(this, id)).post(); + // This is a default ID, nothing should ever rely on the ID of any ModLogoSprite being this + this->setID(std::string(Mod::get()->expandSpriteName(fmt::format("sprite-{}", m_modID)))); + + ModLogoUIEvent(std::make_unique(this, m_modID)).post(); return true; } @@ -224,6 +239,13 @@ protected: ModLogoUIEvent(std::make_unique(this, m_modID)).post(); } } + void setSprite(ByteVector&& data, bool postEvent) { + auto image = Ref(new CCImage()); + image->initWithImageData(data.data(), data.size()); + + auto texture = CCTextureCache::get()->addUIImage(image, m_modID.c_str()); + this->setSprite(CCSprite::createWithTexture(texture), postEvent); + } void onFetch(server::ServerRequest::Event* event) { if (auto result = event->getValue()) { @@ -233,12 +255,7 @@ protected: } // Otherwise load downloaded sprite to memory else { - auto data = result->unwrap(); - auto image = Ref(new CCImage()); - image->initWithImageData(data.data(), data.size()); - - auto texture = CCTextureCache::get()->addUIImage(image, m_modID.c_str()); - this->setSprite(CCSprite::createWithTexture(texture), true); + this->setSprite(std::move(result->unwrap()), true); } } else if (event->isCancelled()) { @@ -247,9 +264,9 @@ protected: } public: - static ModLogoSprite* create(std::string const& id, bool fetch = false) { + static ModLogoSprite* create(ModLogoSrc&& src) { auto ret = new ModLogoSprite(); - if (ret->init(id, fetch)) { + if (ret->init(std::move(src))) { ret->autorelease(); return ret; } @@ -259,13 +276,17 @@ public: }; CCNode* geode::createDefaultLogo() { - return ModLogoSprite::create(""); + return ModLogoSprite::create(ModLogoSrc(nullptr)); } CCNode* geode::createModLogo(Mod* mod) { - return ModLogoSprite::create(mod->getID()); + return ModLogoSprite::create(ModLogoSrc(mod)); +} + +CCNode* geode::createModLogo(std::filesystem::path const& geodePackage) { + return ModLogoSprite::create(ModLogoSrc(geodePackage)); } CCNode* geode::createServerModLogo(std::string const& id) { - return ModLogoSprite::create(id, true); + return ModLogoSprite::create(ModLogoSrc(id)); } diff --git a/loader/src/ui/mods/ModsLayer.cpp b/loader/src/ui/mods/ModsLayer.cpp index b97bd46e..8f9b0d64 100644 --- a/loader/src/ui/mods/ModsLayer.cpp +++ b/loader/src/ui/mods/ModsLayer.cpp @@ -145,7 +145,7 @@ void ModsStatusNode::updateState() { switch (state) { // If there are no downloads happening, just show the restart button if needed case DownloadState::None: { - m_restartBtn->setVisible(ModListSource::isRestartRequired()); + m_restartBtn->setVisible(LoaderImpl::get()->isRestartRequired()); } break; // If some downloads were cancelled, show the restart button normally @@ -154,7 +154,7 @@ void ModsStatusNode::updateState() { m_status->setColor(ccWHITE); m_status->setVisible(true); - m_restartBtn->setVisible(ModListSource::isRestartRequired()); + m_restartBtn->setVisible(LoaderImpl::get()->isRestartRequired()); } break; // If all downloads were finished, show the restart button normally @@ -170,7 +170,7 @@ void ModsStatusNode::updateState() { m_status->setVisible(true); m_statusBG->setVisible(true); - m_restartBtn->setVisible(ModListSource::isRestartRequired()); + m_restartBtn->setVisible(LoaderImpl::get()->isRestartRequired()); } break; case DownloadState::SomeErrored: { @@ -274,6 +274,39 @@ void ModsLayer::onOpenModsFolder(CCObject*) { file::openFolder(dirs::getModsDir()); } +void ModsLayer::onAddModFromFile(CCObject*) { + if (!Mod::get()->setSavedValue("shown-manual-install-info", true)) { + return FLAlertLayer::create( + nullptr, + "Manually Installing Mods", + "You can manually install mods by selecting their .geode files. " + "Do note that manually installed mods are not verified to be safe and stable!\n" + "Proceed at your own risk!", + "OK", nullptr, + 350 + )->show(); + } + file::pick(file::PickMode::OpenFile, file::FilePickOptions { + .filters = { file::FilePickOptions::Filter { + .description = "Geode Mods", + .files = { "*.geode" }, + }} + }).listen([](Result* path) { + if (*path) { + LoaderImpl::get()->installModManuallyFromFile(path->unwrap(), []() { + InstalledModListSource::get(InstalledModListType::All)->clearCache(); + }); + } + else { + FLAlertLayer::create( + "Unable to Select File", + path->unwrapErr(), + "OK" + )->show(); + } + }); +} + void ModsStatusNode::onRestart(CCObject*) { // Update button state to let user know it's restarting but it might take a bit m_restartBtn->setEnabled(false); @@ -380,6 +413,20 @@ bool ModsLayer::init() { folderBtn->setID("mods-folder-button"); actionsMenu->addChild(folderBtn); + auto addSpr = createGeodeCircleButton( + CCSprite::createWithSpriteFrameName("file-add.png"_spr), 1.f, + CircleBaseSize::Medium + ); + addSpr->setScale(.8f); + addSpr->setTopRelativeScale(.8f); + auto addBtn = CCMenuItemSpriteExtra::create( + addSpr, + this, + menu_selector(ModsLayer::onAddModFromFile) + ); + addBtn->setID("mods-add-button"); + actionsMenu->addChild(addBtn); + actionsMenu->setLayout( ColumnLayout::create() ->setAxisAlignment(AxisAlignment::Start) diff --git a/loader/src/ui/mods/ModsLayer.hpp b/loader/src/ui/mods/ModsLayer.hpp index ed585239..2b2afaa0 100644 --- a/loader/src/ui/mods/ModsLayer.hpp +++ b/loader/src/ui/mods/ModsLayer.hpp @@ -76,6 +76,7 @@ protected: void onTab(CCObject* sender); void onOpenModsFolder(CCObject*); + void onAddModFromFile(CCObject*); void onBigView(CCObject*); void onSearch(CCObject*); void onGoToPage(CCObject*); diff --git a/loader/src/ui/mods/list/ModDeveloperList.cpp b/loader/src/ui/mods/list/ModDeveloperList.cpp index 6a3a62ee..0f3ae4c9 100644 --- a/loader/src/ui/mods/list/ModDeveloperList.cpp +++ b/loader/src/ui/mods/list/ModDeveloperList.cpp @@ -64,11 +64,6 @@ bool ModDeveloperList::init(DevListPopup* popup, ModSource const& source, CCSize m_list->m_contentLayer->addChild(ModDeveloperItem::create(popup, dev.username, itemSize, dev.displayName)); } }, - [this, popup, itemSize](ModSuggestion const& suggestion) { - for (std::string& dev : suggestion.suggestion.getDevelopers()) { - m_list->m_contentLayer->addChild(ModDeveloperItem::create(popup, dev, itemSize, std::nullopt, false)); - } - }, }); m_list->m_contentLayer->updateLayout(); m_list->scrollToTop(); diff --git a/loader/src/ui/mods/list/ModItem.cpp b/loader/src/ui/mods/list/ModItem.cpp index c257c3cf..9004a21c 100644 --- a/loader/src/ui/mods/list/ModItem.cpp +++ b/loader/src/ui/mods/list/ModItem.cpp @@ -284,7 +284,7 @@ bool ModItem::init(ModSource&& source) { m_recommendedBy->addChild(nameLabel); m_recommendedBy->setLayout( - RowLayout::create() + RowLayout::create() ->setDefaultScaleLimits(.1f, 1.f) ->setAxisAlignment(AxisAlignment::Start) ); @@ -390,10 +390,6 @@ void ModItem::updateState() { m_bg->setColor("mod-list-featured-color"_cc3b); m_bg->setOpacity(65); } - }, - [this](ModSuggestion const& suggestion) { - m_bg->setColor("mod-list-recommended-bg"_cc3b); - m_bg->setOpacity(isGeodeTheme() ? 25 : 90); } }); diff --git a/loader/src/ui/mods/sources/InstalledModListSource.cpp b/loader/src/ui/mods/sources/InstalledModListSource.cpp index 79b03a90..91e6930e 100644 --- a/loader/src/ui/mods/sources/InstalledModListSource.cpp +++ b/loader/src/ui/mods/sources/InstalledModListSource.cpp @@ -13,10 +13,10 @@ bool InstalledModsQuery::preCheck(ModSource const& src) const { } // If only errors requested, only show mods with errors (duh) if (type == InstalledModListType::OnlyOutdated) { - return src.asMod()->targetsOutdatedVersion().has_value(); + return src.asMod() && src.asMod()->targetsOutdatedVersion().has_value(); } if (type == InstalledModListType::OnlyErrors) { - return src.asMod()->hasLoadProblems(); + return src.asMod() && src.asMod()->hasLoadProblems(); } return true; } diff --git a/loader/src/ui/mods/sources/ModListSource.cpp b/loader/src/ui/mods/sources/ModListSource.cpp index 1c6f57b2..44900d20 100644 --- a/loader/src/ui/mods/sources/ModListSource.cpp +++ b/loader/src/ui/mods/sources/ModListSource.cpp @@ -1,6 +1,7 @@ #include "ModListSource.hpp" #include #include +#include #define FTS_FUZZY_MATCH_IMPLEMENTATION #include @@ -88,20 +89,6 @@ void ModListSource::clearAllCaches() { src->clearCache(); } } -bool ModListSource::isRestartRequired() { - for (auto mod : Loader::get()->getAllMods()) { - if (mod->getRequestedAction() != ModRequestedAction::None) { - return true; - } - if (ModSettingsManager::from(mod)->restartRequired()) { - return true; - } - } - if (server::ModDownloadManager::get()->wantsRestart()) { - return true; - } - return false; -} bool weightedFuzzyMatch(std::string const& str, std::string const& kw, double weight, double& out) { int score; diff --git a/loader/src/ui/mods/sources/ModListSource.hpp b/loader/src/ui/mods/sources/ModListSource.hpp index 46d02236..7e241a9d 100644 --- a/loader/src/ui/mods/sources/ModListSource.hpp +++ b/loader/src/ui/mods/sources/ModListSource.hpp @@ -79,7 +79,6 @@ public: void setPageSize(size_t size); static void clearAllCaches(); - static bool isRestartRequired(); }; template @@ -231,8 +230,8 @@ void filterModsWithLocalQuery(ModListSource::ProvidedMods& mods, Query const& qu return a.second > b.second; } // Make sure outdated mods are always last by default - auto aIsOutdated = a.first.getMetadata().checkGameVersion().isErr(); - auto bIsOutdated = b.first.getMetadata().checkGameVersion().isErr(); + auto aIsOutdated = a.first.getMetadata().checkTargetVersions().isErr(); + auto bIsOutdated = b.first.getMetadata().checkTargetVersions().isErr(); if (aIsOutdated != bIsOutdated) { return !aIsOutdated; } diff --git a/loader/src/ui/mods/sources/ModSource.cpp b/loader/src/ui/mods/sources/ModSource.cpp index 0860ee73..b41edee2 100644 --- a/loader/src/ui/mods/sources/ModSource.cpp +++ b/loader/src/ui/mods/sources/ModSource.cpp @@ -51,9 +51,6 @@ std::string ModSource::getID() const { [](server::ServerModMetadata const& metadata) { return metadata.id; }, - [](ModSuggestion const& suggestion) { - return suggestion.suggestion.getID(); - }, }, m_value); } ModMetadata ModSource::getMetadata() const { @@ -65,9 +62,6 @@ ModMetadata ModSource::getMetadata() const { // Versions should be guaranteed to have at least one item return metadata.versions.front().metadata; }, - [](ModSuggestion const& suggestion) { - return suggestion.suggestion; - }, }, m_value); } @@ -80,9 +74,6 @@ std::string ModSource::formatDevelopers() const { // Versions should be guaranteed to have at least one item return metadata.formatDevelopersToString(); }, - [](ModSuggestion const& suggestion) { - return ModMetadata::formatDeveloperDisplayString(suggestion.suggestion.getDevelopers()); - }, }, m_value); } @@ -94,9 +85,6 @@ CCNode* ModSource::createModLogo() const { [](server::ServerModMetadata const& metadata) { return createServerModLogo(metadata.id); }, - [](ModSuggestion const& suggestion) { - return createServerModLogo(suggestion.suggestion.getID()); - }, }, m_value); } bool ModSource::wantsRestart() const { @@ -113,9 +101,6 @@ bool ModSource::wantsRestart() const { [](server::ServerModMetadata const& metdata) { return false; }, - [](ModSuggestion const& suggestion) { - return false; - }, }, m_value); } std::optional ModSource::hasUpdates() const { @@ -133,9 +118,6 @@ ModSource ModSource::convertForPopup() const { } return ModSource(server::ServerModMetadata(metadata)); }, - [](ModSuggestion const& suggestion) { - return ModSource(ModSuggestion(suggestion)); - }, }, m_value); } @@ -148,6 +130,7 @@ server::ServerModMetadata const* ModSource::asServer() const { } server::ServerRequest> ModSource::fetchAbout() const { + // todo: write as visit if (auto mod = this->asMod()) { return server::ServerRequest>::immediate(Ok(mod->getMetadata().getDetails())); } @@ -181,12 +164,15 @@ server::ServerRequest ModSource::fetchServerInfo() co } server::ServerRequest> ModSource::fetchValidTags() const { return std::visit(makeVisitor { - [](Mod* mod) { + [](server::ServerModMetadata const& metadata) { + // Server info tags are always certain to be valid since the server has already validated them + return server::ServerRequest>::immediate(Ok(metadata.tags)); + }, + [this](auto const&) { return server::getTags().map( - [mod](auto* result) -> Result, server::ServerError> { + [modTags = this->getMetadata().getTags()](auto* result) -> Result, server::ServerError> { if (result->isOk()) { // Filter out invalid tags - auto modTags = mod->getMetadata().getTags(); auto finalTags = std::unordered_set(); for (auto& tag : modTags) { if (result->unwrap().contains(tag)) { @@ -202,14 +188,6 @@ server::ServerRequest> ModSource::fetchValidTags } ); }, - [](server::ServerModMetadata const& metadata) { - // Server info tags are always certain to be valid since the server has already validated them - return server::ServerRequest>::immediate(Ok(metadata.tags)); - }, - [](ModSuggestion const& suggestion) { - // Suggestions are also guaranteed to be valid since they come from the server - return server::ServerRequest>::immediate(Ok(suggestion.suggestion.getTags())); - }, }, m_value); } server::ServerRequest> ModSource::checkUpdates() { @@ -230,9 +208,5 @@ server::ServerRequest> ModSource::checkUp // Server mods aren't installed so you can't install updates for them return server::ServerRequest>::immediate(Ok(std::nullopt)); }, - [](ModSuggestion const& suggestion) { - // Suggestions also aren't installed so you can't install updates for them - return server::ServerRequest>::immediate(Ok(std::nullopt)); - }, }, m_value); } diff --git a/loader/src/ui/mods/sources/ModSource.hpp b/loader/src/ui/mods/sources/ModSource.hpp index aa13f3bb..909a8d56 100644 --- a/loader/src/ui/mods/sources/ModSource.hpp +++ b/loader/src/ui/mods/sources/ModSource.hpp @@ -2,6 +2,7 @@ #include #include +#include using namespace geode::prelude; @@ -23,7 +24,6 @@ public: ModSource() = default; ModSource(Mod* mod); ModSource(server::ServerModMetadata&& metadata); - ModSource(ModSuggestion&& suggestion); std::string getID() const; ModMetadata getMetadata() const;