From ecf95e1b9d5ce2010db33c757aba89b6e0f7af91 Mon Sep 17 00:00:00 2001 From: Cosmella-v Date: Mon, 6 Apr 2026 04:53:54 +0100 Subject: [PATCH 1/3] add load-if for detecting mods, add a ingame error thing rather then just logging to console. update sdk to 5.5.3 --- include/TextureLoader.hpp | 3 + mod.json | 2 +- src/API.cpp | 3 + src/Pack.cpp | 455 ++++++++++++++++++++++---------------- src/Pack.hpp | 10 +- src/PackManager.cpp | 36 ++- src/PackManager.hpp | 3 + src/PackNode.cpp | 62 ++++-- src/PackSelectPopup.cpp | 62 +++++- src/PackSelectPopup.hpp | 2 + 10 files changed, 409 insertions(+), 229 deletions(-) diff --git a/include/TextureLoader.hpp b/include/TextureLoader.hpp index 8cf7120..4387dc6 100644 --- a/include/TextureLoader.hpp +++ b/include/TextureLoader.hpp @@ -33,8 +33,11 @@ struct Pack { inline std::vector getAvailablePacks() GEODE_EVENT_EXPORT_NORES(&getAvailablePacks, ()); inline std::vector getAppliedPacks() GEODE_EVENT_EXPORT_NORES(&getAppliedPacks, ()); + inline void openPopup() GEODE_EVENT_EXPORT_NORES(&openPopup, ()); +inline std::vector getFailedPacks() GEODE_EVENT_EXPORT_NORES(&getFailedPacks, ()); + } #undef MY_MOD_ID diff --git a/mod.json b/mod.json index 764ed2e..bd75de3 100644 --- a/mod.json +++ b/mod.json @@ -1,5 +1,5 @@ { - "geode": "5.4.0", + "geode": "5.5.3", "version": "1.10.0", "gd": { "win": "2.2081", diff --git a/src/API.cpp b/src/API.cpp index 52df2f4..ba6a5f1 100644 --- a/src/API.cpp +++ b/src/API.cpp @@ -29,6 +29,9 @@ std::vector api::getAvailablePacks() { std::vector api::getAppliedPacks() { return utils::ranges::map>(PackManager::get()->getAppliedPacks(), convertPack); } +std::vector api::getFailedPacks() { + return utils::ranges::map>(PackManager::get()->getFailedPacks(), convertPack); +} void api::openPopup() { PackSelectPopup::create()->show(); diff --git a/src/Pack.cpp b/src/Pack.cpp index e7bc90b..13e1083 100644 --- a/src/Pack.cpp +++ b/src/Pack.cpp @@ -1,241 +1,310 @@ #include "Pack.hpp" -#include #include #include +#include #include - -Result PackInfo::from(matjson::Value const& json) { - auto info = PackInfo(); - - auto copyJson = json; - auto root = checkJson(copyJson, "[pack.json]"); - - auto target = root.needs("textureldr").get(); - - GEODE_UNWRAP(root.ok()); - - auto current = Mod::get()->getVersion(); - if (target > VersionInfo(current.getMajor(), current.getMinor(), 99999999)) { - return Err("Pack targets newer version of TextureLdr"); - } - info.m_textureldr = target; - root.needs("name").into(info.m_name); - root.needs("id").into(info.m_id); - root.needs("version").into(info.m_version); - - // has single "author" key? - if (auto author = root.has("author")) { - std::string temp; - author.into(temp); - info.m_authors = { temp }; - } - // otherwise use "authors" key - else { - root.needs("authors").into(info.m_authors); +Result<> Pack::canLoad() { + if (!m_info.has_value()) + return Ok(); + auto info = m_info.value(); + auto current = Mod::get()->getVersion(); + if (info.m_textureldr > VersionInfo(current.getMajor(), current.getMinor(), 99999999)) { + return Err("Pack targets a newer version of Texture Loader ({})", info.m_textureldr); + }; + std::string realError = ""; + auto* loader = Loader::get(); + + for (const auto& var : info.m_mods) { + if (var.m_required) { + auto mod = loader->getLoadedMod(var.m_id); + if (var.m_incompatible) { + if (mod) { + if (!var.m_version.compare(mod->getVersion())) { + continue; + } + realError += fmt::format("Mod {} is Installed\n",var.m_id); + } + } + else { + if (mod) { + auto version = mod->getVersion(); + auto resonned = var.m_version.compareWithReason(version); + if (resonned == VersionCompareResult::Match) { + continue; + } + const char* reason = "version mismatch"; + + switch (resonned) { + case VersionCompareResult::TooOld: + realError += fmt::format("Current version of the mod {} is older than the version provided ({} < {})\n",var.m_id, version, var.m_version.getUnderlyingVersion()); + break; + case VersionCompareResult::TooNew: + realError += fmt::format("Current version of the mod {} is newer than the version provided ({} > {})\n",var.m_id, version, var.m_version.getUnderlyingVersion()); + break; + case VersionCompareResult::MajorMismatch: + realError += fmt::format("Current version of the mod {} has an incompatible major version with the version provided ({} != {})\n",var.m_id, version.getMajor(), var.m_version.getUnderlyingVersion().getMajor()); + break; + default: + realError += fmt::format("Current version of the mod {} version mismatch ({} {}/c>)\n",var.m_id, version, var.m_version); + break; + } + } else { + realError += fmt::format("Mod {}({}) is not installed\n",var.m_id, var.m_version.getUnderlyingVersion()); + } + } + } } - - GEODE_UNWRAP(root.ok()); - - return Ok(info); + if (realError.empty()) return Ok(); + return Err(realError); +} +Result PackInfo::from(matjson::Value const &json) { + auto info = PackInfo(); + + auto copyJson = json; + auto root = checkJson(copyJson, "[pack.json]"); + + auto target = root.needs("textureldr").get(); + + GEODE_UNWRAP(root.ok()); + /* + // old way of checking + auto current = Mod::get()->getVersion(); + if (target > VersionInfo(current.getMajor(), current.getMinor(), 99999999)) { + return Err("Pack targets newer version of TextureLdr"); + } + */ + info.m_textureldr = target; + root.needs("name").into(info.m_name); + root.needs("id").into(info.m_id); + root.needs("version").into(info.m_version); + + // has single "author" key? + if (auto author = root.has("author")) { + std::string temp; + author.into(temp); + info.m_authors = {temp}; + } + // otherwise use "authors" key + else { + root.needs("authors").into(info.m_authors); + } + + if (auto loadif = root.has("load-if")) { + auto loadifVal = loadif.get("load-if"); // i couldn't get unwrapordefault working?? + if (loadifVal.isObject() || loadifVal.isArray()) { + for (auto const &[ModID, Json] : loadifVal) { + auto modinfo = ModInfo(); + modinfo.m_id = ModID; + if (Json.contains("incompatible")) { + modinfo.m_incompatible = Json["incompatible"].asBool().unwrapOrDefault(); + }; + if (Json.contains("require")) { + modinfo.m_required = Json["require"].asBool().unwrapOrDefault(); + }; + if (Json.contains("version")) { + modinfo.m_version = Json["version"].as().unwrapOrDefault(); + }; + info.m_mods.push_back(modinfo); + } + } else { + return Err("Pack load-if isn't a object list"); + } + } + + GEODE_UNWRAP(root.ok()); + + return Ok(info); } std::filesystem::path Pack::getOriginPath() const { - return m_path; + return m_path; } std::filesystem::path Pack::getResourcesPath() const { - return m_resourcesPath; + return m_resourcesPath; } std::string Pack::getID() const { - return m_info.has_value() ? - m_info.value().m_id : - string::pathToString(m_path.filename()); + return m_info.has_value() ? m_info.value().m_id : string::pathToString(m_path.filename()); } std::string Pack::getDisplayName() const { - return m_info.has_value() ? - m_info.value().m_name : - string::pathToString(m_path.filename()); + return m_info.has_value() ? m_info.value().m_name : string::pathToString(m_path.filename()); } std::optional Pack::getInfo() const { - return m_info; + return m_info; } Result<> Pack::apply() { - CCFileUtils::get()->addTexturePack(CCTexturePack { - .m_id = this->getID(), - .m_paths = { string::pathToString(this->getResourcesPath()) } - }); - return Ok(); + CCFileUtils::get()->addTexturePack(CCTexturePack{ + .m_id = this->getID(), + .m_paths = {string::pathToString(this->getResourcesPath())}}); + return Ok(); } Result<> Pack::unapply() const { - CCFileUtils::get()->removeTexturePack(this->getID()); - return Ok(); + CCFileUtils::get()->removeTexturePack(this->getID()); + return Ok(); } Result<> Pack::parsePackJson() { - try { - GEODE_UNWRAP_INTO(auto json, file::readJson(m_resourcesPath / "pack.json")); - GEODE_UNWRAP_INTO(m_info, PackInfo::from(json)); - return Ok(); - } catch(std::exception& e) { - return Err("Unable to parse pack.json: {}", e.what()); - } + GEODE_UNWRAP_INTO(auto json, file::readJson(m_resourcesPath / "pack.json")); + GEODE_UNWRAP_INTO(m_info, PackInfo::from(json)); + return Ok(); } Result<> Pack::setup() { - m_unzippedPath = m_path; - m_resourcesPath = m_path; - GEODE_UNWRAP(this->extract()); - auto optPath = this->findResourcesPath(m_unzippedPath); - if (optPath) { - m_resourcesPath = *optPath; - } - // TODO: read this from the zip before extracting.. somehow - if (std::filesystem::exists(m_resourcesPath / "pack.json")) { - GEODE_UNWRAP(this->parsePackJson()); - } - return Ok(); + m_unzippedPath = m_path; + m_resourcesPath = m_path; + GEODE_UNWRAP(this->extract()); + auto optPath = this->findResourcesPath(m_unzippedPath); + if (optPath) { + m_resourcesPath = *optPath; + } + // TODO: read this from the zip before extracting.. somehow + if (std::filesystem::exists(m_resourcesPath / "pack.json")) { + GEODE_UNWRAP(this->parsePackJson()); + } + return Ok(); } Result<> Pack::extract() { - // this method is only for zips and stuff - if (std::filesystem::is_directory(m_path)) return Ok(); - - auto const fileExt = string::pathToString(m_path.extension()); - // TODO: we dont support rar, lol - if (fileExt != ".zip" && fileExt != ".apk") { - return Err("Expected zip or apk"); - } - - auto extractPath = Mod::get()->getSaveDir() / "unzipped" / this->getID(); - (void) utils::file::createDirectoryAll(extractPath); - - auto datePath = extractPath / "modified-at"; - std::string currentHash = file::readString(datePath).unwrapOr(""); - - std::error_code ec; - auto modifiedDate = std::filesystem::last_write_time(m_path, ec); - if (ec) { - return Err("Unable get last_write_time: {}", ec.message()); - } - auto modifiedCount = std::chrono::duration_cast(modifiedDate.time_since_epoch()); - auto modifiedHash = std::to_string(modifiedCount.count()); - if (currentHash == modifiedHash) { - m_unzippedPath = extractPath; - return Ok(); - } - log::debug("Hash mismatch detected, unzipping {}", this->getID()); - - std::filesystem::remove_all(extractPath, ec); - if (ec) { - return Err("Unable to delete temp dir: {}", ec.message()); - } - - (void) utils::file::createDirectoryAll(extractPath); - auto res = file::writeString(datePath, modifiedHash); - if (!res) { - log::warn("Failed to write modified date of extracted pack: {}", res.unwrapErr()); - } - - GEODE_UNWRAP_INTO(auto unzip, file::Unzip::create(m_path)); - GEODE_UNWRAP(unzip.extractAllTo(extractPath)); - - m_unzippedPath = extractPath; - m_resourcesPath = extractPath; - - return Ok(); + // this method is only for zips and stuff + if (std::filesystem::is_directory(m_path)) + return Ok(); + + auto const fileExt = string::pathToString(m_path.extension()); + // TODO: we dont support rar, lol + if (fileExt != ".zip" && fileExt != ".apk") { + return Err("Expected zip or apk"); + } + + auto extractPath = Mod::get()->getSaveDir() / "unzipped" / this->getID(); + (void)utils::file::createDirectoryAll(extractPath); + + auto datePath = extractPath / "modified-at"; + std::string currentHash = file::readString(datePath).unwrapOr(""); + + std::error_code ec; + auto modifiedDate = std::filesystem::last_write_time(m_path, ec); + if (ec) { + return Err("Unable get last_write_time: {}", ec.message()); + } + auto modifiedCount = std::chrono::duration_cast(modifiedDate.time_since_epoch()); + auto modifiedHash = std::to_string(modifiedCount.count()); + if (currentHash == modifiedHash) { + m_unzippedPath = extractPath; + return Ok(); + } + log::debug("Hash mismatch detected, unzipping {}", this->getID()); + + std::filesystem::remove_all(extractPath, ec); + if (ec) { + return Err("Unable to delete temp dir: {}", ec.message()); + } + + (void)utils::file::createDirectoryAll(extractPath); + auto res = file::writeString(datePath, modifiedHash); + if (!res) { + log::warn("Failed to write modified date of extracted pack: {}", res.unwrapErr()); + } + + GEODE_UNWRAP_INTO(auto unzip, file::Unzip::create(m_path)); + GEODE_UNWRAP(unzip.extractAllTo(extractPath)); + + m_unzippedPath = extractPath; + m_resourcesPath = extractPath; + + return Ok(); } std::optional Pack::findResourcesPath(std::filesystem::path targetPath) { - // Packs are often distributed in weird ways, this code tries to find where the resources actually are.. - - if (string::pathToString(m_path.extension()) == ".apk") { - // resources can only be in one place! very easy - return m_unzippedPath / "assets"; - } - - if (std::filesystem::exists(targetPath / "pack.json") || std::filesystem::exists(targetPath / "pack.png")) { - // this pack is made for texture loader, so it should be correct already - return targetPath; - } - - const auto existsDir = [](auto path) { - return std::filesystem::exists(path) && std::filesystem::is_directory(path); - }; - - if (existsDir(targetPath / "Resources")) { - // its probably there, i hope - return targetPath / "Resources"; - } - - // 2.2 icons folder - if (existsDir(targetPath / "icons")) { - return targetPath; - } - - // Look for any plist files, or png files ending in -uhd -hd or starting in GJ_ - - for (auto const& file : std::filesystem::directory_iterator(targetPath, std::filesystem::directory_options::skip_permission_denied)) { - if (!file.is_regular_file()) continue; - - auto const path = file.path(); - auto const name = string::pathToString(path.stem()); - auto const ext = string::pathToString(path.extension()); - - if (ext == ".plist") { - return targetPath; - } else if (ext == ".png" && (name.starts_with("GJ_") || name.ends_with("-hd") || name.ends_with("-uhd"))) { - return targetPath; - } else if (ext == ".ogg") { - return targetPath; - } else if (ext == ".mp3") { - return targetPath; - } - } - - // ok, look recursively through the folders then - for (auto const& dir : std::filesystem::directory_iterator(targetPath, std::filesystem::directory_options::skip_permission_denied)) { - if (!dir.is_directory()) continue; - - auto const path = dir.path(); - - // TODO: this might skip over texture packs that set geode mod textures.. - // though, they should be using pack.json or pack.png anyways! - auto const opt = this->findResourcesPath(path); - if (opt) { - return *opt; - } - } - - return std::nullopt; + // Packs are often distributed in weird ways, this code tries to find where the resources actually are.. + + if (string::pathToString(m_path.extension()) == ".apk") { + // resources can only be in one place! very easy + return m_unzippedPath / "assets"; + } + + if (std::filesystem::exists(targetPath / "pack.json") || std::filesystem::exists(targetPath / "pack.png")) { + // this pack is made for texture loader, so it should be correct already + return targetPath; + } + + const auto existsDir = [](auto path) { + return std::filesystem::exists(path) && std::filesystem::is_directory(path); + }; + + if (existsDir(targetPath / "Resources")) { + // its probably there, i hope + return targetPath / "Resources"; + } + + // 2.2 icons folder + if (existsDir(targetPath / "icons")) { + return targetPath; + } + + // Look for any plist files, or png files ending in -uhd -hd or starting in GJ_ + + for (auto const &file : std::filesystem::directory_iterator(targetPath, std::filesystem::directory_options::skip_permission_denied)) { + if (!file.is_regular_file()) + continue; + + auto const path = file.path(); + auto const name = string::pathToString(path.stem()); + auto const ext = string::pathToString(path.extension()); + + if (ext == ".plist") { + return targetPath; + } else if (ext == ".png" && (name.starts_with("GJ_") || name.ends_with("-hd") || name.ends_with("-uhd"))) { + return targetPath; + } else if (ext == ".ogg") { + return targetPath; + } else if (ext == ".mp3") { + return targetPath; + } + } + + // ok, look recursively through the folders then + for (auto const &dir : std::filesystem::directory_iterator(targetPath, std::filesystem::directory_options::skip_permission_denied)) { + if (!dir.is_directory()) + continue; + + auto const path = dir.path(); + + // TODO: this might skip over texture packs that set geode mod textures.. + // though, they should be using pack.json or pack.png anyways! + auto const opt = this->findResourcesPath(path); + if (opt) { + return *opt; + } + } + + return std::nullopt; } Pack::~Pack() { - (void)this->unapply(); + (void)this->unapply(); } -Result> Pack::from(std::filesystem::path const& dir) { - if (!std::filesystem::exists(dir)) { - return Err("Path does not exist"); - } - auto pack = std::make_shared(); - pack->m_path = dir; - GEODE_UNWRAP(pack->setup()); - return Ok(pack); +Result> Pack::from(std::filesystem::path const &dir) { + if (!std::filesystem::exists(dir)) { + return Err("Path does not exist"); + } + auto pack = std::make_shared(); + pack->m_path = dir; + GEODE_UNWRAP(pack->setup()); + return Ok(pack); } -matjson::Value matjson::Serialize>::toJson(std::shared_ptr const& pack) { - return matjson::makeObject({ - { "path", pack->getOriginPath() } - }); +matjson::Value matjson::Serialize>::toJson(std::shared_ptr const &pack) { + return matjson::makeObject({{"path", pack->getOriginPath()}}); } -Result> matjson::Serialize>::fromJson(matjson::Value const& value) { - GEODE_UNWRAP_INTO(auto path, value["path"].as()); - GEODE_UNWRAP_INTO(auto pack, Pack::from(path)); - return Ok(pack); +Result> matjson::Serialize>::fromJson(matjson::Value const &value) { + GEODE_UNWRAP_INTO(auto path, value["path"].as()); + GEODE_UNWRAP_INTO(auto pack, Pack::from(path)); + return Ok(pack); } diff --git a/src/Pack.hpp b/src/Pack.hpp index aecd877..b26acac 100644 --- a/src/Pack.hpp +++ b/src/Pack.hpp @@ -9,6 +9,12 @@ #include using namespace geode::prelude; +struct ModInfo { + ComparableVersionInfo m_version; // default to all versions of the mod + std::string m_id; + bool m_incompatible; + bool m_required = true; // default to needing +}; struct PackInfo { VersionInfo m_textureldr; @@ -16,7 +22,7 @@ struct PackInfo { std::string m_name; VersionInfo m_version; std::vector m_authors; - + std::vector m_mods; static Result from(matjson::Value const& json); }; @@ -45,6 +51,8 @@ class Pack { [[nodiscard]] Result<> apply(); [[nodiscard]] Result<> unapply() const; + // returns if the pack has any reasons to not load (e.g texture loader outdated), Ok() if it can load, Err(reason) if it cannot + [[nodiscard]] Result<> canLoad(); ~Pack(); diff --git a/src/PackManager.cpp b/src/PackManager.cpp index 54fa01a..704d040 100644 --- a/src/PackManager.cpp +++ b/src/PackManager.cpp @@ -23,12 +23,22 @@ std::vector> PackManager::getAppliedPacks() const { return m_applied; } +std::vector> PackManager::getFailedPacks() const { + return m_loadfailed; +} + void PackManager::movePackToIdx(const std::shared_ptr& pack, PackListType to, size_t index) { - auto& destination = to == PackListType::Applied ? m_applied : m_available; + auto& destination = + to == PackListType::Applied ? m_applied : + to == PackListType::Available ? m_available : + m_loadfailed; if (ranges::contains(destination, pack)) { ranges::move(destination, pack, index); } else { - auto& from = to != PackListType::Applied ? m_applied : m_available; + auto& from = + to == PackListType::Applied ? m_applied : + to == PackListType::Available ? m_available : + m_loadfailed; ranges::remove(from, pack); if (index < destination.size()) { destination.insert(destination.begin() + static_cast(index), pack); @@ -53,6 +63,7 @@ size_t PackManager::loadPacks() { size_t loaded = 0; std::vector> found; + std::vector> failed; // Load new packs for (auto& dir : std::filesystem::directory_iterator(packDir)) { @@ -60,8 +71,13 @@ size_t PackManager::loadPacks() { if (!packRes) { log::warn("Unable to load pack {}: {}", string::pathToString(dir.path()), packRes.unwrapErr()); } else { - found.push_back(packRes.unwrap()); - loaded++; + auto wpackRes = packRes.unwrap(); + if (wpackRes->canLoad().isOk()){ + found.push_back(wpackRes); + loaded++; + } else { + failed.push_back(wpackRes); + }; } } @@ -75,7 +91,8 @@ size_t PackManager::loadPacks() { if(auto pathRes = obj["path"].as()) { auto res = Pack::from(pathRes.unwrap()); if (res) { - savedApplied.push_back(res.unwrap()); + auto Wres = res.unwrap(); // check if it can load + if (Wres->canLoad().isOk()) savedApplied.push_back(Wres); } } } @@ -96,15 +113,24 @@ size_t PackManager::loadPacks() { m_applied = newApplied; m_available = found; + m_loadfailed = failed; this->updateAppliedPacks(); log::info("Loaded {} packs", loaded); + if (m_loadfailed.size() > 0) { + log::info("failed to load {} packs", m_loadfailed.size()); + }; + return loaded; } void PackManager::updateAppliedPacks() { + // just in case + for (auto& pack : m_loadfailed) { + (void)pack->unapply(); + } for (auto& pack : m_available) { (void)pack->unapply(); } diff --git a/src/PackManager.hpp b/src/PackManager.hpp index 3ce8ae5..abcf8e7 100644 --- a/src/PackManager.hpp +++ b/src/PackManager.hpp @@ -4,12 +4,14 @@ #include enum class PackListType { + Blocked, Available, Applied }; class PackManager { protected: + std::vector> m_loadfailed; std::vector> m_available; std::vector> m_applied; @@ -20,6 +22,7 @@ class PackManager { [[nodiscard]] std::vector> getAvailablePacks() const; [[nodiscard]] std::vector> getAppliedPacks() const; + [[nodiscard]] std::vector> getFailedPacks() const; void movePackToIdx(const std::shared_ptr& pack, PackListType to, size_t index); diff --git a/src/PackNode.cpp b/src/PackNode.cpp index dff44ec..ed8cc1f 100644 --- a/src/PackNode.cpp +++ b/src/PackNode.cpp @@ -24,6 +24,7 @@ bool PackNode::init( m_pack = pack; m_layer = layer; + auto Canload = m_pack->canLoad(); this->setID("PackNode"); @@ -70,21 +71,44 @@ bool PackNode::init( nameButton->setContentWidth(nameLabel->getScaledContentWidth()); nameButton->setEnabled(false); menu->addChild(nameButton); - - auto applyArrowSpr = CCSprite::create("dragIcon.png"_spr); - applyArrowSpr->setScale(.6f); - - DragThingy* dragHandle = DragThingy::create( - [=, this] { - m_draggingBg->setVisible(true); - m_layer->startDragging(this); - }, - [=, this] (const CCPoint& offset) { m_layer->moveDrag(offset); }, - [=, this] { - m_draggingBg->setVisible(false); - m_layer->stopDrag(); - } - ); + if (Canload.isOk()) { + DragThingy* dragHandle = DragThingy::create( + [=, this] { + m_draggingBg->setVisible(true); + m_layer->startDragging(this); + }, + [=, this] (const CCPoint& offset) { m_layer->moveDrag(offset); }, + [=, this] { + m_draggingBg->setVisible(false); + m_layer->stopDrag(); + } + ); + auto applyArrowSpr = CCSprite::create("dragIcon.png"_spr); + applyArrowSpr->setScale(.6f); + applyArrowSpr->setAnchorPoint(ccp(0, 0)); + dragHandle->addChild(applyArrowSpr); + dragHandle->setContentSize(applyArrowSpr->getScaledContentSize()); + dragHandle->setID("apply-pack-button"); + dragHandle->setPosition(width - MOVE_OFFSET, HEIGHT / 2.f); + dragHandle->setTouchPriority(-130); + this->addChild(dragHandle); + } else { + log::debug("cannot apply forced"); + auto errorIcon = CCSprite::createWithSpriteFrameName("geode.loader/info-warning.png"); + errorIcon->setScale(.8f); + errorIcon->setAnchorPoint(ccp(0, 0)); + std::string error = Canload.isErr() ? Canload.unwrapErr() : "unknown error"; + auto errorButton = CCMenuItemExt::createSpriteExtra(errorIcon, [error](auto btn){ + MDPopup::create( + "Failed to load", + error, + "OK" + )->show(); + }); + errorButton->setID("error-pack-info-button"); + errorButton->setPosition(width - MOVE_OFFSET, HEIGHT / 2.f); + menu->addChild(errorButton); + } if (!m_pack->getInfo().has_value()) { nameButton->setPosition({40 + nameButton->getContentWidth()/2, this->getContentHeight()/2}); @@ -115,14 +139,6 @@ bool PackNode::init( nameButton->setPosition({40 + nameButton->getContentWidth()/2, this->getContentHeight() - 9.5f}); } - applyArrowSpr->setAnchorPoint(ccp(0, 0)); - dragHandle->addChild(applyArrowSpr); - dragHandle->setContentSize(applyArrowSpr->getScaledContentSize()); - dragHandle->setID("apply-pack-button"); - dragHandle->setPosition(width - MOVE_OFFSET, HEIGHT / 2.f); - dragHandle->setTouchPriority(-130); - - this->addChild(dragHandle); m_draggingBg = NineSlice::create( "square02b_001.png" diff --git a/src/PackSelectPopup.cpp b/src/PackSelectPopup.cpp index 4e151e4..0003a87 100644 --- a/src/PackSelectPopup.cpp +++ b/src/PackSelectPopup.cpp @@ -72,6 +72,19 @@ bool PackSelectPopup::init() { reloadBtn->setID("reload-button"); reloadBtn->setPosition({30, 25}); + auto warninginfoON = CCSprite::createWithSpriteFrameName("geode.loader/info-warning.png"); + warninginfoON->setScale(.7f); + auto warninginfoOFF = CCSprite::createWithSpriteFrameName("geode.loader/info-warning.png"); + warninginfoOFF->setScale(.7f); + warninginfoOFF->setOpacity(80); + m_errortoggler = CCMenuItemExt::createToggler(warninginfoON, warninginfoOFF, [this](auto btn){ + m_showerrors = !btn->isToggled(); + PackSelectPopup::updateLists(true); + }); + m_errortoggler->setID("errors-button"); + m_errortoggler->setPosition({size.width - 60, 25}); + m_buttonMenu->addChild(m_errortoggler); + m_buttonMenu->addChild(reloadBtn); auto applySpr = ButtonSprite::create("Apply", "goldFont.fnt", "GJ_button_01.png", .8f); applySpr->setScale(0.9f); @@ -188,11 +201,35 @@ void PackSelectPopup::updateList( int availCount = PackManager::get()->getAvailablePacks().size(); int appliedCount = PackManager::get()->getAppliedPacks().size(); - m_infoLabel->setString(fmt::format("Available: {}\nApplied: {}\nTotal: {}", availCount, appliedCount, availCount + appliedCount).c_str()); -} - + int failedCount = PackManager::get()->getFailedPacks().size(); + if (failedCount <= 0) { + if (m_errortoggler) { + m_errortoggler->setVisible(false); + m_showerrors = false; + } + } else { + if (m_errortoggler) m_errortoggler->setVisible(true); + } + m_infoLabel->setString( + fmt::format( + "Available: {}\nApplied: {}\nTotal: {}{}", + availCount, + appliedCount, + availCount + appliedCount, + failedCount > 0 ? fmt::format("\nFailed: {}", failedCount) : "" + ).c_str()); +}; void PackSelectPopup::updateLists(bool resetPos) { - this->updateList(m_availableList, PackManager::get()->getAvailablePacks(), resetPos); + auto l_availablepacks = PackManager::get()->getAvailablePacks(); + if (m_showerrors) { + auto l_failed = PackManager::get()->getFailedPacks(); + l_availablepacks.insert( + l_availablepacks.end(), + std::make_move_iterator(l_failed.begin()), + std::make_move_iterator(l_failed.end()) + ); + } + this->updateList(m_availableList, l_availablepacks, resetPos); this->updateList(m_appliedList, PackManager::get()->getAppliedPacks(), resetPos); } @@ -231,10 +268,13 @@ std::pair PackSelectPopup::getPackListTypeAndIndex(const s return { PackListType::Available, static_cast(std::distance(available.begin(), it)) }; } void PackSelectPopup::startDragging(PackNode* node) { + auto pack = node->getPack(); + + if (pack->canLoad().isErr()) return; m_draggingNode = node; // set initial information, else clicking with no drag can cause it to move to available - auto packData = getPackListTypeAndIndex(node->getPack()); + auto packData = getPackListTypeAndIndex(pack); m_dragListTo = packData.first; m_lastDragIdx = packData.second; auto const pos = node->getParent()->convertToWorldSpace(node->getPosition()); @@ -300,9 +340,19 @@ void PackSelectPopup::scrollOnDrag(PackListType type, bool up) { void PackSelectPopup::reorderDragging() { auto const listTypeTo = this->whereDragList(); + auto l_availablepacks = PackManager::get()->getAvailablePacks(); + + if (m_showerrors) { + auto l_failed = PackManager::get()->getFailedPacks(); + l_availablepacks.insert( + l_availablepacks.end(), + std::make_move_iterator(l_failed.begin()), + std::make_move_iterator(l_failed.end()) + ); + } auto appliedList = std::make_pair(m_appliedList, PackManager::get()->getAppliedPacks()); - auto availableList = std::make_pair(m_availableList, PackManager::get()->getAvailablePacks()); + auto availableList = std::make_pair(m_availableList, l_availablepacks); auto& listTo = listTypeTo == PackListType::Applied ? appliedList : availableList; auto& listFrom = listTypeTo != PackListType::Applied ? appliedList : availableList; diff --git a/src/PackSelectPopup.hpp b/src/PackSelectPopup.hpp index f500c3f..cef77a0 100644 --- a/src/PackSelectPopup.hpp +++ b/src/PackSelectPopup.hpp @@ -16,6 +16,8 @@ class PackSelectPopup : public Popup { ScrollLayer* m_appliedList = nullptr; CCLabelBMFont* m_infoLabel = nullptr; PackNode* m_draggingNode = nullptr; + bool m_showerrors = false; + CCMenuItemToggler* m_errortoggler = nullptr; size_t m_lastDragIdx = size_t(-1); PackListType m_dragListFrom, m_dragListTo; From 256e902904209b0d00bcaa5465057f43c2ba674b Mon Sep 17 00:00:00 2001 From: Cosmella-v Date: Tue, 7 Apr 2026 12:01:14 +0100 Subject: [PATCH 2/3] bump fixes --- src/Pack.cpp | 150 ++++++++++++++++++++++++---------------- src/PackManager.cpp | 36 ++++++---- src/PackNode.cpp | 1 - src/PackSelectPopup.cpp | 7 +- 4 files changed, 121 insertions(+), 73 deletions(-) diff --git a/src/Pack.cpp b/src/Pack.cpp index 13e1083..5a41486 100644 --- a/src/Pack.cpp +++ b/src/Pack.cpp @@ -12,50 +12,84 @@ Result<> Pack::canLoad() { return Err("Pack targets a newer version of Texture Loader ({})", info.m_textureldr); }; std::string realError = ""; - auto* loader = Loader::get(); - - for (const auto& var : info.m_mods) { - if (var.m_required) { - auto mod = loader->getLoadedMod(var.m_id); - if (var.m_incompatible) { - if (mod) { - if (!var.m_version.compare(mod->getVersion())) { - continue; - } - realError += fmt::format("Mod {} is Installed\n",var.m_id); - } - } - else { - if (mod) { - auto version = mod->getVersion(); - auto resonned = var.m_version.compareWithReason(version); - if (resonned == VersionCompareResult::Match) { - continue; - } - const char* reason = "version mismatch"; - - switch (resonned) { - case VersionCompareResult::TooOld: - realError += fmt::format("Current version of the mod {} is older than the version provided ({} < {})\n",var.m_id, version, var.m_version.getUnderlyingVersion()); - break; - case VersionCompareResult::TooNew: - realError += fmt::format("Current version of the mod {} is newer than the version provided ({} > {})\n",var.m_id, version, var.m_version.getUnderlyingVersion()); - break; - case VersionCompareResult::MajorMismatch: - realError += fmt::format("Current version of the mod {} has an incompatible major version with the version provided ({} != {})\n",var.m_id, version.getMajor(), var.m_version.getUnderlyingVersion().getMajor()); - break; - default: - realError += fmt::format("Current version of the mod {} version mismatch ({} {}/c>)\n",var.m_id, version, var.m_version); - break; - } - } else { - realError += fmt::format("Mod {}({}) is not installed\n",var.m_id, var.m_version.getUnderlyingVersion()); - } - } - } - } - if (realError.empty()) return Ok(); - return Err(realError); + auto *loader = Loader::get(); + + for (const auto &var : info.m_mods) { + if (var.m_required) { + auto mod = loader->getLoadedMod(var.m_id); + if (var.m_incompatible) { + if (mod) { + if (!var.m_version.compare(mod->getVersion())) { + continue; + } + realError += fmt::format("Mod {} is Installed\n", var.m_id); + } + } else { + if (mod) { + auto version = mod->getVersion(); + auto resonned = var.m_version.compareWithReason(version); + if (resonned == VersionCompareResult::Match) { + continue; + } + + const char *detail = "version mismatch"; + const char *op = "?"; + + switch (var.m_version.getComparison()) { + case VersionCompare::Less: + op = "<"; + break; + case VersionCompare::More: + op = ">"; + break; + case VersionCompare::LessEq: + op = "<="; + break; + case VersionCompare::MoreEq: + op = ">="; + break; + case VersionCompare::Exact: + op = "=="; + break; + case VersionCompare::Any: + op = "*"; + break; + } + + switch (resonned) { + case VersionCompareResult::TooOld: + detail = "older"; + break; + + case VersionCompareResult::TooNew: + detail = "newer"; + break; + + case VersionCompareResult::MajorMismatch: + detail = "majorly incompatible"; + op = "!="; + break; + + default: + break; + } + + realError += fmt::format( + "Current version of the mod {} is {} than the version provided ({} {} {})\n", + var.m_id, + detail, + version, + op, + var.m_version.getUnderlyingVersion()); + } else { + realError += fmt::format("Mod {}({}) is not installed\n", var.m_id, var.m_version.getUnderlyingVersion()); + } + } + } + } + if (realError.empty()) + return Ok(); + return Err(realError); } Result PackInfo::from(matjson::Value const &json) { auto info = PackInfo(); @@ -92,20 +126,20 @@ Result PackInfo::from(matjson::Value const &json) { if (auto loadif = root.has("load-if")) { auto loadifVal = loadif.get("load-if"); // i couldn't get unwrapordefault working?? if (loadifVal.isObject() || loadifVal.isArray()) { - for (auto const &[ModID, Json] : loadifVal) { - auto modinfo = ModInfo(); - modinfo.m_id = ModID; - if (Json.contains("incompatible")) { - modinfo.m_incompatible = Json["incompatible"].asBool().unwrapOrDefault(); - }; - if (Json.contains("require")) { - modinfo.m_required = Json["require"].asBool().unwrapOrDefault(); - }; - if (Json.contains("version")) { - modinfo.m_version = Json["version"].as().unwrapOrDefault(); - }; - info.m_mods.push_back(modinfo); - } + for (auto const &[ModID, Json] : loadifVal) { + auto modinfo = ModInfo(); + modinfo.m_id = ModID; + if (Json.contains("incompatible")) { + modinfo.m_incompatible = Json["incompatible"].asBool().unwrapOrDefault(); + }; + if (Json.contains("require")) { + modinfo.m_required = Json["require"].asBool().unwrapOrDefault(); + }; + if (Json.contains("version")) { + modinfo.m_version = Json["version"].as().unwrapOrDefault(); + }; + info.m_mods.push_back(modinfo); + } } else { return Err("Pack load-if isn't a object list"); } diff --git a/src/PackManager.cpp b/src/PackManager.cpp index 704d040..3a1b9eb 100644 --- a/src/PackManager.cpp +++ b/src/PackManager.cpp @@ -28,26 +28,36 @@ std::vector> PackManager::getFailedPacks() const { } void PackManager::movePackToIdx(const std::shared_ptr& pack, PackListType to, size_t index) { - auto& destination = - to == PackListType::Applied ? m_applied : - to == PackListType::Available ? m_available : - m_loadfailed; + auto& destination = to == PackListType::Applied ? m_applied : to == PackListType::Available ? m_available : m_loadfailed; if (ranges::contains(destination, pack)) { ranges::move(destination, pack, index); } else { - auto& from = - to == PackListType::Applied ? m_applied : - to == PackListType::Available ? m_available : - m_loadfailed; - ranges::remove(from, pack); - if (index < destination.size()) { - destination.insert(destination.begin() + static_cast(index), pack); + auto& from = to == PackListType::Applied ? m_available : to == PackListType::Available ? m_applied : m_loadfailed; + if (ranges::contains(from, pack)) { + ranges::remove(from, pack); + if (index < destination.size()) { + destination.insert(destination.begin() + static_cast(index), pack); + } else { + destination.push_back(pack); + } } else { - destination.push_back(pack); + // failsafe just in case it isn't there? + auto removeFromIfPresent = [&](auto& list) { + if (ranges::contains(list, pack)) { + ranges::remove(list, pack); + return true; + } + return false; + }; + if (!removeFromIfPresent(m_applied)) if(!removeFromIfPresent(m_available)) removeFromIfPresent(m_loadfailed); + if (index < destination.size()) { + destination.insert(destination.begin() + static_cast(index), pack); + } else { + destination.push_back(pack); + } } } } - void PackManager::savePacks() { Mod::get()->setSavedValue("applied", m_applied); } diff --git a/src/PackNode.cpp b/src/PackNode.cpp index ed8cc1f..10454fa 100644 --- a/src/PackNode.cpp +++ b/src/PackNode.cpp @@ -93,7 +93,6 @@ bool PackNode::init( dragHandle->setTouchPriority(-130); this->addChild(dragHandle); } else { - log::debug("cannot apply forced"); auto errorIcon = CCSprite::createWithSpriteFrameName("geode.loader/info-warning.png"); errorIcon->setScale(.8f); errorIcon->setAnchorPoint(ccp(0, 0)); diff --git a/src/PackSelectPopup.cpp b/src/PackSelectPopup.cpp index 0003a87..ae3d06c 100644 --- a/src/PackSelectPopup.cpp +++ b/src/PackSelectPopup.cpp @@ -206,6 +206,7 @@ void PackSelectPopup::updateList( if (m_errortoggler) { m_errortoggler->setVisible(false); m_showerrors = false; + m_errortoggler->toggle(m_showerrors); } } else { if (m_errortoggler) m_errortoggler->setVisible(true); @@ -257,13 +258,17 @@ void PackSelectPopup::onReloadPacks(CCObject*) { std::pair PackSelectPopup::getPackListTypeAndIndex(const std::shared_ptr& pack) { auto manager = PackManager::get(); const auto& applied = manager->getAppliedPacks(); + const auto& locked = manager->getFailedPacks(); const auto& available = manager->getAvailablePacks(); auto it = std::find(applied.begin(), applied.end(), pack); if (it != applied.end()) { return { PackListType::Applied, static_cast(std::distance(applied.begin(), it)) }; } - + it = std::find(locked.begin(), locked.end(), pack); + if (it != locked.end()) { + return { PackListType::Blocked, static_cast(std::distance(locked.begin(), it)) }; + } it = std::find(available.begin(), available.end(), pack); return { PackListType::Available, static_cast(std::distance(available.begin(), it)) }; } From 5a5852e372d5a0db633ab5953f952d2286234323 Mon Sep 17 00:00:00 2001 From: Cosmella-v Date: Tue, 7 Apr 2026 12:18:55 +0100 Subject: [PATCH 3/3] flip dam signs --- src/Pack.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Pack.cpp b/src/Pack.cpp index 5a41486..38b51ae 100644 --- a/src/Pack.cpp +++ b/src/Pack.cpp @@ -37,22 +37,22 @@ Result<> Pack::canLoad() { switch (var.m_version.getComparison()) { case VersionCompare::Less: - op = "<"; + op = ">"; break; case VersionCompare::More: - op = ">"; + op = "<"; break; case VersionCompare::LessEq: - op = "<="; + op = ">"; break; case VersionCompare::MoreEq: - op = ">="; + op = "<"; break; case VersionCompare::Exact: - op = "=="; + op = "!="; break; case VersionCompare::Any: - op = "*"; + op = "*="; break; }