mirror of
https://github.com/geode-sdk/geode.git
synced 2024-11-14 19:15:05 -05:00
Compare commits
7 commits
d41ffec49d
...
120ea81743
Author | SHA1 | Date | |
---|---|---|---|
|
120ea81743 | ||
|
13b30e591a | ||
|
893b03e313 | ||
|
7c4e06d20c | ||
|
e881dc5ef2 | ||
|
9c61f74efd | ||
|
280b6efa94 |
30 changed files with 488 additions and 194 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -1,5 +1,16 @@
|
||||||
# Geode Changelog
|
# Geode Changelog
|
||||||
|
|
||||||
|
## v4.0.0-alpha.2
|
||||||
|
* Button to manually install mods from files (e881dc5)
|
||||||
|
* Add `ModRequestedAction::Update` (e881dc5)
|
||||||
|
* Add `ModMetadata::checkGeodeVersion` and `ModMetadata::checkTargetVersions` (e881dc5)
|
||||||
|
* Add `geode::createModLogo` for creating a logo from a `.geode` package (e881dc5)
|
||||||
|
* Tags now use names provided by the server (893b03e)
|
||||||
|
* Fix `Task::chain` using the wrong type in the impl (22a11b9)
|
||||||
|
* Fix installing mods not checking the current version (#1148)
|
||||||
|
* Fix crash when checking tags (01807fe)
|
||||||
|
* Fix 'Outdated' label being visible while updating (6679a69)
|
||||||
|
|
||||||
## v4.0.0-alpha.1
|
## v4.0.0-alpha.1
|
||||||
* Support for the 2.2074 update
|
* Support for the 2.2074 update
|
||||||
* Developers, see [this page for a migration guide](https://docs.geode-sdk.org/tutorials/migrate-v4)
|
* Developers, see [this page for a migration guide](https://docs.geode-sdk.org/tutorials/migrate-v4)
|
||||||
|
|
|
@ -41,7 +41,8 @@ namespace geode {
|
||||||
Enable,
|
Enable,
|
||||||
Disable,
|
Disable,
|
||||||
Uninstall,
|
Uninstall,
|
||||||
UninstallWithSaveData
|
UninstallWithSaveData,
|
||||||
|
Update
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr bool modRequestedActionIsToggle(ModRequestedAction action) {
|
static constexpr bool modRequestedActionIsToggle(ModRequestedAction action) {
|
||||||
|
|
|
@ -198,9 +198,19 @@ namespace geode {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if mod can be installed on the current GD version.
|
* 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;
|
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)
|
#if defined(GEODE_EXPOSE_SECRET_INTERNALS_IN_HEADERS_DO_NOT_DEFINE_PLEASE)
|
||||||
void setPath(std::filesystem::path const& value);
|
void setPath(std::filesystem::path const& value);
|
||||||
|
|
|
@ -155,6 +155,10 @@ namespace geode {
|
||||||
* Create a logo sprite for a mod
|
* Create a logo sprite for a mod
|
||||||
*/
|
*/
|
||||||
GEODE_DLL cocos2d::CCNode* createModLogo(Mod* 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
|
* Create a logo sprite for a mod downloaded from the Geode servers. The
|
||||||
* logo is initially a loading circle, with the actual sprite downloaded
|
* logo is initially a loading circle, with the actual sprite downloaded
|
||||||
|
|
|
@ -82,7 +82,8 @@ namespace geode::utils::web {
|
||||||
Result<> into(std::filesystem::path const& path) const;
|
Result<> into(std::filesystem::path const& path) const;
|
||||||
|
|
||||||
std::vector<std::string> headers() const;
|
std::vector<std::string> headers() const;
|
||||||
std::optional<std::string> header(std::string_view name, int index = 0) const;
|
std::optional<std::string> header(std::string_view name) const;
|
||||||
|
std::optional<std::vector<std::string>> headersWithName(std::string_view name) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
class GEODE_DLL WebProgress final {
|
class GEODE_DLL WebProgress final {
|
||||||
|
|
BIN
loader/resources/file-add.png
Normal file
BIN
loader/resources/file-add.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
|
@ -29,6 +29,9 @@
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include <server/DownloadManager.hpp>
|
||||||
|
#include <Geode/ui/Popup.hpp>
|
||||||
|
|
||||||
using namespace geode::prelude;
|
using namespace geode::prelude;
|
||||||
|
|
||||||
Loader::Impl* LoaderImpl::get() {
|
Loader::Impl* LoaderImpl::get() {
|
||||||
|
@ -405,17 +408,16 @@ void Loader::Impl::loadModGraph(Mod* node, bool early) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this->isModVersionSupported(node->getMetadata().getGeodeVersion())) {
|
auto geodeVerRes = node->getMetadata().checkGeodeVersion();
|
||||||
|
if (!geodeVerRes) {
|
||||||
this->addProblem({
|
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,
|
node,
|
||||||
fmt::format(
|
geodeVerRes.unwrapErr()
|
||||||
"Geode version {}\nis required to run this mod\n(installed: {})",
|
|
||||||
node->getMetadata().getGeodeVersion().toVString(),
|
|
||||||
this->getVersion().toVString()
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
log::error("Unsupported Geode version: {}", node->getMetadata().getGeodeVersion());
|
log::error("{}", geodeVerRes.unwrapErr());
|
||||||
log::popNest();
|
log::popNest();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -978,3 +980,170 @@ bool Loader::Impl::isSafeMode() const {
|
||||||
void Loader::Impl::forceSafeMode() {
|
void Loader::Impl::forceSafeMode() {
|
||||||
m_forceSafeMode = true;
|
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;
|
||||||
|
}
|
||||||
|
|
|
@ -138,6 +138,12 @@ namespace geode {
|
||||||
bool isSafeMode() const;
|
bool isSafeMode() const;
|
||||||
// enables safe mode, even if the launch arg wasnt provided
|
// enables safe mode, even if the launch arg wasnt provided
|
||||||
void forceSafeMode();
|
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 {
|
class LoaderImpl : public Loader::Impl {
|
||||||
|
|
|
@ -30,6 +30,7 @@ static constexpr const char* humanReadableDescForAction(ModRequestedAction actio
|
||||||
case ModRequestedAction::Disable: return "Mod has been disabled";
|
case ModRequestedAction::Disable: return "Mod has been disabled";
|
||||||
case ModRequestedAction::Uninstall: return "Mod has been uninstalled";
|
case ModRequestedAction::Uninstall: return "Mod has been uninstalled";
|
||||||
case ModRequestedAction::UninstallWithSaveData: return "Mod has been uninstalled";
|
case ModRequestedAction::UninstallWithSaveData: return "Mod has been uninstalled";
|
||||||
|
case ModRequestedAction::Update: return "Mod has been updated";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -572,6 +572,29 @@ Result<> ModMetadata::checkGameVersion() const {
|
||||||
}
|
}
|
||||||
return Ok();
|
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)
|
#if defined(GEODE_EXPOSE_SECRET_INTERNALS_IN_HEADERS_DO_NOT_DEFINE_PLEASE)
|
||||||
void ModMetadata::setPath(std::filesystem::path const& value) {
|
void ModMetadata::setPath(std::filesystem::path const& value) {
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#include <Geode/utils/map.hpp>
|
#include <Geode/utils/map.hpp>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <hash/hash.hpp>
|
#include <hash/hash.hpp>
|
||||||
|
#include <loader/ModImpl.hpp>
|
||||||
|
|
||||||
using namespace server;
|
using namespace server;
|
||||||
|
|
||||||
|
@ -124,6 +125,8 @@ public:
|
||||||
.details = fmt::format("Unable to delete existing .geode package (code {})", ec),
|
.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 this was an update, delete the old file first
|
||||||
if (!removingInstalledWasError) {
|
if (!removingInstalledWasError) {
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include <fmt/chrono.h>
|
#include <fmt/chrono.h>
|
||||||
#include <loader/LoaderImpl.hpp>
|
#include <loader/LoaderImpl.hpp>
|
||||||
#include "../internal/about.hpp"
|
#include "../internal/about.hpp"
|
||||||
|
#include "Geode/loader/Loader.hpp"
|
||||||
|
|
||||||
using namespace server;
|
using namespace server;
|
||||||
|
|
||||||
|
@ -245,6 +246,31 @@ std::string ServerDateTime::toAgoString() const {
|
||||||
return fmt::format("{:%b %d %Y}", value);
|
return fmt::format("{:%b %d %Y}", value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Result<ServerTag> ServerTag::parse(matjson::Value const& raw) {
|
||||||
|
auto root = checkJson(raw, "ServerTag");
|
||||||
|
auto res = ServerTag();
|
||||||
|
|
||||||
|
root.needs("id").into(res.id);
|
||||||
|
root.needs("name").into(res.name);
|
||||||
|
root.needs("display_name").into(res.displayName);
|
||||||
|
|
||||||
|
return root.ok(res);
|
||||||
|
}
|
||||||
|
Result<std::vector<ServerTag>> ServerTag::parseList(matjson::Value const& raw) {
|
||||||
|
auto payload = checkJson(raw, "ServerTagsList");
|
||||||
|
std::vector<ServerTag> list {};
|
||||||
|
for (auto& item : payload.items()) {
|
||||||
|
auto mod = ServerTag::parse(item.json());
|
||||||
|
if (mod) {
|
||||||
|
list.push_back(mod.unwrap());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
log::error("Unable to parse tag from the server: {}", mod.unwrapErr());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return payload.ok(list);
|
||||||
|
}
|
||||||
|
|
||||||
Result<ServerDateTime> ServerDateTime::parse(std::string const& str) {
|
Result<ServerDateTime> ServerDateTime::parse(std::string const& str) {
|
||||||
std::stringstream ss(str);
|
std::stringstream ss(str);
|
||||||
date::sys_seconds seconds;
|
date::sys_seconds seconds;
|
||||||
|
@ -647,7 +673,7 @@ ServerRequest<ServerModVersion> server::getModVersion(std::string const& id, Mod
|
||||||
},
|
},
|
||||||
}, version);
|
}, version);
|
||||||
|
|
||||||
return req.get(formatServerURL("/mods/{}/versions/{}", id, versionURL)).map(
|
return req.get(formatServerURL("/mods/{}/versions/{}?gd={}", id, versionURL, Loader::get()->getGameVersion())).map(
|
||||||
[](web::WebResponse* response) -> Result<ServerModVersion, ServerError> {
|
[](web::WebResponse* response) -> Result<ServerModVersion, ServerError> {
|
||||||
if (response->ok()) {
|
if (response->ok()) {
|
||||||
// Parse payload
|
// Parse payload
|
||||||
|
@ -689,33 +715,25 @@ ServerRequest<ByteVector> server::getModLogo(std::string const& id, bool useCach
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
ServerRequest<std::unordered_set<std::string>> server::getTags(bool useCache) {
|
ServerRequest<std::vector<ServerTag>> server::getTags(bool useCache) {
|
||||||
if (useCache) {
|
if (useCache) {
|
||||||
return getCache<getTags>().get();
|
return getCache<getTags>().get();
|
||||||
}
|
}
|
||||||
auto req = web::WebRequest();
|
auto req = web::WebRequest();
|
||||||
req.userAgent(getServerUserAgent());
|
req.userAgent(getServerUserAgent());
|
||||||
return req.get(formatServerURL("/tags")).map(
|
return req.get(formatServerURL("/detailed-tags")).map(
|
||||||
[](web::WebResponse* response) -> Result<std::unordered_set<std::string>, ServerError> {
|
[](web::WebResponse* response) -> Result<std::vector<ServerTag>, ServerError> {
|
||||||
if (response->ok()) {
|
if (response->ok()) {
|
||||||
// Parse payload
|
// Parse payload
|
||||||
auto payload = parseServerPayload(*response);
|
auto payload = parseServerPayload(*response);
|
||||||
if (!payload) {
|
if (!payload) {
|
||||||
return Err(payload.unwrapErr());
|
return Err(payload.unwrapErr());
|
||||||
}
|
}
|
||||||
matjson::Value json = payload.unwrap();
|
auto list = ServerTag::parseList(payload.unwrap());
|
||||||
if (!json.isArray()) {
|
if (!list) {
|
||||||
return Err(ServerError(response->code(), "Expected a string array"));
|
return Err(ServerError(response->code(), "Unable to parse response: {}", list.unwrapErr()));
|
||||||
}
|
}
|
||||||
|
return Ok(list.unwrap());
|
||||||
std::unordered_set<std::string> tags;
|
|
||||||
for (auto item : json) {
|
|
||||||
if (!item.isString()) {
|
|
||||||
return Err(ServerError(response->code(), "Expected a string array"));
|
|
||||||
}
|
|
||||||
tags.insert(item.asString().unwrap());
|
|
||||||
}
|
|
||||||
return Ok(tags);
|
|
||||||
}
|
}
|
||||||
return Err(parseServerError(*response));
|
return Err(parseServerError(*response));
|
||||||
},
|
},
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
using namespace geode::prelude;
|
using namespace geode::prelude;
|
||||||
|
|
||||||
namespace server {
|
namespace server {
|
||||||
|
// todo: replace parse()s with Serialize::fromJson now that it uses Results
|
||||||
|
|
||||||
struct ServerDateTime final {
|
struct ServerDateTime final {
|
||||||
using Clock = std::chrono::system_clock;
|
using Clock = std::chrono::system_clock;
|
||||||
using Value = std::chrono::time_point<Clock>;
|
using Value = std::chrono::time_point<Clock>;
|
||||||
|
@ -21,6 +23,15 @@ namespace server {
|
||||||
static Result<ServerDateTime> parse(std::string const& str);
|
static Result<ServerDateTime> parse(std::string const& str);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct ServerTag final {
|
||||||
|
size_t id;
|
||||||
|
std::string name;
|
||||||
|
std::string displayName;
|
||||||
|
|
||||||
|
static Result<ServerTag> parse(matjson::Value const& json);
|
||||||
|
static Result<std::vector<ServerTag>> parseList(matjson::Value const& json);
|
||||||
|
};
|
||||||
|
|
||||||
struct ServerDeveloper final {
|
struct ServerDeveloper final {
|
||||||
std::string username;
|
std::string username;
|
||||||
std::string displayName;
|
std::string displayName;
|
||||||
|
@ -147,7 +158,7 @@ namespace server {
|
||||||
ServerRequest<ServerModMetadata> getMod(std::string const& id, bool useCache = true);
|
ServerRequest<ServerModMetadata> getMod(std::string const& id, bool useCache = true);
|
||||||
ServerRequest<ServerModVersion> getModVersion(std::string const& id, ModVersion const& version = ModVersionLatest(), bool useCache = true);
|
ServerRequest<ServerModVersion> getModVersion(std::string const& id, ModVersion const& version = ModVersionLatest(), bool useCache = true);
|
||||||
ServerRequest<ByteVector> getModLogo(std::string const& id, bool useCache = true);
|
ServerRequest<ByteVector> getModLogo(std::string const& id, bool useCache = true);
|
||||||
ServerRequest<std::unordered_set<std::string>> getTags(bool useCache = true);
|
ServerRequest<std::vector<ServerTag>> getTags(bool useCache = true);
|
||||||
|
|
||||||
ServerRequest<std::optional<ServerModUpdate>> checkUpdates(Mod const* mod);
|
ServerRequest<std::optional<ServerModUpdate>> checkUpdates(Mod const* mod);
|
||||||
|
|
||||||
|
|
|
@ -166,40 +166,55 @@ Popup<Mod*>* geode::openSettingsPopup(Mod* mod, bool disableGeodeTheme) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using ModLogoSrc = std::variant<Mod*, std::string, std::filesystem::path>;
|
||||||
|
|
||||||
class ModLogoSprite : public CCNode {
|
class ModLogoSprite : public CCNode {
|
||||||
protected:
|
protected:
|
||||||
std::string m_modID;
|
std::string m_modID;
|
||||||
CCNode* m_sprite = nullptr;
|
CCNode* m_sprite = nullptr;
|
||||||
EventListener<server::ServerRequest<ByteVector>> m_listener;
|
EventListener<server::ServerRequest<ByteVector>> m_listener;
|
||||||
|
|
||||||
bool init(std::string const& id, bool fetch) {
|
bool init(ModLogoSrc&& src) {
|
||||||
if (!CCNode::init())
|
if (!CCNode::init())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
this->setAnchorPoint({ .5f, .5f });
|
this->setAnchorPoint({ .5f, .5f });
|
||||||
this->setContentSize({ 50, 50 });
|
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);
|
m_listener.bind(this, &ModLogoSprite::onFetch);
|
||||||
|
|
||||||
|
std::visit(makeVisitor {
|
||||||
|
[this](Mod* mod) {
|
||||||
|
m_modID = mod->getID();
|
||||||
|
|
||||||
// Load from Resources
|
// Load from Resources
|
||||||
if (!fetch) {
|
this->setSprite(mod->isInternal() ?
|
||||||
this->setSprite(id == "geode.loader" ?
|
|
||||||
CCSprite::createWithSpriteFrameName("geode-logo.png"_spr) :
|
CCSprite::createWithSpriteFrameName("geode-logo.png"_spr) :
|
||||||
CCSprite::create(fmt::format("{}/logo.png", id).c_str()),
|
CCSprite::create(fmt::format("{}/logo.png", mod->getID()).c_str()),
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
|
[this](std::string const& id) {
|
||||||
|
m_modID = id;
|
||||||
|
|
||||||
// Asynchronously fetch from server
|
// Asynchronously fetch from server
|
||||||
else {
|
|
||||||
this->setSprite(createLoadingCircle(25), false);
|
this->setSprite(createLoadingCircle(25), false);
|
||||||
m_listener.setFilter(server::getModLogo(id));
|
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<ModLogoUIEvent::Impl>(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<ModLogoUIEvent::Impl>(this, m_modID)).post();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -224,6 +239,13 @@ protected:
|
||||||
ModLogoUIEvent(std::make_unique<ModLogoUIEvent::Impl>(this, m_modID)).post();
|
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) {
|
void onFetch(server::ServerRequest<ByteVector>::Event* event) {
|
||||||
if (auto result = event->getValue()) {
|
if (auto result = event->getValue()) {
|
||||||
|
@ -233,12 +255,7 @@ protected:
|
||||||
}
|
}
|
||||||
// Otherwise load downloaded sprite to memory
|
// Otherwise load downloaded sprite to memory
|
||||||
else {
|
else {
|
||||||
auto data = result->unwrap();
|
this->setSprite(std::move(result->unwrap()), true);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (event->isCancelled()) {
|
else if (event->isCancelled()) {
|
||||||
|
@ -247,9 +264,9 @@ protected:
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static ModLogoSprite* create(std::string const& id, bool fetch = false) {
|
static ModLogoSprite* create(ModLogoSrc&& src) {
|
||||||
auto ret = new ModLogoSprite();
|
auto ret = new ModLogoSprite();
|
||||||
if (ret->init(id, fetch)) {
|
if (ret->init(std::move(src))) {
|
||||||
ret->autorelease();
|
ret->autorelease();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -259,13 +276,17 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
CCNode* geode::createDefaultLogo() {
|
CCNode* geode::createDefaultLogo() {
|
||||||
return ModLogoSprite::create("");
|
return ModLogoSprite::create(ModLogoSrc(nullptr));
|
||||||
}
|
}
|
||||||
|
|
||||||
CCNode* geode::createModLogo(Mod* mod) {
|
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) {
|
CCNode* geode::createServerModLogo(std::string const& id) {
|
||||||
return ModLogoSprite::create(id, true);
|
return ModLogoSprite::create(ModLogoSrc(id));
|
||||||
}
|
}
|
||||||
|
|
|
@ -196,10 +196,10 @@ ButtonSprite* createTagLabel(std::string const& text, std::pair<ccColor3B, ccCol
|
||||||
label->m_BGSprite->setColor(color.second);
|
label->m_BGSprite->setColor(color.second);
|
||||||
return label;
|
return label;
|
||||||
}
|
}
|
||||||
ButtonSprite* createGeodeTagLabel(std::string_view tag) {
|
ButtonSprite* createGeodeTagLabel(server::ServerTag const& tag) {
|
||||||
return createTagLabel(geodeTagName(tag), geodeTagColors(tag));
|
return createTagLabel(tag.displayName, geodeTagColors(tag));
|
||||||
}
|
}
|
||||||
std::pair<ccColor3B, ccColor3B> geodeTagColors(std::string_view tag) {
|
std::pair<ccColor3B, ccColor3B> geodeTagColors(server::ServerTag const& tag) {
|
||||||
static std::array TAG_COLORS {
|
static std::array TAG_COLORS {
|
||||||
std::make_pair(ccc3(240, 233, 255), ccc3(130, 123, 163)),
|
std::make_pair(ccc3(240, 233, 255), ccc3(130, 123, 163)),
|
||||||
std::make_pair(ccc3(234, 255, 245), ccc3(123, 163, 136)),
|
std::make_pair(ccc3(234, 255, 245), ccc3(123, 163, 136)),
|
||||||
|
@ -207,20 +207,10 @@ std::pair<ccColor3B, ccColor3B> geodeTagColors(std::string_view tag) {
|
||||||
std::make_pair(ccc3(255, 253, 240), ccc3(163, 157, 123)),
|
std::make_pair(ccc3(255, 253, 240), ccc3(163, 157, 123)),
|
||||||
std::make_pair(ccc3(255, 242, 240), ccc3(163, 128, 123)),
|
std::make_pair(ccc3(255, 242, 240), ccc3(163, 128, 123)),
|
||||||
};
|
};
|
||||||
if (tag == "modtober24") {
|
if (tag.name == "modtober24") {
|
||||||
return std::make_pair(ccc3(225, 236, 245), ccc3(82, 139, 201));
|
return std::make_pair(ccc3(225, 236, 245), ccc3(82, 139, 201));
|
||||||
}
|
}
|
||||||
return TAG_COLORS[hash(tag) % 5932 % TAG_COLORS.size()];
|
return TAG_COLORS[hash(tag.name) % 5932 % TAG_COLORS.size()];
|
||||||
}
|
|
||||||
std::string geodeTagName(std::string_view tag) {
|
|
||||||
// todo in v4: rework tags to use a server-provided display name instead
|
|
||||||
if (tag == "modtober24") {
|
|
||||||
return "Modtober 2024";
|
|
||||||
}
|
|
||||||
// Everything else just capitalize and that's it
|
|
||||||
auto readable = std::string(tag);
|
|
||||||
readable[0] = std::toupper(readable[0]);
|
|
||||||
return readable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ListBorders* createGeodeListBorders(CCSize const& size, bool forceDisableTheme) {
|
ListBorders* createGeodeListBorders(CCSize const& size, bool forceDisableTheme) {
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
#include <Geode/ui/BasedButtonSprite.hpp>
|
#include <Geode/ui/BasedButtonSprite.hpp>
|
||||||
#include <Geode/ui/Popup.hpp>
|
#include <Geode/ui/Popup.hpp>
|
||||||
#include <Geode/loader/Mod.hpp>
|
#include <Geode/loader/Mod.hpp>
|
||||||
|
#include <server/Server.hpp>
|
||||||
|
|
||||||
using namespace geode::prelude;
|
using namespace geode::prelude;
|
||||||
|
|
||||||
|
@ -87,9 +88,8 @@ ButtonSprite* createGeodeButton(std::string const& text, bool gold = false, Geod
|
||||||
CircleButtonSprite* createGeodeCircleButton(CCSprite* top, float scale = 1.f, CircleBaseSize size = CircleBaseSize::Medium, bool altColor = false, bool forceDisableTheme = false);
|
CircleButtonSprite* createGeodeCircleButton(CCSprite* top, float scale = 1.f, CircleBaseSize size = CircleBaseSize::Medium, bool altColor = false, bool forceDisableTheme = false);
|
||||||
|
|
||||||
ButtonSprite* createTagLabel(std::string const& text, std::pair<ccColor3B, ccColor3B> const& color);
|
ButtonSprite* createTagLabel(std::string const& text, std::pair<ccColor3B, ccColor3B> const& color);
|
||||||
ButtonSprite* createGeodeTagLabel(std::string_view tag);
|
ButtonSprite* createGeodeTagLabel(server::ServerTag const& tag);
|
||||||
std::pair<ccColor3B, ccColor3B> geodeTagColors(std::string_view tag);
|
std::pair<ccColor3B, ccColor3B> geodeTagColors(server::ServerTag const& tag);
|
||||||
std::string geodeTagName(std::string_view tag);
|
|
||||||
|
|
||||||
ListBorders* createGeodeListBorders(CCSize const& size, bool forceDisableTheme = false);
|
ListBorders* createGeodeListBorders(CCSize const& size, bool forceDisableTheme = false);
|
||||||
|
|
||||||
|
|
|
@ -145,7 +145,7 @@ void ModsStatusNode::updateState() {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
// If there are no downloads happening, just show the restart button if needed
|
// If there are no downloads happening, just show the restart button if needed
|
||||||
case DownloadState::None: {
|
case DownloadState::None: {
|
||||||
m_restartBtn->setVisible(ModListSource::isRestartRequired());
|
m_restartBtn->setVisible(LoaderImpl::get()->isRestartRequired());
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
// If some downloads were cancelled, show the restart button normally
|
// If some downloads were cancelled, show the restart button normally
|
||||||
|
@ -154,7 +154,7 @@ void ModsStatusNode::updateState() {
|
||||||
m_status->setColor(ccWHITE);
|
m_status->setColor(ccWHITE);
|
||||||
m_status->setVisible(true);
|
m_status->setVisible(true);
|
||||||
|
|
||||||
m_restartBtn->setVisible(ModListSource::isRestartRequired());
|
m_restartBtn->setVisible(LoaderImpl::get()->isRestartRequired());
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
// If all downloads were finished, show the restart button normally
|
// If all downloads were finished, show the restart button normally
|
||||||
|
@ -170,7 +170,7 @@ void ModsStatusNode::updateState() {
|
||||||
m_status->setVisible(true);
|
m_status->setVisible(true);
|
||||||
m_statusBG->setVisible(true);
|
m_statusBG->setVisible(true);
|
||||||
|
|
||||||
m_restartBtn->setVisible(ModListSource::isRestartRequired());
|
m_restartBtn->setVisible(LoaderImpl::get()->isRestartRequired());
|
||||||
} break;
|
} break;
|
||||||
|
|
||||||
case DownloadState::SomeErrored: {
|
case DownloadState::SomeErrored: {
|
||||||
|
@ -274,6 +274,39 @@ void ModsLayer::onOpenModsFolder(CCObject*) {
|
||||||
file::openFolder(dirs::getModsDir());
|
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*) {
|
void ModsStatusNode::onRestart(CCObject*) {
|
||||||
// Update button state to let user know it's restarting but it might take a bit
|
// Update button state to let user know it's restarting but it might take a bit
|
||||||
m_restartBtn->setEnabled(false);
|
m_restartBtn->setEnabled(false);
|
||||||
|
@ -380,6 +413,20 @@ bool ModsLayer::init() {
|
||||||
folderBtn->setID("mods-folder-button");
|
folderBtn->setID("mods-folder-button");
|
||||||
actionsMenu->addChild(folderBtn);
|
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(
|
actionsMenu->setLayout(
|
||||||
ColumnLayout::create()
|
ColumnLayout::create()
|
||||||
->setAxisAlignment(AxisAlignment::Start)
|
->setAxisAlignment(AxisAlignment::Start)
|
||||||
|
|
|
@ -76,6 +76,7 @@ protected:
|
||||||
|
|
||||||
void onTab(CCObject* sender);
|
void onTab(CCObject* sender);
|
||||||
void onOpenModsFolder(CCObject*);
|
void onOpenModsFolder(CCObject*);
|
||||||
|
void onAddModFromFile(CCObject*);
|
||||||
void onBigView(CCObject*);
|
void onBigView(CCObject*);
|
||||||
void onSearch(CCObject*);
|
void onSearch(CCObject*);
|
||||||
void onGoToPage(CCObject*);
|
void onGoToPage(CCObject*);
|
||||||
|
|
|
@ -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));
|
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->m_contentLayer->updateLayout();
|
||||||
m_list->scrollToTop();
|
m_list->scrollToTop();
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include <Geode/utils/ColorProvider.hpp>
|
#include <Geode/utils/ColorProvider.hpp>
|
||||||
#include <Geode/binding/ButtonSprite.hpp>
|
#include <Geode/binding/ButtonSprite.hpp>
|
||||||
#include <Geode/loader/Loader.hpp>
|
#include <Geode/loader/Loader.hpp>
|
||||||
|
#include "server/DownloadManager.hpp"
|
||||||
#include "ui/mods/GeodeStyle.hpp"
|
#include "ui/mods/GeodeStyle.hpp"
|
||||||
#include "ui/mods/popups/ModPopup.hpp"
|
#include "ui/mods/popups/ModPopup.hpp"
|
||||||
#include "ui/mods/popups/DevPopup.hpp"
|
#include "ui/mods/popups/DevPopup.hpp"
|
||||||
|
@ -391,10 +392,6 @@ void ModItem::updateState() {
|
||||||
m_bg->setColor("mod-list-featured-color"_cc3b);
|
m_bg->setColor("mod-list-featured-color"_cc3b);
|
||||||
m_bg->setOpacity(65);
|
m_bg->setOpacity(65);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
[this](ModSuggestion const& suggestion) {
|
|
||||||
m_bg->setColor("mod-list-recommended-bg"_cc3b);
|
|
||||||
m_bg->setOpacity(isGeodeTheme() ? 25 : 90);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -631,18 +628,7 @@ void ModItem::onEnable(CCObject*) {
|
||||||
UpdateModListStateEvent(UpdateModState(m_source.getID())).post();
|
UpdateModListStateEvent(UpdateModState(m_source.getID())).post();
|
||||||
}
|
}
|
||||||
void ModItem::onInstall(CCObject*) {
|
void ModItem::onInstall(CCObject*) {
|
||||||
if (auto updates = m_source.hasUpdates()) {
|
m_source.startInstall();
|
||||||
if (updates->replacement.has_value()) {
|
|
||||||
server::ModDownloadManager::get()->startDownload(
|
|
||||||
updates->replacement->id,
|
|
||||||
updates->replacement->version,
|
|
||||||
std::nullopt,
|
|
||||||
m_source.getID()
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
server::ModDownloadManager::get()->startDownload(m_source.getID(), std::nullopt);
|
|
||||||
}
|
}
|
||||||
void ModItem::onDevelopers(CCObject*) {
|
void ModItem::onDevelopers(CCObject*) {
|
||||||
DevListPopup::create(m_source)->show();
|
DevListPopup::create(m_source)->show();
|
||||||
|
|
|
@ -144,7 +144,7 @@ bool FiltersPopup::setup(ModListSource* src) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FiltersPopup::onLoadTags(typename server::ServerRequest<std::unordered_set<std::string>>::Event* event) {
|
void FiltersPopup::onLoadTags(typename server::ServerRequest<std::vector<server::ServerTag>>::Event* event) {
|
||||||
if (event->getValue() && event->getValue()->isOk()) {
|
if (event->getValue() && event->getValue()->isOk()) {
|
||||||
auto tags = event->getValue()->unwrap();
|
auto tags = event->getValue()->unwrap();
|
||||||
m_tagsMenu->removeAllChildren();
|
m_tagsMenu->removeAllChildren();
|
||||||
|
@ -157,7 +157,7 @@ void FiltersPopup::onLoadTags(typename server::ServerRequest<std::unordered_set<
|
||||||
offSpr, onSpr, this, menu_selector(FiltersPopup::onSelectTag)
|
offSpr, onSpr, this, menu_selector(FiltersPopup::onSelectTag)
|
||||||
);
|
);
|
||||||
btn->m_notClickable = true;
|
btn->m_notClickable = true;
|
||||||
btn->setUserObject("tag", CCString::create(tag));
|
btn->setUserObject("tag", CCString::create(tag.name));
|
||||||
m_tagsMenu->addChild(btn);
|
m_tagsMenu->addChild(btn);
|
||||||
}
|
}
|
||||||
m_tagsMenu->updateLayout();
|
m_tagsMenu->updateLayout();
|
||||||
|
|
|
@ -13,14 +13,14 @@ protected:
|
||||||
ModListSource* m_source;
|
ModListSource* m_source;
|
||||||
CCMenu* m_tagsMenu;
|
CCMenu* m_tagsMenu;
|
||||||
std::unordered_set<std::string> m_selectedTags;
|
std::unordered_set<std::string> m_selectedTags;
|
||||||
EventListener<server::ServerRequest<std::unordered_set<std::string>>> m_tagsListener;
|
EventListener<server::ServerRequest<std::vector<server::ServerTag>>> m_tagsListener;
|
||||||
CCMenuItemToggler* m_enabledModsOnly = nullptr;
|
CCMenuItemToggler* m_enabledModsOnly = nullptr;
|
||||||
TextInput* m_developerNameInput = nullptr;
|
TextInput* m_developerNameInput = nullptr;
|
||||||
|
|
||||||
bool setup(ModListSource* src) override;
|
bool setup(ModListSource* src) override;
|
||||||
void updateTags();
|
void updateTags();
|
||||||
|
|
||||||
void onLoadTags(typename server::ServerRequest<std::unordered_set<std::string>>::Event* event);
|
void onLoadTags(typename server::ServerRequest<std::vector<server::ServerTag>>::Event* event);
|
||||||
void onResetTags(CCObject*);
|
void onResetTags(CCObject*);
|
||||||
void onResetDevName(CCObject*);
|
void onResetDevName(CCObject*);
|
||||||
void onSelectTag(CCObject* sender);
|
void onSelectTag(CCObject* sender);
|
||||||
|
|
|
@ -7,11 +7,13 @@
|
||||||
#include <Geode/loader/ModSettingsManager.hpp>
|
#include <Geode/loader/ModSettingsManager.hpp>
|
||||||
#include <Geode/ui/GeodeUI.hpp>
|
#include <Geode/ui/GeodeUI.hpp>
|
||||||
#include <Geode/utils/ColorProvider.hpp>
|
#include <Geode/utils/ColorProvider.hpp>
|
||||||
|
#include <optional>
|
||||||
#include "ConfirmUninstallPopup.hpp"
|
#include "ConfirmUninstallPopup.hpp"
|
||||||
#include "../settings/ModSettingsPopup.hpp"
|
#include "../settings/ModSettingsPopup.hpp"
|
||||||
#include "../../../internal/about.hpp"
|
#include "../../../internal/about.hpp"
|
||||||
#include "../../GeodeUIEvent.hpp"
|
#include "../../GeodeUIEvent.hpp"
|
||||||
#include "../popups/ModtoberPopup.hpp"
|
#include "../popups/ModtoberPopup.hpp"
|
||||||
|
#include "server/DownloadManager.hpp"
|
||||||
|
|
||||||
class FetchTextArea : public CCNode {
|
class FetchTextArea : public CCNode {
|
||||||
public:
|
public:
|
||||||
|
@ -883,7 +885,7 @@ void ModPopup::onCheckUpdates(typename server::ServerRequest<std::optional<serve
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ModPopup::onLoadTags(typename server::ServerRequest<std::unordered_set<std::string>>::Event* event) {
|
void ModPopup::onLoadTags(typename server::ServerRequest<std::vector<server::ServerTag>>::Event* event) {
|
||||||
if (event->getValue() && event->getValue()->isOk()) {
|
if (event->getValue() && event->getValue()->isOk()) {
|
||||||
auto data = event->getValue()->unwrap();
|
auto data = event->getValue()->unwrap();
|
||||||
m_tags->removeAllChildren();
|
m_tags->removeAllChildren();
|
||||||
|
@ -902,7 +904,7 @@ void ModPopup::onLoadTags(typename server::ServerRequest<std::unordered_set<std:
|
||||||
// If the build times from the cool popup become too long then we can
|
// If the build times from the cool popup become too long then we can
|
||||||
// probably move that to a normal FLAlert that explains "Modtober was
|
// probably move that to a normal FLAlert that explains "Modtober was
|
||||||
// this contest blah blah this mod was made for it"
|
// this contest blah blah this mod was made for it"
|
||||||
else if (data.contains("modtober24")) {
|
else if (ranges::contains(data, [](auto const& tag) { return tag.name == "modtober24"; })) {
|
||||||
auto menu = CCMenu::create();
|
auto menu = CCMenu::create();
|
||||||
menu->setID("modtober-banner");
|
menu->setID("modtober-banner");
|
||||||
menu->ignoreAnchorPointForPosition(false);
|
menu->ignoreAnchorPointForPosition(false);
|
||||||
|
@ -1047,7 +1049,9 @@ void ModPopup::onInstall(CCObject*) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
server::ModDownloadManager::get()->startDownload(m_source.getID(), std::nullopt);
|
|
||||||
|
m_source.startInstall();
|
||||||
|
|
||||||
this->onClose(nullptr);
|
this->onClose(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -38,7 +38,7 @@ protected:
|
||||||
CCNode* m_modtoberBanner = nullptr;
|
CCNode* m_modtoberBanner = nullptr;
|
||||||
std::unordered_map<Tab, std::pair<GeodeTabSprite*, Ref<CCNode>>> m_tabs;
|
std::unordered_map<Tab, std::pair<GeodeTabSprite*, Ref<CCNode>>> m_tabs;
|
||||||
EventListener<server::ServerRequest<server::ServerModMetadata>> m_statsListener;
|
EventListener<server::ServerRequest<server::ServerModMetadata>> m_statsListener;
|
||||||
EventListener<server::ServerRequest<std::unordered_set<std::string>>> m_tagsListener;
|
EventListener<server::ServerRequest<std::vector<server::ServerTag>>> m_tagsListener;
|
||||||
EventListener<server::ServerRequest<std::optional<server::ServerModUpdate>>> m_checkUpdateListener;
|
EventListener<server::ServerRequest<std::optional<server::ServerModUpdate>>> m_checkUpdateListener;
|
||||||
EventListener<UpdateModListStateFilter> m_updateStateListener;
|
EventListener<UpdateModListStateFilter> m_updateStateListener;
|
||||||
EventListener<server::ModDownloadFilter> m_downloadListener;
|
EventListener<server::ModDownloadFilter> m_downloadListener;
|
||||||
|
@ -52,7 +52,7 @@ protected:
|
||||||
void setStatValue(CCNode* stat, std::optional<std::string> const& value);
|
void setStatValue(CCNode* stat, std::optional<std::string> const& value);
|
||||||
|
|
||||||
void onLoadServerInfo(typename server::ServerRequest<server::ServerModMetadata>::Event* event);
|
void onLoadServerInfo(typename server::ServerRequest<server::ServerModMetadata>::Event* event);
|
||||||
void onLoadTags(typename server::ServerRequest<std::unordered_set<std::string>>::Event* event);
|
void onLoadTags(typename server::ServerRequest<std::vector<server::ServerTag>>::Event* event);
|
||||||
void onCheckUpdates(typename server::ServerRequest<std::optional<server::ServerModUpdate>>::Event* event);
|
void onCheckUpdates(typename server::ServerRequest<std::optional<server::ServerModUpdate>>::Event* event);
|
||||||
|
|
||||||
void onTab(CCObject* sender);
|
void onTab(CCObject* sender);
|
||||||
|
|
|
@ -13,10 +13,10 @@ bool InstalledModsQuery::preCheck(ModSource const& src) const {
|
||||||
}
|
}
|
||||||
// If only errors requested, only show mods with errors (duh)
|
// If only errors requested, only show mods with errors (duh)
|
||||||
if (type == InstalledModListType::OnlyOutdated) {
|
if (type == InstalledModListType::OnlyOutdated) {
|
||||||
return src.asMod()->targetsOutdatedVersion().has_value();
|
return src.asMod() && src.asMod()->targetsOutdatedVersion().has_value();
|
||||||
}
|
}
|
||||||
if (type == InstalledModListType::OnlyErrors) {
|
if (type == InstalledModListType::OnlyErrors) {
|
||||||
return src.asMod()->hasLoadProblems();
|
return src.asMod() && src.asMod()->hasLoadProblems();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
#include "ModListSource.hpp"
|
#include "ModListSource.hpp"
|
||||||
#include <server/DownloadManager.hpp>
|
#include <server/DownloadManager.hpp>
|
||||||
#include <Geode/loader/ModSettingsManager.hpp>
|
#include <Geode/loader/ModSettingsManager.hpp>
|
||||||
|
#include <loader/LoaderImpl.hpp>
|
||||||
|
|
||||||
#define FTS_FUZZY_MATCH_IMPLEMENTATION
|
#define FTS_FUZZY_MATCH_IMPLEMENTATION
|
||||||
#include <Geode/external/fts/fts_fuzzy_match.h>
|
#include <Geode/external/fts/fts_fuzzy_match.h>
|
||||||
|
@ -88,20 +89,6 @@ void ModListSource::clearAllCaches() {
|
||||||
src->clearCache();
|
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) {
|
bool weightedFuzzyMatch(std::string const& str, std::string const& kw, double weight, double& out) {
|
||||||
int score;
|
int score;
|
||||||
|
|
|
@ -79,7 +79,6 @@ public:
|
||||||
void setPageSize(size_t size);
|
void setPageSize(size_t size);
|
||||||
|
|
||||||
static void clearAllCaches();
|
static void clearAllCaches();
|
||||||
static bool isRestartRequired();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
|
@ -231,8 +230,8 @@ void filterModsWithLocalQuery(ModListSource::ProvidedMods& mods, Query const& qu
|
||||||
return a.second > b.second;
|
return a.second > b.second;
|
||||||
}
|
}
|
||||||
// Make sure outdated mods are always last by default
|
// Make sure outdated mods are always last by default
|
||||||
auto aIsOutdated = a.first.getMetadata().checkGameVersion().isErr();
|
auto aIsOutdated = a.first.getMetadata().checkTargetVersions().isErr();
|
||||||
auto bIsOutdated = b.first.getMetadata().checkGameVersion().isErr();
|
auto bIsOutdated = b.first.getMetadata().checkTargetVersions().isErr();
|
||||||
if (aIsOutdated != bIsOutdated) {
|
if (aIsOutdated != bIsOutdated) {
|
||||||
return !aIsOutdated;
|
return !aIsOutdated;
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,9 +52,6 @@ std::string ModSource::getID() const {
|
||||||
[](server::ServerModMetadata const& metadata) {
|
[](server::ServerModMetadata const& metadata) {
|
||||||
return metadata.id;
|
return metadata.id;
|
||||||
},
|
},
|
||||||
[](ModSuggestion const& suggestion) {
|
|
||||||
return suggestion.suggestion.getID();
|
|
||||||
},
|
|
||||||
}, m_value);
|
}, m_value);
|
||||||
}
|
}
|
||||||
ModMetadata ModSource::getMetadata() const {
|
ModMetadata ModSource::getMetadata() const {
|
||||||
|
@ -66,9 +63,6 @@ ModMetadata ModSource::getMetadata() const {
|
||||||
// Versions should be guaranteed to have at least one item
|
// Versions should be guaranteed to have at least one item
|
||||||
return metadata.versions.front().metadata;
|
return metadata.versions.front().metadata;
|
||||||
},
|
},
|
||||||
[](ModSuggestion const& suggestion) {
|
|
||||||
return suggestion.suggestion;
|
|
||||||
},
|
|
||||||
}, m_value);
|
}, m_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,9 +75,6 @@ std::string ModSource::formatDevelopers() const {
|
||||||
// Versions should be guaranteed to have at least one item
|
// Versions should be guaranteed to have at least one item
|
||||||
return metadata.formatDevelopersToString();
|
return metadata.formatDevelopersToString();
|
||||||
},
|
},
|
||||||
[](ModSuggestion const& suggestion) {
|
|
||||||
return ModMetadata::formatDeveloperDisplayString(suggestion.suggestion.getDevelopers());
|
|
||||||
},
|
|
||||||
}, m_value);
|
}, m_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,9 +86,6 @@ CCNode* ModSource::createModLogo() const {
|
||||||
[](server::ServerModMetadata const& metadata) {
|
[](server::ServerModMetadata const& metadata) {
|
||||||
return createServerModLogo(metadata.id);
|
return createServerModLogo(metadata.id);
|
||||||
},
|
},
|
||||||
[](ModSuggestion const& suggestion) {
|
|
||||||
return createServerModLogo(suggestion.suggestion.getID());
|
|
||||||
},
|
|
||||||
}, m_value);
|
}, m_value);
|
||||||
}
|
}
|
||||||
bool ModSource::wantsRestart() const {
|
bool ModSource::wantsRestart() const {
|
||||||
|
@ -114,9 +102,6 @@ bool ModSource::wantsRestart() const {
|
||||||
[](server::ServerModMetadata const& metdata) {
|
[](server::ServerModMetadata const& metdata) {
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
[](ModSuggestion const& suggestion) {
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
}, m_value);
|
}, m_value);
|
||||||
}
|
}
|
||||||
std::optional<server::ServerModUpdate> ModSource::hasUpdates() const {
|
std::optional<server::ServerModUpdate> ModSource::hasUpdates() const {
|
||||||
|
@ -134,9 +119,6 @@ ModSource ModSource::convertForPopup() const {
|
||||||
}
|
}
|
||||||
return ModSource(server::ServerModMetadata(metadata));
|
return ModSource(server::ServerModMetadata(metadata));
|
||||||
},
|
},
|
||||||
[](ModSuggestion const& suggestion) {
|
|
||||||
return ModSource(ModSuggestion(suggestion));
|
|
||||||
},
|
|
||||||
}, m_value);
|
}, m_value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,6 +131,7 @@ server::ServerModMetadata const* ModSource::asServer() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
server::ServerRequest<std::optional<std::string>> ModSource::fetchAbout() const {
|
server::ServerRequest<std::optional<std::string>> ModSource::fetchAbout() const {
|
||||||
|
// todo: write as visit
|
||||||
if (auto mod = this->asMod()) {
|
if (auto mod = this->asMod()) {
|
||||||
return server::ServerRequest<std::optional<std::string>>::immediate(Ok(mod->getMetadata().getDetails()));
|
return server::ServerRequest<std::optional<std::string>>::immediate(Ok(mod->getMetadata().getDetails()));
|
||||||
}
|
}
|
||||||
|
@ -180,41 +163,41 @@ server::ServerRequest<server::ServerModMetadata> ModSource::fetchServerInfo() co
|
||||||
// should deal with performance issues
|
// should deal with performance issues
|
||||||
return server::getMod(this->getID());
|
return server::getMod(this->getID());
|
||||||
}
|
}
|
||||||
server::ServerRequest<std::unordered_set<std::string>> ModSource::fetchValidTags() const {
|
server::ServerRequest<std::vector<server::ServerTag>> ModSource::fetchValidTags() const {
|
||||||
return std::visit(makeVisitor {
|
std::unordered_set<std::string> modTags;
|
||||||
[](Mod* mod) {
|
std::visit(makeVisitor {
|
||||||
|
[&](Mod* mod) {
|
||||||
|
modTags = mod->getMetadata().getTags();
|
||||||
|
},
|
||||||
|
[&](server::ServerModMetadata const& metadata) {
|
||||||
|
modTags = metadata.tags;
|
||||||
|
},
|
||||||
|
}, m_value);
|
||||||
|
|
||||||
|
// This does two things:
|
||||||
|
// 1. For installed mods, it filters out invalid tags
|
||||||
|
// 2. For everything else, it gets the rest of the tag info (display name) from the server
|
||||||
return server::getTags().map(
|
return server::getTags().map(
|
||||||
[mod](Result<std::unordered_set<std::string>, server::ServerError>* result)
|
[modTags = std::move(modTags)](auto* result) -> Result<std::vector<server::ServerTag>, server::ServerError> {
|
||||||
-> Result<std::unordered_set<std::string>, server::ServerError> {
|
auto finalTags = std::vector<server::ServerTag>();
|
||||||
std::unordered_set<std::string> finalTags;
|
|
||||||
auto modTags = mod->getMetadata().getTags();
|
|
||||||
|
|
||||||
if (result->isOk()) {
|
if (result->isOk()) {
|
||||||
std::unordered_set<std::string> fetched = result->unwrap();
|
auto fetched = result->unwrap();
|
||||||
// Filter out invalid tags
|
// Filter out invalid tags
|
||||||
for (std::string const& tag : modTags) {
|
for (auto& tag : modTags) {
|
||||||
if (fetched.contains(tag)) {
|
auto stag = ranges::find(fetched, [&tag](server::ServerTag const& stag) {
|
||||||
finalTags.insert(tag);
|
return stag.name == tag;
|
||||||
|
});
|
||||||
|
if (stag) {
|
||||||
|
finalTags.push_back(*stag);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(finalTags);
|
return Ok(finalTags);
|
||||||
},
|
},
|
||||||
[](server::ServerProgress* progress) {
|
[](server::ServerProgress* progress) {
|
||||||
return *progress;
|
return *progress;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
},
|
|
||||||
[](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() {
|
server::ServerRequest<std::optional<server::ServerModUpdate>> ModSource::checkUpdates() {
|
||||||
m_availableUpdate = std::nullopt;
|
m_availableUpdate = std::nullopt;
|
||||||
|
@ -234,9 +217,29 @@ server::ServerRequest<std::optional<server::ServerModUpdate>> ModSource::checkUp
|
||||||
// Server mods aren't installed so you can't install updates for them
|
// Server mods aren't installed so you can't install updates for them
|
||||||
return server::ServerRequest<std::optional<server::ServerModUpdate>>::immediate(Ok(std::nullopt));
|
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);
|
}, m_value);
|
||||||
}
|
}
|
||||||
|
void ModSource::startInstall() {
|
||||||
|
if (auto updates = this->hasUpdates()) {
|
||||||
|
if (updates->replacement.has_value()) {
|
||||||
|
server::ModDownloadManager::get()->startDownload(
|
||||||
|
updates->replacement->id,
|
||||||
|
updates->replacement->version,
|
||||||
|
std::nullopt,
|
||||||
|
this->getID()
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
server::ModDownloadManager::get()->startDownload(
|
||||||
|
this->getID(),
|
||||||
|
updates->version
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
server::ModDownloadManager::get()->startDownload(
|
||||||
|
this->getID(),
|
||||||
|
this->asServer()
|
||||||
|
? std::optional{this->asServer()->latestVersion().getVersion()}
|
||||||
|
: std::nullopt
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
#include <Geode/loader/Mod.hpp>
|
#include <Geode/loader/Mod.hpp>
|
||||||
#include <server/Server.hpp>
|
#include <server/Server.hpp>
|
||||||
|
#include <loader/LoaderImpl.hpp>
|
||||||
|
|
||||||
using namespace geode::prelude;
|
using namespace geode::prelude;
|
||||||
|
|
||||||
|
@ -23,7 +24,6 @@ public:
|
||||||
ModSource() = default;
|
ModSource() = default;
|
||||||
ModSource(Mod* mod);
|
ModSource(Mod* mod);
|
||||||
ModSource(server::ServerModMetadata&& metadata);
|
ModSource(server::ServerModMetadata&& metadata);
|
||||||
ModSource(ModSuggestion&& suggestion);
|
|
||||||
|
|
||||||
std::string getID() const;
|
std::string getID() const;
|
||||||
ModMetadata getMetadata() const;
|
ModMetadata getMetadata() const;
|
||||||
|
@ -47,6 +47,7 @@ public:
|
||||||
server::ServerRequest<server::ServerModMetadata> fetchServerInfo() const;
|
server::ServerRequest<server::ServerModMetadata> fetchServerInfo() const;
|
||||||
server::ServerRequest<std::optional<std::string>> fetchAbout() const;
|
server::ServerRequest<std::optional<std::string>> fetchAbout() const;
|
||||||
server::ServerRequest<std::optional<std::string>> fetchChangelog() const;
|
server::ServerRequest<std::optional<std::string>> fetchChangelog() const;
|
||||||
server::ServerRequest<std::unordered_set<std::string>> fetchValidTags() const;
|
server::ServerRequest<std::vector<server::ServerTag>> fetchValidTags() const;
|
||||||
server::ServerRequest<std::optional<server::ServerModUpdate>> checkUpdates();
|
server::ServerRequest<std::optional<server::ServerModUpdate>> checkUpdates();
|
||||||
|
void startInstall();
|
||||||
};
|
};
|
||||||
|
|
|
@ -145,15 +145,17 @@ std::vector<std::string> WebResponse::headers() const {
|
||||||
return map::keys(m_impl->m_headers);
|
return map::keys(m_impl->m_headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<std::string> WebResponse::header(std::string_view name, int index) const {
|
std::optional<std::string> WebResponse::header(std::string_view name) const {
|
||||||
auto str = std::string(name);
|
if (auto str = std::string(name); m_impl->m_headers.contains(str)) {
|
||||||
if (m_impl->m_headers.contains(str)) {
|
return m_impl->m_headers.at(str).at(0);
|
||||||
const auto headersWithName = m_impl->m_headers.at(str);
|
|
||||||
if (headersWithName.size() > index) {
|
|
||||||
return headersWithName.at(index);
|
|
||||||
}
|
}
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<std::vector<std::string>> WebResponse::headersWithName(std::string_view name) const {
|
||||||
|
if (auto str = std::string(name); m_impl->m_headers.contains(str)) {
|
||||||
|
return m_impl->m_headers.at(str);
|
||||||
|
}
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue