manually installing mods from files button

This commit is contained in:
HJfod 2024-11-14 15:57:11 +02:00
parent 708e6dc7f2
commit e881dc5ef2
19 changed files with 344 additions and 107 deletions

View file

@ -41,7 +41,8 @@ namespace geode {
Enable,
Disable,
Uninstall,
UninstallWithSaveData
UninstallWithSaveData,
Update
};
static constexpr bool modRequestedActionIsToggle(ModRequestedAction action) {

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View file

@ -29,6 +29,9 @@
#include <string_view>
#include <vector>
#include <server/DownloadManager.hpp>
#include <Geode/ui/Popup.hpp>
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;
}
@ -978,3 +980,170 @@ bool Loader::Impl::isSafeMode() const {
void Loader::Impl::forceSafeMode() {
m_forceSafeMode = true;
}
void Loader::Impl::installModManuallyFromFile(std::filesystem::path const& path, std::function<void()> after) {
auto res = ModMetadata::createFromGeodeFile(path);
if (!res) {
FLAlertLayer::create(
"Invalid File",
fmt::format(
"The path <cy>'{}'</c> 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 <cy>{}</c> 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 <co>{}</c>: 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 <co>{}</c>: {} (Error code <cr>{}</c>)",
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 <co>{}</c> has been succesfully installed from file! "
"<cy>Do you want to delete the original file?</c>",
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 <cy>{}</c>: {} (Error code <cr>{}</c>)",
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 <cy>{}</c> <cj>v{}</c> has already been installed "
"as version <cl>{}</c>. Do you want to <co>replace the "
"installed version with the file</c>?",
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 <cy>{}</c>: {} (Error code <cr>{}</c>)",
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;
}

View file

@ -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<void()> after);
bool isRestartRequired() const;
};
class LoaderImpl : public Loader::Impl {

View file

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

View file

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

View file

@ -4,6 +4,7 @@
#include <Geode/utils/map.hpp>
#include <optional>
#include <hash/hash.hpp>
#include <loader/ModImpl.hpp>
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) {

View file

@ -166,40 +166,55 @@ Popup<Mod*>* geode::openSettingsPopup(Mod* mod, bool disableGeodeTheme) {
return nullptr;
}
using ModLogoSrc = std::variant<Mod*, std::string, std::filesystem::path>;
class ModLogoSprite : public CCNode {
protected:
std::string m_modID;
CCNode* m_sprite = nullptr;
EventListener<server::ServerRequest<ByteVector>> 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);
// 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));
}
std::visit(makeVisitor {
[this](Mod* mod) {
m_modID = mod->getID();
ModLogoUIEvent(std::make_unique<ModLogoUIEvent::Impl>(this, id)).post();
// 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);
// 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<ModLogoUIEvent::Impl>(this, m_modID)).post();
return true;
}
@ -224,6 +239,13 @@ protected:
ModLogoUIEvent(std::make_unique<ModLogoUIEvent::Impl>(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<ByteVector>::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));
}

View file

@ -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 <cg>manually install mods</c> by selecting their <cd>.geode</c> files. "
"Do note that manually installed mods <co>are not verified to be safe and stable</c>!\n"
"<cr>Proceed at your own risk!</c>",
"OK", nullptr,
350
)->show();
}
file::pick(file::PickMode::OpenFile, file::FilePickOptions {
.filters = { file::FilePickOptions::Filter {
.description = "Geode Mods",
.files = { "*.geode" },
}}
}).listen([](Result<std::filesystem::path>* 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)

View file

@ -76,6 +76,7 @@ protected:
void onTab(CCObject* sender);
void onOpenModsFolder(CCObject*);
void onAddModFromFile(CCObject*);
void onBigView(CCObject*);
void onSearch(CCObject*);
void onGoToPage(CCObject*);

View file

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

View file

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

View file

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

View file

@ -1,6 +1,7 @@
#include "ModListSource.hpp"
#include <server/DownloadManager.hpp>
#include <Geode/loader/ModSettingsManager.hpp>
#include <loader/LoaderImpl.hpp>
#define FTS_FUZZY_MATCH_IMPLEMENTATION
#include <Geode/external/fts/fts_fuzzy_match.h>
@ -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;

View file

@ -79,7 +79,6 @@ public:
void setPageSize(size_t size);
static void clearAllCaches();
static bool isRestartRequired();
};
template <class T>
@ -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;
}

View file

@ -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<server::ServerModUpdate> 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<std::optional<std::string>> ModSource::fetchAbout() const {
// todo: write as visit
if (auto mod = this->asMod()) {
return server::ServerRequest<std::optional<std::string>>::immediate(Ok(mod->getMetadata().getDetails()));
}
@ -181,12 +164,15 @@ server::ServerRequest<server::ServerModMetadata> ModSource::fetchServerInfo() co
}
server::ServerRequest<std::unordered_set<std::string>> 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<std::unordered_set<std::string>>::immediate(Ok(metadata.tags));
},
[this](auto const&) {
return server::getTags().map(
[mod](auto* result) -> Result<std::unordered_set<std::string>, server::ServerError> {
[modTags = this->getMetadata().getTags()](auto* result) -> Result<std::unordered_set<std::string>, server::ServerError> {
if (result->isOk()) {
// Filter out invalid tags
auto modTags = mod->getMetadata().getTags();
auto finalTags = std::unordered_set<std::string>();
for (auto& tag : modTags) {
if (result->unwrap().contains(tag)) {
@ -202,14 +188,6 @@ server::ServerRequest<std::unordered_set<std::string>> 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<std::unordered_set<std::string>>::immediate(Ok(metadata.tags));
},
[](ModSuggestion const& suggestion) {
// Suggestions are also guaranteed to be valid since they come from the server
return server::ServerRequest<std::unordered_set<std::string>>::immediate(Ok(suggestion.suggestion.getTags()));
},
}, m_value);
}
server::ServerRequest<std::optional<server::ServerModUpdate>> ModSource::checkUpdates() {
@ -230,9 +208,5 @@ server::ServerRequest<std::optional<server::ServerModUpdate>> ModSource::checkUp
// Server mods aren't installed so you can't install updates for them
return server::ServerRequest<std::optional<server::ServerModUpdate>>::immediate(Ok(std::nullopt));
},
[](ModSuggestion const& suggestion) {
// Suggestions also aren't installed so you can't install updates for them
return server::ServerRequest<std::optional<server::ServerModUpdate>>::immediate(Ok(std::nullopt));
},
}, m_value);
}

View file

@ -2,6 +2,7 @@
#include <Geode/loader/Mod.hpp>
#include <server/Server.hpp>
#include <loader/LoaderImpl.hpp>
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;