diff --git a/CMakeLists.txt b/CMakeLists.txt index 8fa2cec2..13e4a766 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -113,6 +113,7 @@ elseif(EXISTS ${GEODE_PLATFORM_BIN_PATH}) else() message(FATAL_ERROR "No valid loader binary to link to! Install prebuilts with `geode sdk install-prebuilts`, " - "or build Geode from source and add `set(GEODE_LINK_NIGHTLY On)` to your CMakeLists.txt." + "or build Geode from source and add `set(GEODE_LINK_NIGHTLY On)` to your CMakeLists.txt " + "in the line before calling add_subdirectory for Geode." ) endif() diff --git a/loader/include/Geode/loader/Index.hpp b/loader/include/Geode/loader/Index.hpp index 70bace87..e24654b2 100644 --- a/loader/include/Geode/loader/Index.hpp +++ b/loader/include/Geode/loader/Index.hpp @@ -97,12 +97,23 @@ namespace geode { std::vector getSources() const; std::vector getItems() const; - bool isKnownItem(std::string const& id, std::optional version) const; - IndexItemHandle getItem(std::string const& id, std::optional version) const; + bool isKnownItem(std::string const& id, std::optional version) const; + IndexItemHandle getItem( + std::string const& id, + std::optional version + ) const; + IndexItemHandle getItem( + std::string const& id, + ComparableVersionInfo version + ) const; IndexItemHandle getItem(ModInfo const& info) const; IndexItemHandle getItem(Mod* mod) const; bool isUpdateAvailable(IndexItemHandle item) const; bool areUpdatesAvailable() const; + void install(IndexItemHandle item); + Result> getInstallList( + IndexItemHandle item + ) const; bool hasTriedToUpdate() const; bool isUpToDate() const; diff --git a/loader/include/Geode/loader/Mod.hpp b/loader/include/Geode/loader/Mod.hpp index d55147cf..2eebf7bf 100644 --- a/loader/include/Geode/loader/Mod.hpp +++ b/loader/include/Geode/loader/Mod.hpp @@ -63,10 +63,6 @@ namespace geode { * Whether the mod binary is loaded or not */ bool m_binaryLoaded = false; - /** - * Whether the mod is loadable or not - */ - bool m_resolved = false; /** * Mod temp directory name */ @@ -370,7 +366,7 @@ namespace geode { * @returns True if the mod has unresolved * dependencies, false if not. */ - bool updateDependencyStates(); + Result<> updateDependencies(); /** * Get a list of all the unresolved * dependencies this mod has diff --git a/loader/include/Geode/loader/ModInfo.hpp b/loader/include/Geode/loader/ModInfo.hpp index aac4e4a9..ac0667e8 100644 --- a/loader/include/Geode/loader/ModInfo.hpp +++ b/loader/include/Geode/loader/ModInfo.hpp @@ -12,14 +12,12 @@ namespace geode { using ModJson = nlohmann::ordered_json; - struct Dependency { - std::string m_id; - // todo: Dynamic versions (1.*.*) - VersionInfo m_version { 1, 0, 0 }; - ModResolveState m_state = ModResolveState::Unloaded; - bool m_required = false; - Mod* m_mod = nullptr; - bool isUnresolved() const; + struct GEODE_DLL Dependency { + std::string id; + ComparableVersionInfo version; + bool required = false; + Mod* mod = nullptr; + bool isResolved() const; }; struct IssuesInfo { diff --git a/loader/include/Geode/loader/Types.hpp b/loader/include/Geode/loader/Types.hpp index aabf344b..1e49be06 100644 --- a/loader/include/Geode/loader/Types.hpp +++ b/loader/include/Geode/loader/Types.hpp @@ -129,25 +129,6 @@ namespace geode { } }; - /** - * Represents if a mod has been loaded & - * its dependencies resolved - */ - enum class ModResolveState { - // Mod has not been loaded at all - Unloaded, - // Mod has unresolved dependencies - Unresolved, - // Mod has all dependencies resolved, - // but is not loaded yet - Resolved, - // Mod is loaded - Loaded, - // Mod is loaded, however it is also - // disabled and therefore can't be used - Disabled, - }; - constexpr std::string_view GEODE_MOD_EXTENSION = ".geode"; class Mod; diff --git a/loader/include/Geode/utils/VersionInfo.hpp b/loader/include/Geode/utils/VersionInfo.hpp index 22f80802..fc4efc92 100644 --- a/loader/include/Geode/utils/VersionInfo.hpp +++ b/loader/include/Geode/utils/VersionInfo.hpp @@ -5,11 +5,17 @@ #include "../external/json/json.hpp" namespace geode { + enum class VersionCompare { + LessEq, + Exact, + MoreEq, + }; + /** * Class representing version information * @class VersionInfo */ - class GEODE_DLL VersionInfo { + class GEODE_DLL VersionInfo final { protected: int m_major = 1; int m_minor = 0; @@ -23,6 +29,7 @@ namespace geode { m_patch = patch; } VersionInfo(std::string const& versionString); + static bool validate(std::string const& string); constexpr int getMajor() const { return m_major; @@ -40,12 +47,40 @@ namespace geode { return std::tie(m_major, m_minor, m_patch) <=> std::tie(other.m_major, other.m_minor, other.m_patch); } - - static bool validate(std::string const& string); + constexpr bool operator==(VersionInfo const& other) const = default; std::string toString() const; }; void GEODE_DLL to_json(nlohmann::json& json, VersionInfo const& info); + void GEODE_DLL from_json(nlohmann::json const& json, VersionInfo& info); + GEODE_DLL std::ostream& operator<<(std::ostream& stream, VersionInfo const& version); - std::ostream& GEODE_DLL operator<<(std::ostream& stream, VersionInfo const& version); + class GEODE_DLL ComparableVersionInfo final { + protected: + VersionInfo m_version; + VersionCompare m_compare = VersionCompare::Exact; + + public: + constexpr ComparableVersionInfo() = default; + constexpr ComparableVersionInfo( + VersionInfo const& version, + VersionCompare const& compare + ) : m_version(version), m_compare(compare) {} + ComparableVersionInfo(std::string const& versionString); + static bool validate(std::string const& string); + + constexpr bool compare(VersionInfo const& version) const { + switch (m_compare) { + case VersionCompare::Exact: return m_version == version; break; + case VersionCompare::LessEq: return m_version <= version; break; + case VersionCompare::MoreEq: return m_version >= version; break; + } + return false; + } + + std::string toString() const; + }; + void GEODE_DLL to_json(nlohmann::json& json, ComparableVersionInfo const& info); + void GEODE_DLL from_json(nlohmann::json const& json, ComparableVersionInfo& info); + GEODE_DLL std::ostream& operator<<(std::ostream& stream, ComparableVersionInfo const& version); } diff --git a/loader/include/Geode/utils/ranges.hpp b/loader/include/Geode/utils/ranges.hpp index da53c60b..4e98fcc8 100644 --- a/loader/include/Geode/utils/ranges.hpp +++ b/loader/include/Geode/utils/ranges.hpp @@ -275,4 +275,24 @@ namespace geode::utils::ranges { } return member(*it); } + + template + struct ConstReverseWrapper { + C const& iter; + }; + + template + auto begin(ConstReverseWrapper const& c) { + return std::rbegin(c.iter); + } + + template + auto end(ConstReverseWrapper const& c) { + return std::rend(c.iter); + } + + template + ConstReverseWrapper reverse(C const& iter) { + return { iter }; + } } diff --git a/loader/src/loader/Index.cpp b/loader/src/loader/Index.cpp index b9723fa2..67dc2159 100644 --- a/loader/src/loader/Index.cpp +++ b/loader/src/loader/Index.cpp @@ -454,28 +454,21 @@ std::vector Index::getItems() const { bool Index::isKnownItem( std::string const& id, - std::optional version + std::optional version ) const { - if (m_items.count(id)) { - if (version) { - return m_items.at(id).count(version.value()); - } else { - return true; - } - } else { - return false; - } + return this->getItem(id, version).get(); } IndexItemHandle Index::getItem( std::string const& id, - std::optional version + std::optional version ) const { if (m_items.count(id)) { auto versions = m_items.at(id); if (version) { - if (versions.count(version.value())) { - return versions.at(version.value()); + auto major = version.value().getMajor(); + if (versions.count(major)) { + return versions.at(major); } } else { if (versions.size()) { @@ -486,12 +479,27 @@ IndexItemHandle Index::getItem( return nullptr; } +IndexItemHandle Index::getItem( + std::string const& id, + ComparableVersionInfo version +) const { + if (m_items.count(id)) { + // prefer most major version + for (auto& [_, item] : ranges::reverse(m_items.at(id))) { + if (version.compare(item->info.m_version)) { + return item; + } + } + } + return nullptr; +} + IndexItemHandle Index::getItem(ModInfo const& info) const { - return this->getItem(info.m_id, info.m_version.getMajor()); + return this->getItem(info.m_id, info.m_version); } IndexItemHandle Index::getItem(Mod* mod) const { - return this->getItem(mod->getID(), mod->getVersion().getMajor()); + return this->getItem(mod->getID(), mod->getVersion()); } bool Index::isUpdateAvailable(IndexItemHandle item) const { @@ -511,3 +519,39 @@ bool Index::areUpdatesAvailable() const { } return false; } + +Result> Index::getInstallList( + IndexItemHandle item +) const { + std::vector list; + for (auto& dep : item->info.m_dependencies) { + if (!dep.isResolved()) { + // check if this dep is available in the index + if (auto depItem = this->getItem(dep.id, dep.version)) { + // recursively add dependencies + GEODE_UNWRAP_INTO(auto deps, this->getInstallList(depItem)); + ranges::push(list, deps); + } + // otherwise user must get this dependency manually from somewhere + // else + else { + return Err( + "Dependency {} version {} not found in the index! Likely " + "reason is that the version of the dependency this mod " + "depends on is not available. Please let the the developer " + "({}) of the mod know!", + dep.id, dep.version.toString(), item->info.m_developer + ); + } + } + // if the dep is resolved, then all its dependencies must be installed + // already in order for that to have happened + } + // add this item to the end of the list + list.push_back(item); + return Ok(list); +} + +void Index::install(IndexItemHandle item) { + // todo +} diff --git a/loader/src/loader/Loader.cpp b/loader/src/loader/Loader.cpp index 30240f01..b25867e1 100644 --- a/loader/src/loader/Loader.cpp +++ b/loader/src/loader/Loader.cpp @@ -137,7 +137,7 @@ Result Loader::loadModFromInfo(ModInfo const& info) { "should-load-" + info.m_id, true ); // this loads the mod if its dependencies are resolved - mod->updateDependencyStates(); + GEODE_UNWRAP(mod->updateDependencies()); // add mod resources this->queueInGDThread([this, mod]() { @@ -288,7 +288,7 @@ std::vector Loader::getFailedMods() const { void Loader::updateAllDependencies() { for (auto const& [_, mod] : m_mods) { - mod->updateDependencyStates(); + (void)mod->updateDependencies(); } } diff --git a/loader/src/loader/Mod.cpp b/loader/src/loader/Mod.cpp index caa771bb..6ceabd38 100644 --- a/loader/src/loader/Mod.cpp +++ b/loader/src/loader/Mod.cpp @@ -192,71 +192,82 @@ bool Mod::hasSetting(std::string const& key) const { // Loading, Toggling, Installing Result<> Mod::loadBinary() { - if (!m_binaryLoaded) { - GEODE_UNWRAP(this->createTempDir()); - - if (this->hasUnresolvedDependencies()) return Err("Mod has unresolved dependencies"); - - GEODE_UNWRAP(this->loadPlatformBinary()); - m_binaryLoaded = true; - - // Call implicit entry point to place hooks etc. - m_implicitLoadFunc(this); - - ModStateEvent(this, ModEventType::Loaded).post(); - - auto loadRes = this->loadData(); - if (!loadRes) { - log::warn("Unable to load data for \"{}\": {}", m_info.m_id, loadRes.unwrapErr()); - } - - Loader::get()->updateAllDependencies(); - - GEODE_UNWRAP(this->enable()); + if (m_binaryLoaded) { + return Ok(); } + GEODE_UNWRAP(this->createTempDir()); + + if (this->hasUnresolvedDependencies()) { + return Err("Mod has unresolved dependencies"); + } + + GEODE_UNWRAP(this->loadPlatformBinary()); + m_binaryLoaded = true; + + // Call implicit entry point to place hooks etc. + m_implicitLoadFunc(this); + + ModStateEvent(this, ModEventType::Loaded).post(); + + auto loadRes = this->loadData(); + if (!loadRes) { + log::warn("Unable to load data for \"{}\": {}", m_info.m_id, loadRes.unwrapErr()); + } + + Loader::get()->updateAllDependencies(); + + GEODE_UNWRAP(this->enable()); + return Ok(); } Result<> Mod::unloadBinary() { - if (m_binaryLoaded) { - if (!m_info.m_supportsUnloading) return Err("Mod does not support unloading"); - - GEODE_UNWRAP(this->saveData()); - - GEODE_UNWRAP(this->disable()); - ModStateEvent(this, ModEventType::Unloaded).post(); - - // Disabling unhooks and unpatches already - for (auto const& hook : m_hooks) { - delete hook; - } - m_hooks.clear(); - - for (auto const& patch : m_patches) { - delete patch; - } - m_patches.clear(); - - GEODE_UNWRAP(this->unloadPlatformBinary()); - m_binaryLoaded = false; - - Loader::get()->updateAllDependencies(); + if (!m_binaryLoaded) { + return Ok(); } + if (!m_info.m_supportsUnloading) { + return Err("Mod does not support unloading"); + } + + GEODE_UNWRAP(this->saveData()); + + GEODE_UNWRAP(this->disable()); + ModStateEvent(this, ModEventType::Unloaded).post(); + + // Disabling unhooks and unpatches already + for (auto const& hook : m_hooks) { + delete hook; + } + m_hooks.clear(); + + for (auto const& patch : m_patches) { + delete patch; + } + m_patches.clear(); + + GEODE_UNWRAP(this->unloadPlatformBinary()); + m_binaryLoaded = false; + + Loader::get()->updateAllDependencies(); + return Ok(); } Result<> Mod::enable() { - if (!m_binaryLoaded) return this->loadBinary(); + if (!m_binaryLoaded) { + return this->loadBinary(); + } for (auto const& hook : m_hooks) { GEODE_UNWRAP(this->enableHook(hook)); } for (auto const& patch : m_patches) { - if (!patch->apply()) + if (!patch->apply()) { return Err("Unable to apply patch at " + std::to_string(patch->getAddress())); + } } ModStateEvent(this, ModEventType::Enabled).post(); @@ -266,21 +277,25 @@ Result<> Mod::enable() { } Result<> Mod::disable() { - if (m_enabled) { - if (!m_info.m_supportsDisabling) return Err("Mod does not support disabling"); - - ModStateEvent(this, ModEventType::Disabled).post(); - - for (auto const& hook : m_hooks) { - GEODE_UNWRAP(this->disableHook(hook)); - } - for (auto const& patch : m_patches) { - if (!patch->restore()) - return Err("Unable to restore patch at " + std::to_string(patch->getAddress())); - } - - m_enabled = false; + if (!m_enabled) { + return Ok(); } + if (!m_info.m_supportsDisabling) { + return Err("Mod does not support disabling"); + } + + ModStateEvent(this, ModEventType::Disabled).post(); + + for (auto const& hook : m_hooks) { + GEODE_UNWRAP(this->disableHook(hook)); + } + for (auto const& patch : m_patches) { + if (!patch->restore()) { + return Err("Unable to restore patch at " + std::to_string(patch->getAddress())); + } + } + + m_enabled = false; return Ok(); } @@ -309,73 +324,59 @@ bool Mod::isUninstalled() const { // Dependencies -bool Dependency::isUnresolved() const { - return m_required && - (m_state == ModResolveState::Unloaded || m_state == ModResolveState::Unresolved || - m_state == ModResolveState::Disabled); -} - -bool Mod::updateDependencyStates() { +Result<> Mod::updateDependencies() { bool hasUnresolved = false; for (auto& dep : m_info.m_dependencies) { - if (!dep.m_mod) dep.m_mod = Loader::get()->getLoadedMod(dep.m_id); - - if (dep.m_mod) { - dep.m_mod->updateDependencyStates(); - - if (dep.m_mod->hasUnresolvedDependencies()) { - dep.m_state = ModResolveState::Unresolved; + // set the dependency's loaded mod if such exists + if (!dep.mod) { + dep.mod = Loader::get()->getLoadedMod(dep.id); + // verify loaded dependency version + if (dep.mod && !dep.version.compare(dep.mod->getVersion())) { + dep.mod = nullptr; } - else { - if (!dep.m_mod->m_resolved) { - dep.m_mod->m_resolved = true; - dep.m_state = ModResolveState::Resolved; - auto r = dep.m_mod->loadBinary(); - if (!r) { - dep.m_state = ModResolveState::Unloaded; - log::log(Severity::Error, dep.m_mod, "{}", r.unwrapErr()); - } - } - else { - if (dep.m_mod->isEnabled()) { - dep.m_state = ModResolveState::Loaded; - } - else { - dep.m_state = ModResolveState::Disabled; - } + } + + // check if the dependency is loaded + if (dep.mod) { + // update the dependency recursively + GEODE_UNWRAP(dep.mod->updateDependencies()); + + // enable mod if it's resolved & enabled + if (!dep.mod->hasUnresolvedDependencies()) { + if (dep.mod->isEnabled()) { + GEODE_UNWRAP(dep.mod->loadBinary() + .expect("Unable to load dependency: {error}") + ); } } } - else { - dep.m_state = ModResolveState::Unloaded; - } - if (dep.isUnresolved()) { - m_resolved = false; - (void)this->unloadBinary(); + // check if the dependency is resolved now + if (!dep.isResolved()) { + GEODE_UNWRAP(this->unloadBinary() + .expect("Unable to unload mod: {error}") + ); hasUnresolved = true; } } - - if (!hasUnresolved && !m_resolved) { + // load if there weren't any unresolved dependencies + if (!hasUnresolved) { log::debug("All dependencies for {} found", m_info.m_id); - m_resolved = true; if (m_enabled) { log::debug("Resolved & loading {}", m_info.m_id); - auto r = this->loadBinary(); - if (!r) { - log::error("{} Error loading: {}", this, r.unwrapErr()); - } + GEODE_UNWRAP(this->loadBinary()); } else { log::debug("Resolved {}, however not loading it as it is disabled", m_info.m_id); } } - return hasUnresolved; + return Ok(); } bool Mod::hasUnresolvedDependencies() const { for (auto const& dep : m_info.m_dependencies) { - if (dep.isUnresolved()) return true; + if (!dep.isResolved()) { + return true; + } } return false; } @@ -383,15 +384,20 @@ bool Mod::hasUnresolvedDependencies() const { std::vector Mod::getUnresolvedDependencies() { std::vector unresolved; for (auto const& dep : m_info.m_dependencies) { - if (dep.isUnresolved()) unresolved.push_back(dep); + if (!dep.isResolved()) { + unresolved.push_back(dep); + } } return unresolved; } bool Mod::depends(std::string const& id) const { - return utils::ranges::contains(m_info.m_dependencies, [id](Dependency const& t) { - return t.m_id == id; - }); + return utils::ranges::contains( + m_info.m_dependencies, + [id](Dependency const& t) { + return t.id == id; + } + ); } // Hooks diff --git a/loader/src/loader/ModInfo.cpp b/loader/src/loader/ModInfo.cpp index c2c9f1e9..e18efcb4 100644 --- a/loader/src/loader/ModInfo.cpp +++ b/loader/src/loader/ModInfo.cpp @@ -6,6 +6,17 @@ USE_GEODE_NAMESPACE(); +bool Dependency::isResolved() const { + return + !this->required || + ( + this->mod && + this->mod->isLoaded() && + this->mod->isEnabled() && + this->version.compare(this->mod->getVersion()) + ); +} + static std::string sanitizeDetailsData(std::string const& str) { // delete CRLF return utils::string::replace(str, "\r", ""); @@ -37,7 +48,7 @@ Result ModInfo::createFromSchemaV010(ModJson const& rawJson) { using nlohmann::detail::value_t; root.needs("id").validate(&ModInfo::validateID).into(info.m_id); - root.needs("version").validate(&VersionInfo::validate).intoAs(info.m_version); + root.needs("version").validate(&VersionInfo::validate).into(info.m_version); root.needs("name").into(info.m_name); root.needs("developer").into(info.m_developer); root.has("description").into(info.m_description); @@ -50,9 +61,11 @@ Result ModInfo::createFromSchemaV010(ModJson const& rawJson) { auto obj = dep.obj(); auto depobj = Dependency {}; - obj.needs("id").validate(&ModInfo::validateID).into(depobj.m_id); - obj.needs("version").validate(&VersionInfo::validate).intoAs(depobj.m_version); - obj.has("required").into(depobj.m_required); + obj.needs("id").validate(&ModInfo::validateID).into(depobj.id); + obj.needs("version") + .validate(&ComparableVersionInfo::validate) + .into(depobj.version); + obj.has("required").into(depobj.required); obj.checkUnknownKeys(); info.m_dependencies.push_back(depobj); diff --git a/loader/src/ui/internal/GeodeUI.cpp b/loader/src/ui/internal/GeodeUI.cpp index 807a0e45..3079a330 100644 --- a/loader/src/ui/internal/GeodeUI.cpp +++ b/loader/src/ui/internal/GeodeUI.cpp @@ -49,9 +49,7 @@ void geode::openInfoPopup(Mod* mod) { } void geode::openIndexPopup(Mod* mod) { - if (auto item = Index::get()->getItem( - mod->getID(), mod->getVersion().getMajor() - )) { + if (auto item = Index::get()->getItem(mod)) { IndexItemInfoPopup::create(item, nullptr)->show(); } } diff --git a/loader/src/ui/internal/info/ModInfoPopup.cpp b/loader/src/ui/internal/info/ModInfoPopup.cpp index fa04b283..6f5f4331 100644 --- a/loader/src/ui/internal/info/ModInfoPopup.cpp +++ b/loader/src/ui/internal/info/ModInfoPopup.cpp @@ -514,7 +514,7 @@ void LocalModInfoPopup::FLAlert_Clicked(FLAlertLayer* layer, bool btn2) { switch (layer->getTag()) { case TAG_CONFIRM_UNINSTALL: { if (btn2) { - this->uninstall(); + this->doUninstall(); } } break; @@ -537,7 +537,7 @@ void LocalModInfoPopup::FLAlert_Clicked(FLAlertLayer* layer, bool btn2) { } } -void LocalModInfoPopup::uninstall() { +void LocalModInfoPopup::doUninstall() { auto res = m_mod->uninstall(); if (!res) { return FLAlertLayer::create( @@ -584,7 +584,7 @@ bool IndexItemInfoPopup::init(IndexItemHandle item, ModListLayer* list) { m_installBtnSpr->setScale(.6f); m_installBtn = CCMenuItemSpriteExtra::create( - m_installBtnSpr, this, nullptr + m_installBtnSpr, this, menu_selector(IndexItemInfoPopup::onInstall) ); m_installBtn->setPosition(-143.0f, 75.f); m_buttonMenu->addChild(m_installBtn); @@ -600,6 +600,48 @@ bool IndexItemInfoPopup::init(IndexItemHandle item, ModListLayer* list) { return true; } +void IndexItemInfoPopup::onInstall(CCObject*) { + auto list = Index::get()->getInstallList(m_item); + if (!list) { + return FLAlertLayer::create( + "Unable to Install", + list.unwrapErr(), + "OK" + )->show(); + } + FLAlertLayer::create( + this, + "Confirm Install", + fmt::format( + "The following mods will be installed:\n {}", + // le nest + ranges::join( + ranges::map>( + list.unwrap(), + [](IndexItemHandle handle) { + return fmt::format( + " - {} ({})", + handle->info.m_name, handle->info.m_id + ); + } + ), + "\n " + ) + ), + "Cancel", "OK" + )->show(); +} + +void IndexItemInfoPopup::doInstall() { + Index::get()->install(m_item); +} + +void IndexItemInfoPopup::FLAlert_Clicked(FLAlertLayer*, bool btn2) { + if (btn2) { + this->doInstall(); + } +} + CCNode* IndexItemInfoPopup::createLogo(CCSize const& size) { return geode::createIndexItemLogo(m_item, size); } diff --git a/loader/src/ui/internal/info/ModInfoPopup.hpp b/loader/src/ui/internal/info/ModInfoPopup.hpp index a258357a..0d2a6401 100644 --- a/loader/src/ui/internal/info/ModInfoPopup.hpp +++ b/loader/src/ui/internal/info/ModInfoPopup.hpp @@ -66,7 +66,7 @@ protected: void onUninstall(CCObject*); void onOpenConfigDir(CCObject*); void onAdvancedSettings(CCObject*); - void uninstall(); + void doUninstall(); void FLAlert_Clicked(FLAlertLayer*, bool) override; @@ -77,12 +77,17 @@ public: static LocalModInfoPopup* create(Mod* mod, ModListLayer* list); }; -class IndexItemInfoPopup : public ModInfoPopup { +class IndexItemInfoPopup : public ModInfoPopup, public FLAlertLayerProtocol { protected: IndexItemHandle m_item; bool init(IndexItemHandle item, ModListLayer* list); + void onInstall(CCObject*); + void doInstall(); + + void FLAlert_Clicked(FLAlertLayer*, bool) override; + CCNode* createLogo(CCSize const& size) override; ModInfo getModInfo() const override; diff --git a/loader/src/ui/internal/list/ModListCell.cpp b/loader/src/ui/internal/list/ModListCell.cpp index 86e90656..d0a5b66a 100644 --- a/loader/src/ui/internal/list/ModListCell.cpp +++ b/loader/src/ui/internal/list/ModListCell.cpp @@ -162,7 +162,7 @@ void ModCell::onUnresolvedInfo(CCObject*) { for (auto const& dep : m_mod->getUnresolvedDependencies()) { info += fmt::format( "{} ({}), ", - dep.m_id, dep.m_version.toString() + dep.id, dep.version.toString() ); } info.pop_back(); diff --git a/loader/src/utils/VersionInfo.cpp b/loader/src/utils/VersionInfo.cpp index 03f4d768..a8a2abe2 100644 --- a/loader/src/utils/VersionInfo.cpp +++ b/loader/src/utils/VersionInfo.cpp @@ -13,6 +13,8 @@ USE_GEODE_NAMESPACE(); #define GEODE_SSCANF sscanf #endif +// VersionInfo + bool VersionInfo::validate(std::string const& string) { int buf0, buf1, buf2; if (GEODE_SSCANF(string.c_str(), "v%d.%d.%d", &buf0, &buf1, &buf2)) return true; @@ -34,7 +36,65 @@ void geode::to_json(nlohmann::json& json, VersionInfo const& info) { json = info.toString(); } -std::ostream& geode::operator<<(std::ostream& stream, VersionInfo const& version) { - stream << version.toString(); - return stream; +void geode::from_json(nlohmann::json const& json, VersionInfo& info) { + info = VersionInfo(json.template get()); +} + +std::ostream& geode::operator<<(std::ostream& stream, VersionInfo const& version) { + return stream << version.toString(); +} + +// ComparableVersionInfo + +ComparableVersionInfo::ComparableVersionInfo(std::string const& rawStr) { + auto version = rawStr; + if (version.starts_with("<=")) { + m_compare = VersionCompare::LessEq; + version.erase(0, 2); + } + else if (version.starts_with(">=")) { + m_compare = VersionCompare::MoreEq; + version.erase(0, 2); + } + else if (version.starts_with("==")) { + m_compare = VersionCompare::Exact; + version.erase(0, 2); + } + m_version = VersionInfo(version); +} + +bool ComparableVersionInfo::validate(std::string const& rawStr) { + auto version = rawStr; + // remove prefix + if ( + version.starts_with("<=") || + version.starts_with(">=") || + version.starts_with("==") + ) { + version.erase(0, 2); + } + // otherwise there's no prefix or it's invalid + return VersionInfo::validate(version); +} + +std::string ComparableVersionInfo::toString() const { + std::string prefix = ""; + switch (m_compare) { + case VersionCompare::Exact: prefix = "=="; break; + case VersionCompare::LessEq: prefix = "<="; break; + case VersionCompare::MoreEq: prefix = ">="; break; + } + return prefix + m_version.toString(); +} + +void geode::to_json(nlohmann::json& json, ComparableVersionInfo const& info) { + json = info.toString(); +} + +void geode::from_json(nlohmann::json const& json, ComparableVersionInfo& info) { + info = ComparableVersionInfo(json.template get()); +} + +std::ostream& geode::operator<<(std::ostream& stream, ComparableVersionInfo const& version) { + return stream << version.toString(); }