huge dependency stuff refactor

This commit is contained in:
ConfiG 2023-08-08 23:40:14 +03:00
parent 7f449b996e
commit 5200128544
No known key found for this signature in database
GPG key ID: 44DA1983F524C11B
22 changed files with 907 additions and 710 deletions

View file

@ -115,6 +115,7 @@ namespace geode {
std::unordered_set<PlatformID> getAvailablePlatforms() const;
bool isFeatured() const;
std::unordered_set<std::string> getTags() const;
bool isInstalled() const;
IndexItem();
~IndexItem();

View file

@ -4,6 +4,7 @@
#include "../utils/Result.hpp"
#include "../utils/MiniFunction.hpp"
#include "Log.hpp"
#include "ModEvent.hpp"
#include "ModInfo.hpp"
#include "ModMetadata.hpp"
#include "Types.hpp"
@ -19,6 +20,24 @@ namespace geode {
std::string reason;
};
struct LoadProblem {
enum class Type : uint8_t {
Unknown,
Suggestion,
Recommendation,
InvalidFile,
Duplicate,
SetupFailed,
LoadFailed,
EnableFailed,
MissingDependency,
PresentIncompatibility
};
Type type;
std::variant<ghc::filesystem::path, ModMetadata, Mod*> cause;
std::string message;
};
class LoaderImpl;
class GEODE_DLL Loader {
@ -37,8 +56,8 @@ namespace geode {
void dispatchScheduledFunctions(Mod* mod);
friend void GEODE_CALL ::geode_implicit_load(Mod*);
Result<Mod*> loadModFromInfo(ModInfo const& info);
[[deprecated]] Result<Mod*> loadModFromInfo(ModInfo const& info);
Mod* takeNextMod();
public:
@ -53,16 +72,18 @@ namespace geode {
VersionInfo maxModVersion();
bool isModVersionSupported(VersionInfo const& version);
Result<Mod*> loadModFromFile(ghc::filesystem::path const& file);
void loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive = true);
void refreshModsList();
[[deprecated]] Result<Mod*> loadModFromFile(ghc::filesystem::path const& file);
[[deprecated]] void loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive = true);
[[deprecated]] void refreshModsList();
bool isModInstalled(std::string const& id) const;
Mod* getInstalledMod(std::string const& id) const;
bool isModLoaded(std::string const& id) const;
Mod* getLoadedMod(std::string const& id) const;
std::vector<Mod*> getAllMods();
Mod* getModImpl();
[[deprecated("use Mod::get instead")]] Mod* getModImpl();
[[deprecated]] void updateAllDependencies();
[[deprecated("use getProblems instead")]] std::vector<InvalidGeodeFile> getFailedMods() const;
std::vector<LoadProblem> getProblems() const;
void updateResources();
void updateResources(bool forceReload);

View file

@ -80,11 +80,10 @@ namespace geode {
bool isEnabled() const;
bool isLoaded() const;
bool supportsDisabling() const;
bool supportsUnloading() const;
bool wasSuccesfullyLoaded() const;
ModInfo getModInfo() const;
bool canDisable() const;
bool canEnable() const;
[[deprecated]] bool supportsUnloading() const;
[[deprecated("wasSuccessfullyLoaded")]] bool wasSuccesfullyLoaded() const;
[[deprecated("use wasSuccessfullyLoaded instead")]] bool wasSuccesfullyLoaded() const;
bool wasSuccessfullyLoaded() const;
[[deprecated("use getMetadata instead")]] ModInfo getModInfo() const;
ModMetadata getMetadata() const;
@ -299,7 +298,7 @@ namespace geode {
* @returns Successful result on success,
* errorful result with info on error
*/
Result<> loadBinary();
[[deprecated]] Result<> loadBinary();
/**
* Disable & unload this mod
@ -308,7 +307,7 @@ namespace geode {
* @returns Successful result on success,
* errorful result with info on error
*/
Result<> unloadBinary();
[[deprecated]] Result<> unloadBinary();
/**
* Enable this mod
@ -325,10 +324,7 @@ namespace geode {
Result<> disable();
/**
* Disable & unload this mod (if supported), then delete the mod's
* .geode package. If unloading isn't supported, the mod's binary
* will stay loaded, and in all cases the Mod* instance will still
* exist and be interactable.
* Disable this mod (if supported), then delete the mod's .geode package.
* @returns Successful result on success,
* errorful result with info on error
*/
@ -341,6 +337,16 @@ namespace geode {
*/
bool depends(std::string const& id) const;
/**
* Update the state of each of the
* dependencies. Depending on if the
* mod has unresolved dependencies,
* it will either be loaded or unloaded
* @returns Error.
* @deprecated No longer needed.
*/
[[deprecated("no longer needed")]] Result<> updateDependencies();
/**
* Check whether all the required
* dependencies for this mod have
@ -350,21 +356,20 @@ namespace geode {
*/
bool hasUnresolvedDependencies() const;
/**
* Update the state of each of the
* dependencies. Depending on if the
* mod has unresolved dependencies,
* it will either be loaded or unloaded
* Check whether none of the
* incompatibilities with this mod are loaded
* @returns True if the mod has unresolved
* dependencies, false if not.
* incompatibilities, false if not.
*/
Result<> updateDependencies();
bool hasUnresolvedIncompatibilities() const;
/**
* Get a list of all the unresolved
* dependencies this mod has
* @returns List of all the unresolved
* dependencies
* @deprecated Use Loader::getProblems instead.
*/
std::vector<Dependency> getUnresolvedDependencies();
[[deprecated("use Loader::getProblems instead")]] std::vector<Dependency> getUnresolvedDependencies();
char const* expandSpriteName(char const* name);

View file

@ -36,6 +36,7 @@ namespace geode {
*/
class GEODE_DLL [[deprecated("use ModMetadata instead")]] ModInfo {
class Impl;
#pragma warning(suppress : 4996)
std::unique_ptr<Impl> m_impl;
public:
@ -200,18 +201,29 @@ namespace geode {
operator ModMetadata() const;
private:
ModJson& rawJSON();
ModJson const& rawJSON() const;
/**
* Version is passed for backwards
* compatibility if we update the mod.json
* format
*/
static Result<ModInfo> createFromSchemaV010(ModJson const& json);
Result<> addSpecialFiles(ghc::filesystem::path const& dir);
Result<> addSpecialFiles(utils::file::Unzip& zip);
std::vector<std::pair<std::string, std::optional<std::string>*>> getSpecialFiles();
friend class ModInfoImpl;
friend class ModMetadata;
};
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
template <>
struct json::Serialize<geode::ModInfo> {
struct [[deprecated]] json::Serialize<geode::ModInfo> {
static json::Value to_json(geode::ModInfo const& info) {
return info.toJSON();
}
};
#pragma clang diagnostic pop

View file

@ -44,14 +44,10 @@ namespace geode {
Mod* mod = nullptr;
[[nodiscard]] bool isResolved() const;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#pragma ide diagnostic ignored "google-explicit-constructor"
operator geode::Dependency();
operator geode::Dependency() const;
[[deprecated]] operator geode::Dependency();
[[deprecated]] operator geode::Dependency() const;
static Dependency fromDeprecated(geode::Dependency const& value);
#pragma clang diagnostic pop
[[deprecated]] static Dependency fromDeprecated(geode::Dependency const& value);
};
struct GEODE_DLL Incompatibility {
@ -65,29 +61,25 @@ namespace geode {
std::string info;
std::optional<std::string> url;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#pragma ide diagnostic ignored "google-explicit-constructor"
operator geode::IssuesInfo();
operator geode::IssuesInfo() const;
[[deprecated]] operator geode::IssuesInfo();
[[deprecated]] operator geode::IssuesInfo() const;
static IssuesInfo fromDeprecated(geode::IssuesInfo const& value);
#pragma clang diagnostic pop
[[deprecated]] static IssuesInfo fromDeprecated(geode::IssuesInfo const& value);
};
/**
* Path to the mod file
*/
[[maybe_unused, nodiscard]] ghc::filesystem::path getPath() const;
[[nodiscard]] ghc::filesystem::path getPath() const;
/**
* Name of the platform binary within
* the mod zip
*/
[[maybe_unused, nodiscard]] std::string getBinaryName() const;
[[nodiscard]] std::string getBinaryName() const;
/**
* Mod Version. Should follow semantic versioning.
*/
[[maybe_unused, nodiscard]] VersionInfo getVersion() const;
[[nodiscard]] VersionInfo getVersion() const;
/**
* Human-readable ID of the Mod.
* Recommended to be in the format
@ -97,14 +89,14 @@ namespace geode {
* be restricted to the ASCII
* character set.
*/
[[maybe_unused, nodiscard]] std::string getID() const;
[[nodiscard]] std::string getID() const;
/**
* Name of the mod. May contain
* spaces & punctuation, but should
* be restricted to the ASCII
* character set.
*/
[[maybe_unused, nodiscard]] std::string getName() const;
[[nodiscard]] std::string getName() const;
/**
* The name of the head developer.
* Should be a single name, like
@ -116,98 +108,94 @@ namespace geode {
* should be named in `m_credits`
* instead.
*/
[[maybe_unused, nodiscard]] std::string getDeveloper() const;
[[nodiscard]] std::string getDeveloper() const;
/**
* Short & concise description of the
* mod.
*/
[[maybe_unused, nodiscard]] std::optional<std::string> getDescription() const;
[[nodiscard]] std::optional<std::string> getDescription() const;
/**
* Detailed description of the mod, written in Markdown (see
* <Geode/ui/MDTextArea.hpp>) for more info
*/
[[maybe_unused, nodiscard]] std::optional<std::string> getDetails() const;
[[nodiscard]] std::optional<std::string> getDetails() const;
/**
* Changelog for the mod, written in Markdown (see
* <Geode/ui/MDTextArea.hpp>) for more info
*/
[[maybe_unused, nodiscard]] std::optional<std::string> getChangelog() const;
[[nodiscard]] std::optional<std::string> getChangelog() const;
/**
* Support info for the mod; this means anything to show ways to
* support the mod's development, like donations. Written in Markdown
* (see MDTextArea for more info)
*/
[[maybe_unused, nodiscard]] std::optional<std::string> getSupportInfo() const;
[[nodiscard]] std::optional<std::string> getSupportInfo() const;
/**
* Git Repository of the mod
*/
[[maybe_unused, nodiscard]] std::optional<std::string> getRepository() const;
[[nodiscard]] std::optional<std::string> getRepository() const;
/**
* Info about where users should report issues and request help
*/
[[maybe_unused, nodiscard]] std::optional<IssuesInfo> getIssues() const;
[[nodiscard]] std::optional<IssuesInfo> getIssues() const;
/**
* Dependencies
*/
[[maybe_unused, nodiscard]] std::vector<Dependency> getDependencies() const;
[[nodiscard]] std::vector<Dependency> getDependencies() const;
/**
* Incompatibilities
*/
[[maybe_unused, nodiscard]] std::vector<Incompatibility> getIncompatibilities() const;
[[nodiscard]] std::vector<Incompatibility> getIncompatibilities() const;
/**
* Mod spritesheet names
*/
[[maybe_unused, nodiscard]] std::vector<std::string> getSpritesheets() const;
[[nodiscard]] std::vector<std::string> getSpritesheets() const;
/**
* Mod settings
* @note Not a map because insertion order must be preserved
*/
[[maybe_unused, nodiscard]] std::vector<std::pair<std::string, Setting>> getSettings() const;
[[nodiscard]] std::vector<std::pair<std::string, Setting>> getSettings() const;
/**
* Whether this mod has to be loaded before the loading screen or not
*/
[[maybe_unused, nodiscard]] bool needsEarlyLoad() const;
[[ nodiscard]] bool needsEarlyLoad() const;
/**
* Whether this mod is an API or not
*/
[[maybe_unused, nodiscard]] bool isAPI() const;
[[nodiscard]] bool isAPI() const;
/**
* Create ModInfo from an unzipped .geode package
*/
[[maybe_unused]] static Result<ModMetadata> createFromGeodeZip(utils::file::Unzip& zip);
static Result<ModMetadata> createFromGeodeZip(utils::file::Unzip& zip);
/**
* Create ModInfo from a .geode package
*/
[[maybe_unused]] static Result<ModMetadata> createFromGeodeFile(ghc::filesystem::path const& path);
static Result<ModMetadata> createFromGeodeFile(ghc::filesystem::path const& path);
/**
* Create ModInfo from a mod.json file
*/
[[maybe_unused]] static Result<ModMetadata> createFromFile(ghc::filesystem::path const& path);
static Result<ModMetadata> createFromFile(ghc::filesystem::path const& path);
/**
* Create ModInfo from a parsed json document
*/
[[maybe_unused]] static Result<ModMetadata> create(ModJson const& json);
static Result<ModMetadata> create(ModJson const& json);
/**
* Convert to JSON. Essentially same as getRawJSON except dynamically
* adds runtime fields like path
*/
[[maybe_unused, nodiscard]] ModJson toJSON() const;
[[nodiscard]] ModJson toJSON() const;
/**
* Get the raw JSON file
*/
[[maybe_unused, nodiscard]] ModJson getRawJSON() const;
[[nodiscard]] ModJson getRawJSON() const;
[[maybe_unused]] bool operator==(ModMetadata const& other) const;
bool operator==(ModMetadata const& other) const;
[[maybe_unused]] static bool validateID(std::string const& id);
static bool validateID(std::string const& id);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#pragma ide diagnostic ignored "google-explicit-constructor"
operator ModInfo();
operator ModInfo() const;
#pragma clang diagnostic pop
[[deprecated]] operator ModInfo();
[[deprecated]] operator ModInfo() const;
private:
/**
@ -226,11 +214,7 @@ namespace geode {
friend class ModMetadataImpl;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
// ModInfo => ModMetadata conversion stuff
friend class ModInfo::Impl;
#pragma clang diagnostic pop
friend class ModInfo;
};
}

View file

@ -23,34 +23,34 @@ class CustomMenuLayer;
static Ref<Notification> INDEX_UPDATE_NOTIF = nullptr;
$execute {
new EventListener<IndexUpdateFilter>(+[](IndexUpdateEvent* event) {
if (!INDEX_UPDATE_NOTIF) return;
std::visit(makeVisitor {
[](UpdateProgress const& prog) {},
[](UpdateFinished const&) {
INDEX_UPDATE_NOTIF->setIcon(NotificationIcon::Success);
INDEX_UPDATE_NOTIF->setString("Index Up-to-Date");
INDEX_UPDATE_NOTIF->waitAndHide();
INDEX_UPDATE_NOTIF = nullptr;
},
[](UpdateFailed const& info) {
INDEX_UPDATE_NOTIF->setIcon(NotificationIcon::Error);
INDEX_UPDATE_NOTIF->setString(info);
INDEX_UPDATE_NOTIF->setTime(NOTIFICATION_LONG_TIME);
INDEX_UPDATE_NOTIF = nullptr;
},
}, event->status);
});
new EventListener<IndexUpdateFilter>(+[](IndexUpdateEvent* event) {
if (!INDEX_UPDATE_NOTIF) return;
std::visit(makeVisitor {
[](UpdateProgress const& prog) {},
[](UpdateFinished const&) {
INDEX_UPDATE_NOTIF->setIcon(NotificationIcon::Success);
INDEX_UPDATE_NOTIF->setString("Index Up-to-Date");
INDEX_UPDATE_NOTIF->waitAndHide();
INDEX_UPDATE_NOTIF = nullptr;
},
[](UpdateFailed const& info) {
INDEX_UPDATE_NOTIF->setIcon(NotificationIcon::Error);
INDEX_UPDATE_NOTIF->setString(info);
INDEX_UPDATE_NOTIF->setTime(NOTIFICATION_LONG_TIME);
INDEX_UPDATE_NOTIF = nullptr;
},
}, event->status);
});
};
struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
static void onModify(auto& self) {
if (!self.setHookPriority("MenuLayer::init", GEODE_ID_PRIORITY)) {
static void onModify(auto& self) {
if (!self.setHookPriority("MenuLayer::init", GEODE_ID_PRIORITY)) {
log::warn("Failed to set MenuLayer::init hook priority, node IDs may not work properly");
}
}
CCSprite* m_geodeButton;
CCSprite* m_geodeButton;
bool init() {
if (!MenuLayer::init()) return false;
@ -61,28 +61,28 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
auto winSize = CCDirector::sharedDirector()->getWinSize();
// add geode button
m_fields->m_geodeButton = CircleButtonSprite::createWithSpriteFrameName(
"geode-logo-outline-gold.png"_spr,
1.0f,
CircleBaseColor::Green,
CircleBaseSize::MediumAlt
);
auto geodeBtnSelector = &CustomMenuLayer::onGeode;
if (!m_fields->m_geodeButton) {
geodeBtnSelector = &CustomMenuLayer::onMissingTextures;
m_fields->m_geodeButton = ButtonSprite::create("!!");
}
// add geode button
m_fields->m_geodeButton = CircleButtonSprite::createWithSpriteFrameName(
"geode-logo-outline-gold.png"_spr,
1.0f,
CircleBaseColor::Green,
CircleBaseSize::MediumAlt
);
auto geodeBtnSelector = &CustomMenuLayer::onGeode;
if (!m_fields->m_geodeButton) {
geodeBtnSelector = &CustomMenuLayer::onMissingTextures;
m_fields->m_geodeButton = ButtonSprite::create("!!");
}
auto bottomMenu = static_cast<CCMenu*>(this->getChildByID("bottom-menu"));
auto btn = CCMenuItemSpriteExtra::create(
m_fields->m_geodeButton, this,
static_cast<SEL_MenuHandler>(geodeBtnSelector)
);
btn->setID("geode-button"_spr);
bottomMenu->addChild(btn);
auto btn = CCMenuItemSpriteExtra::create(
m_fields->m_geodeButton, this,
static_cast<SEL_MenuHandler>(geodeBtnSelector)
);
btn->setID("geode-button"_spr);
bottomMenu->addChild(btn);
bottomMenu->updateLayout();
@ -96,54 +96,57 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
static bool shownFailedNotif = false;
if (!shownFailedNotif) {
shownFailedNotif = true;
if (Loader::get()->getFailedMods().size()) {
Notification::create("Some mods failed to load", NotificationIcon::Error)->show();
auto problems = Loader::get()->getProblems();
if (std::any_of(problems.begin(), problems.end(), [&](auto& item) {
return item.type != LoadProblem::Type::Suggestion && item.type != LoadProblem::Type::Recommendation;
})) {
Notification::create("There were problems loading some mods", NotificationIcon::Error)->show();
}
}
// show if the user tried to be naughty and load arbitary DLLs
static bool shownTriedToLoadDlls = false;
if (!shownTriedToLoadDlls) {
shownTriedToLoadDlls = true;
if (Loader::get()->userTriedToLoadDLLs()) {
auto popup = FLAlertLayer::create(
"Hold up!",
"It appears that you have tried to <cr>load DLLs</c> with Geode. "
"Please note that <cy>Geode is incompatible with ALL DLLs</c>, "
"as they can cause Geode mods to <cr>error</c>, or even "
"<cr>crash</c>.\n\n"
"Remove the DLLs / other mod loaders you have, or <cr>proceed at "
"your own risk.</c>",
"OK"
);
popup->m_scene = this;
popup->m_noElasticity = true;
popup->show();
}
}
// show if the user tried to be naughty and load arbitrary DLLs
static bool shownTriedToLoadDlls = false;
if (!shownTriedToLoadDlls) {
shownTriedToLoadDlls = true;
if (Loader::get()->userTriedToLoadDLLs()) {
auto popup = FLAlertLayer::create(
"Hold up!",
"It appears that you have tried to <cr>load DLLs</c> with Geode. "
"Please note that <cy>Geode is incompatible with ALL DLLs</c>, "
"as they can cause Geode mods to <cr>error</c>, or even "
"<cr>crash</c>.\n\n"
"Remove the DLLs / other mod loaders you have, or <cr>proceed at "
"your own risk.</c>",
"OK"
);
popup->m_scene = this;
popup->m_noElasticity = true;
popup->show();
}
}
// show auto update message
static bool shownUpdateInfo = false;
if (LoaderImpl::get()->isNewUpdateDownloaded() && !shownUpdateInfo) {
shownUpdateInfo = true;
auto popup = FLAlertLayer::create(
"Update downloaded",
"A new <cy>update</c> for Geode has been installed! "
"Please <cy>restart the game</c> to apply.",
"OK"
);
popup->m_scene = this;
popup->m_noElasticity = true;
popup->show();
}
// show auto update message
static bool shownUpdateInfo = false;
if (LoaderImpl::get()->isNewUpdateDownloaded() && !shownUpdateInfo) {
shownUpdateInfo = true;
auto popup = FLAlertLayer::create(
"Update downloaded",
"A new <cy>update</c> for Geode has been installed! "
"Please <cy>restart the game</c> to apply.",
"OK"
);
popup->m_scene = this;
popup->m_noElasticity = true;
popup->show();
}
// show crash info
static bool shownLastCrash = false;
if (
Loader::get()->didLastLaunchCrash() &&
!shownLastCrash &&
!Mod::get()->template getSettingValue<bool>("disable-last-crashed-popup")
) {
Loader::get()->didLastLaunchCrash() &&
!shownLastCrash &&
!Mod::get()->template getSettingValue<bool>("disable-last-crashed-popup")
) {
shownLastCrash = true;
auto popup = createQuickPopup(
"Crashed",
@ -163,94 +166,94 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> {
popup->show();
}
// update mods index
if (!INDEX_UPDATE_NOTIF && !Index::get()->hasTriedToUpdate()) {
this->addChild(EventListenerNode<IndexUpdateFilter>::create(
this, &CustomMenuLayer::onIndexUpdate
));
INDEX_UPDATE_NOTIF = Notification::create(
"Updating Index", NotificationIcon::Loading, 0
);
INDEX_UPDATE_NOTIF->show();
Index::get()->update();
}
// update mods index
if (!INDEX_UPDATE_NOTIF && !Index::get()->hasTriedToUpdate()) {
this->addChild(EventListenerNode<IndexUpdateFilter>::create(
this, &CustomMenuLayer::onIndexUpdate
));
INDEX_UPDATE_NOTIF = Notification::create(
"Updating Index", NotificationIcon::Loading, 0
);
INDEX_UPDATE_NOTIF->show();
Index::get()->update();
}
this->addUpdateIndicator();
return true;
}
this->addUpdateIndicator();
return true;
}
void onIndexUpdate(IndexUpdateEvent* event) {
if (
std::holds_alternative<UpdateFinished>(event->status) ||
std::holds_alternative<UpdateFailed>(event->status)
) {
this->addUpdateIndicator();
}
}
void onIndexUpdate(IndexUpdateEvent* event) {
if (
std::holds_alternative<UpdateFinished>(event->status) ||
std::holds_alternative<UpdateFailed>(event->status)
) {
this->addUpdateIndicator();
}
}
void addUpdateIndicator() {
if (Index::get()->areUpdatesAvailable()) {
auto icon = CCSprite::createWithSpriteFrameName("updates-available.png"_spr);
icon->setPosition(
m_fields->m_geodeButton->getContentSize() - CCSize { 10.f, 10.f }
);
icon->setZOrder(99);
icon->setScale(.5f);
m_fields->m_geodeButton->addChild(icon);
}
}
void addUpdateIndicator() {
if (Index::get()->areUpdatesAvailable()) {
auto icon = CCSprite::createWithSpriteFrameName("updates-available.png"_spr);
icon->setPosition(
m_fields->m_geodeButton->getContentSize() - CCSize { 10.f, 10.f }
);
icon->setZOrder(99);
icon->setScale(.5f);
m_fields->m_geodeButton->addChild(icon);
}
}
void onMissingTextures(CCObject*) {
#ifdef GEODE_IS_DESKTOP
void onMissingTextures(CCObject*) {
#ifdef GEODE_IS_DESKTOP
(void) utils::file::createDirectoryAll(dirs::getGeodeDir() / "update" / "resources");
(void) utils::file::createDirectoryAll(dirs::getGeodeDir() / "update" / "resources");
createQuickPopup(
"Missing Textures",
"You appear to be missing textures, and the automatic texture fixer "
"hasn't fixed the issue.\n"
"Download <cy>resources.zip</c> from the latest release on GitHub, "
"and <cy>unzip its contents</c> into <cb>geode/update/resources</c>.\n"
"Afterwards, <cg>restart the game</c>.\n"
"You may also continue without installing resources, but be aware that "
"you won't be able to open <cr>the Geode menu</c>.",
"Dismiss", "Open Github",
[](auto, bool btn2) {
if (btn2) {
web::openLinkInBrowser("https://github.com/geode-sdk/geode/releases/latest");
file::openFolder(dirs::getGeodeDir() / "update" / "resources");
FLAlertLayer::create(
"Info",
"Opened GitHub in your browser and the destination in "
"your file browser.\n"
"Download <cy>resources.zip</c>, "
"and <cy>unzip its contents</c> into the destination "
"folder.\n"
"<cb>Don't add any new folders to the destination!</c>",
"OK"
)->show();
}
}
);
createQuickPopup(
"Missing Textures",
"You appear to be missing textures, and the automatic texture fixer "
"hasn't fixed the issue.\n"
"Download <cy>resources.zip</c> from the latest release on GitHub, "
"and <cy>unzip its contents</c> into <cb>geode/update/resources</c>.\n"
"Afterwards, <cg>restart the game</c>.\n"
"You may also continue without installing resources, but be aware that "
"you won't be able to open <cr>the Geode menu</c>.",
"Dismiss", "Open Github",
[](auto, bool btn2) {
if (btn2) {
web::openLinkInBrowser("https://github.com/geode-sdk/geode/releases/latest");
file::openFolder(dirs::getGeodeDir() / "update" / "resources");
FLAlertLayer::create(
"Info",
"Opened GitHub in your browser and the destination in "
"your file browser.\n"
"Download <cy>resources.zip</c>, "
"and <cy>unzip its contents</c> into the destination "
"folder.\n"
"<cb>Don't add any new folders to the destination!</c>",
"OK"
)->show();
}
}
);
#else
#else
// dunno if we can auto-create target directory on mobile, nor if the
// user has access to moving stuff there
// dunno if we can auto-create target directory on mobile, nor if the
// user has access to moving stuff there
FLAlertLayer::create(
"Missing Textures",
"You appear to be missing textures, and the automatic texture fixer "
"hasn't fixed the issue.\n"
"**<cy>Report this bug to the Geode developers</c>**. It is very likely "
"that your game <cr>will crash</c> until the issue is resolved.",
"OK"
)->show();
FLAlertLayer::create(
"Missing Textures",
"You appear to be missing textures, and the automatic texture fixer "
"hasn't fixed the issue.\n"
"**<cy>Report this bug to the Geode developers</c>**. It is very likely "
"that your game <cr>will crash</c> until the issue is resolved.",
"OK"
)->show();
#endif
}
#endif
}
void onGeode(CCObject*) {
ModListLayer::scene();

View file

@ -18,7 +18,7 @@ static std::string getDateString(bool filesafe) {
static void printGeodeInfo(std::stringstream& stream) {
stream << "Loader Version: " << Loader::get()->getVersion().toString() << "\n"
<< "Installed mods: " << Loader::get()->getAllMods().size() << "\n"
<< "Failed mods: " << Loader::get()->getFailedMods().size() << "\n";
<< "Problems: " << Loader::get()->getProblems().size() << "\n";
}
static void printMods(std::stringstream& stream) {
@ -84,4 +84,4 @@ std::string crashlog::writeCrashlog(geode::Mod* faultyMod, std::string const& in
actualFile.close();
return file.str();
}
}

View file

@ -64,6 +64,8 @@ public:
static Result<std::shared_ptr<IndexItem>> create(
ghc::filesystem::path const& dir
);
bool isInstalled() const;
};
IndexItem::IndexItem() : m_impl(std::make_unique<Impl>()) {}
@ -101,6 +103,10 @@ std::unordered_set<std::string> IndexItem::getTags() const {
return m_impl->m_tags;
}
bool IndexItem::isInstalled() const {
return m_impl->isInstalled();
}
Result<IndexItemHandle> IndexItem::Impl::create(ghc::filesystem::path const& dir) {
GEODE_UNWRAP_INTO(
auto entry, file::readJson(dir / "entry.json")
@ -134,6 +140,10 @@ Result<IndexItemHandle> IndexItem::Impl::create(ghc::filesystem::path const& dir
return Ok(item);
}
bool IndexItem::Impl::isInstalled() const {
return ghc::filesystem::exists(dirs::getModsDir() / (m_metadata.getID() + ".geode"));
}
// Helpers
static Result<> flattenGithubRepo(ghc::filesystem::path const& dir) {
@ -595,9 +605,6 @@ void Index::Impl::installNext(size_t index, IndexInstallList const& list) {
}
}
// load mods
Loader::get()->refreshModsList();
auto const& eventModID = list.target->getMetadata().getID();
Loader::get()->queueInGDThread([eventModID]() {
ModInstallEvent(eventModID, UpdateFinished()).post();

View file

@ -95,6 +95,10 @@ std::vector<InvalidGeodeFile> Loader::getFailedMods() const {
return m_impl->getFailedMods();
}
std::vector<LoadProblem> Loader::getProblems() const {
return m_impl->getProblems();
}
void Loader::updateResources() {
return m_impl->updateResources();
}

View file

@ -1,4 +1,3 @@
#include "LoaderImpl.hpp"
#include <cocos2d.h>
@ -10,24 +9,19 @@
#include <Geode/loader/Loader.hpp>
#include <Geode/loader/Log.hpp>
#include <Geode/loader/Mod.hpp>
#include <Geode/loader/ModJsonTest.hpp>
#include <Geode/utils/JsonValidation.hpp>
#include <Geode/utils/file.hpp>
#include <Geode/utils/map.hpp>
#include <Geode/utils/ranges.hpp>
#include <Geode/utils/string.hpp>
#include <Geode/utils/web.hpp>
#include <Geode/utils/JsonValidation.hpp>
#include "ModImpl.hpp"
#include "ModInfoImpl.hpp"
#include <about.hpp>
#include <crashlog.hpp>
#include <fmt/format.h>
#include <hash.hpp>
#include <iostream>
#include <resources.hpp>
#include <sstream>
#include <string>
#include <thread>
#include <vector>
using namespace geode::prelude;
@ -36,9 +30,9 @@ Loader::Impl* LoaderImpl::get() {
return Loader::get()->m_impl.get();
}
Loader::Impl::Impl() {}
Loader::Impl::Impl() = default;
Loader::Impl::~Impl() {}
Loader::Impl::~Impl() = default;
// Initialization
@ -92,15 +86,10 @@ Result<> Loader::Impl::setup() {
this->setupIPC();
this->createDirectories();
auto sett = this->loadData();
if (!sett) {
log::warn("Unable to load loader settings: {}", sett.unwrapErr());
}
this->refreshModsList();
this->queueInGDThread([]() {
Loader::get()->addSearchPaths();
});
this->addSearchPaths();
this->refreshModGraph();
m_isSetup = true;
@ -132,12 +121,19 @@ std::vector<Mod*> Loader::Impl::getAllMods() {
return map::values(m_mods);
}
Mod* Loader::Impl::getModImpl() {
return Mod::get();
}
std::vector<InvalidGeodeFile> Loader::Impl::getFailedMods() const {
return m_invalidMods;
std::vector<InvalidGeodeFile> inv;
for (auto const& item : this->getProblems()) {
if (item.type != LoadProblem::Type::InvalidFile)
continue;
if (!holds_alternative<ghc::filesystem::path>(item.cause))
continue;
inv.push_back({
std::get<ghc::filesystem::path>(item.cause),
item.message
});
}
return inv;
}
// Version info
@ -170,7 +166,7 @@ bool Loader::Impl::isModVersionSupported(VersionInfo const& version) {
Result<> Loader::Impl::saveData() {
// save mods' data
for (auto& [id, mod] : m_mods) {
Mod::get()->setSavedValue("should-load-" + id, mod->isEnabled());
Mod::get()->setSavedValue("should-load-" + id, mod->isUninstalled() || mod->isEnabled());
auto r = mod->saveData();
if (!r) {
log::warn("Unable to save data for mod \"{}\": {}", mod->getID(), r.unwrapErr());
@ -178,15 +174,11 @@ Result<> Loader::Impl::saveData() {
}
// save loader data
GEODE_UNWRAP(Mod::get()->saveData());
return Ok();
}
Result<> Loader::Impl::loadData() {
auto e = Mod::get()->loadData();
if (!e) {
log::warn("Unable to load loader settings: {}", e.unwrapErr());
}
for (auto& [_, mod] : m_mods) {
auto r = mod->loadData();
if (!r) {
@ -198,60 +190,6 @@ Result<> Loader::Impl::loadData() {
// Mod loading
Result<Mod*> Loader::Impl::loadModFromInfo(ModInfo const& info) {
if (m_mods.count(info.id())) {
return Err(fmt::format("Mod with ID '{}' already loaded", info.id()));
}
// create Mod instance
auto mod = new Mod(info);
auto setupRes = mod->m_impl->setup();
if (!setupRes) {
// old code artifcat, idk why we are not using unique_ptr TBH
delete mod;
return Err(fmt::format(
"Unable to setup mod '{}': {}",
info.id(), setupRes.unwrapErr()
));
}
m_mods.insert({ info.id(), mod });
mod->m_impl->m_enabled = Mod::get()->getSavedValue<bool>(
"should-load-" + info.id(), true
);
// this loads the mod if its dependencies are resolved
auto dependenciesRes = mod->updateDependencies();
if (!dependenciesRes) {
delete mod;
m_mods.erase(info.id());
return Err(dependenciesRes.unwrapErr());
}
// add mod resources
this->queueInGDThread([this, mod]() {
auto searchPath = dirs::getModRuntimeDir() / mod->getID() / "resources";
CCFileUtils::get()->addSearchPath(searchPath.string().c_str());
this->updateModResources(mod);
});
return Ok(mod);
}
Result<Mod*> Loader::Impl::loadModFromFile(ghc::filesystem::path const& file) {
auto res = ModInfo::createFromGeodeFile(file);
if (!res) {
m_invalidMods.push_back(InvalidGeodeFile {
.path = file,
.reason = res.unwrapErr(),
});
return Err(res.unwrapErr());
}
return this->loadModFromInfo(res.unwrap());
}
bool Loader::Impl::isModInstalled(std::string const& id) const {
return m_mods.count(id) && !m_mods.at(id)->isUninstalled();
}
@ -308,116 +246,320 @@ void Loader::Impl::updateModResources(Mod* mod) {
// Dependencies and refreshing
void Loader::Impl::loadModsFromDirectory(
ghc::filesystem::path const& dir,
bool recursive
) {
log::debug("Searching {}", dir);
for (auto const& entry : ghc::filesystem::directory_iterator(dir)) {
// recursively search directories
if (ghc::filesystem::is_directory(entry) && recursive) {
this->loadModsFromDirectory(entry.path(), true);
continue;
}
Result<Mod*> Loader::Impl::loadModFromInfo(ModInfo const& info) {
return Err("Loader::loadModFromInfo is deprecated");
}
// skip this entry if it's not a file
if (!ghc::filesystem::is_regular_file(entry)) {
continue;
}
Result<Mod*> Loader::Impl::loadModFromFile(ghc::filesystem::path const& file) {
return Err("Loader::loadModFromFile is deprecated");
}
// skip this entry if its extension is not .geode
if (entry.path().extension() != GEODE_MOD_EXTENSION) {
continue;
}
// skip this entry if it's already loaded
if (map::contains<std::string, Mod*>(m_mods, [entry](Mod* p) -> bool {
return p->m_impl->m_info.path() == entry.path();
})) {
continue;
}
// if mods should be loaded immediately, do that
if (m_earlyLoadFinished) {
auto load = this->loadModFromFile(entry);
if (!load) {
log::error("Unable to load {}: {}", entry, load.unwrapErr());
}
}
// otherwise collect mods to load first to make sure the correct
// versions of the mods are loaded and that early-loaded mods are
// loaded early
else {
auto res = ModInfo::createFromGeodeFile(entry.path());
if (!res) {
m_invalidMods.push_back(InvalidGeodeFile {
.path = entry.path(),
.reason = res.unwrapErr(),
});
continue;
}
auto info = res.unwrap();
// skip this entry if it's already set to be loaded
if (ranges::contains(m_modsToLoad, info)) {
continue;
}
// add to list of mods to load
m_modsToLoad.push_back(info);
}
}
void Loader::Impl::loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive) {
log::error("Called deprecated stub: Loader::loadModsFromDirectory");
}
void Loader::Impl::refreshModsList() {
log::debug("Loading mods...");
// find mods
for (auto& dir : m_modSearchDirectories) {
this->loadModsFromDirectory(dir);
}
// load early-load mods first
for (auto& mod : m_modsToLoad) {
if (mod.needsEarlyLoad()) {
auto load = this->loadModFromInfo(mod);
if (!load) {
log::error("Unable to load {}: {}", mod.id(), load.unwrapErr());
m_invalidMods.push_back(InvalidGeodeFile {
.path = mod.path(),
.reason = load.unwrapErr(),
});
}
}
}
// UI can be loaded now
m_earlyLoadFinished = true;
m_earlyLoadFinishedCV.notify_all();
// load the rest of the mods
for (auto& mod : m_modsToLoad) {
if (!mod.needsEarlyLoad()) {
auto load = this->loadModFromInfo(mod);
if (!load) {
log::error("Unable to load {}: {}", mod.id(), load.unwrapErr());
m_invalidMods.push_back(InvalidGeodeFile {
.path = mod.path(),
.reason = load.unwrapErr(),
});
}
}
}
m_modsToLoad.clear();
log::error("Called deprecated stub: Loader::refreshModsList");
}
void Loader::Impl::updateAllDependencies() {
for (auto const& [_, mod] : m_mods) {
(void)mod->updateDependencies();
log::error("Called deprecated stub: Loader::updateAllDependencies");
}
void Loader::Impl::queueMods(std::vector<ModMetadata>& modQueue) {
for (auto const& dir : m_modSearchDirectories) {
log::debug("Searching {}", dir);
log::pushNest();
for (auto const& entry : ghc::filesystem::directory_iterator(dir)) {
if (!ghc::filesystem::is_regular_file(entry) ||
entry.path().extension() != GEODE_MOD_EXTENSION)
continue;
log::debug("Found {}", entry.path().filename());
log::pushNest();
auto res = ModMetadata::createFromGeodeFile(entry.path());
if (!res) {
m_problems.push_back({
LoadProblem::Type::InvalidFile,
entry.path(),
res.unwrapErr()
});
log::error("Failed to queue: {}", res.unwrapErr());
log::popNest();
continue;
}
auto modMetadata = res.unwrap();
log::debug("id: {}", modMetadata.getID());
log::debug("version: {}", modMetadata.getVersion());
if (std::find_if(modQueue.begin(), modQueue.end(), [&](auto& item) {
return modMetadata.getID() == item.getID();
}) != modQueue.end()) {
m_problems.push_back({
LoadProblem::Type::Duplicate,
modMetadata,
"a mod with the same ID is already present"
});
log::error("Failed to queue: a mod with the same ID is already queued");
log::popNest();
continue;
}
modQueue.push_back(modMetadata);
log::popNest();
}
log::popNest();
}
}
void Loader::Impl::populateModList(std::vector<ModMetadata>& modQueue) {
std::vector<std::string> toRemove;
for (auto& [id, mod] : m_mods) {
if (id == "geode.loader")
continue;
delete mod;
toRemove.push_back(id);
}
for (auto const& id : toRemove) {
m_mods.erase(id);
}
for (auto const& metadata : modQueue) {
log::debug("{} {}", metadata.getID(), metadata.getVersion());
log::pushNest();
auto mod = new Mod(metadata);
auto res = mod->m_impl->setup();
if (!res) {
m_problems.push_back({
LoadProblem::Type::SetupFailed,
mod,
res.unwrapErr()
});
log::error("Failed to set up: {}", res.unwrapErr());
log::popNest();
continue;
}
m_mods.insert({metadata.getID(), mod});
queueInGDThread([this, mod]() {
auto searchPath = dirs::getModRuntimeDir() / mod->getID() / "resources";
CCFileUtils::get()->addSearchPath(searchPath.string().c_str());
updateModResources(mod);
});
log::popNest();
}
}
void Loader::Impl::buildModGraph() {
for (auto const& [id, mod] : m_mods) {
log::debug("{}", mod->getID());
log::pushNest();
for (auto& dependency : mod->m_impl->m_metadata.m_impl->m_dependencies) {
log::debug("{}", dependency.id);
if (!m_mods.contains(dependency.id)) {
dependency.mod = nullptr;
continue;
}
dependency.mod = m_mods[dependency.id];
if (!dependency.version.compare(dependency.mod->getVersion())) {
dependency.mod = nullptr;
continue;
}
if (dependency.importance != ModMetadata::Dependency::Importance::Required || dependency.mod == nullptr)
continue;
dependency.mod->m_impl->m_dependants.push_back(mod);
}
for (auto& incompatibility : mod->m_impl->m_metadata.m_impl->m_incompatibilities) {
incompatibility.mod =
m_mods.contains(incompatibility.id) ? m_mods[incompatibility.id] : nullptr;
}
log::popNest();
}
}
void Loader::Impl::loadModGraph(Mod* node) {
if (node->hasUnresolvedDependencies())
return;
if (node->hasUnresolvedIncompatibilities())
return;
log::debug("{} {}", node->getID(), node->getVersion());
log::pushNest();
if (node->isLoaded()) {
for (auto const& dep : node->m_impl->m_dependants) {
this->loadModGraph(dep);
}
log::popNest();
return;
}
log::debug("Load");
auto res = node->m_impl->loadBinary();
if (!res) {
m_problems.push_back({
LoadProblem::Type::LoadFailed,
node,
res.unwrapErr()
});
log::error("Failed to load binary: {}", res.unwrapErr());
log::popNest();
return;
}
if (Mod::get()->getSavedValue<bool>("should-load-" + node->getID(), true)) {
log::debug("Enable");
res = node->m_impl->enable();
if (!res) {
node->m_impl->m_enabled = true;
(void)node->m_impl->disable();
m_problems.push_back({
LoadProblem::Type::EnableFailed,
node,
res.unwrapErr()
});
log::error("Failed to enable: {}", res.unwrapErr());
log::popNest();
return;
}
for (auto const& dep : node->m_impl->m_dependants) {
this->loadModGraph(dep);
}
}
log::popNest();
}
void Loader::Impl::findProblems() {
for (auto const& [id, mod] : m_mods) {
log::debug(id);
log::pushNest();
for (auto const& dep : mod->getMetadata().getDependencies()) {
if (dep.mod && dep.mod->isLoaded() && dep.version.compare(dep.mod->getVersion()))
continue;
switch(dep.importance) {
case ModMetadata::Dependency::Importance::Suggested:
m_problems.push_back({
LoadProblem::Type::Suggestion,
mod,
fmt::format("{} {}", dep.id, dep.version.toString())
});
log::info("{} suggests {} {}", id, dep.id, dep.version);
break;
case ModMetadata::Dependency::Importance::Recommended:
m_problems.push_back({
LoadProblem::Type::Recommendation,
mod,
fmt::format("{} {}", dep.id, dep.version.toString())
});
log::warn("{} recommends {} {}", id, dep.id, dep.version);
break;
case ModMetadata::Dependency::Importance::Required:
m_problems.push_back({
LoadProblem::Type::MissingDependency,
mod,
fmt::format("{} {}", dep.id, dep.version.toString())
});
log::error("{} requires {} {}", id, dep.id, dep.version);
break;
}
}
for (auto const& dep : mod->getMetadata().getIncompatibilities()) {
if (!dep.mod || !dep.version.compare(dep.mod->getVersion()))
continue;
m_problems.push_back({
LoadProblem::Type::PresentIncompatibility,
mod,
fmt::format("{} {}", dep.id, dep.version.toString())
});
log::error("{} is incompatible with {} {}", id, dep.id, dep.version);
}
Mod* myEpicMod = mod; // clang fix
// if the mod is not loaded but there are no problems related to it
if (!mod->isLoaded() && !std::any_of(m_problems.begin(), m_problems.end(), [myEpicMod](auto& item) {
return std::holds_alternative<ModMetadata>(item.cause) &&
std::get<ModMetadata>(item.cause).getID() == myEpicMod->getID() ||
std::holds_alternative<Mod*>(item.cause) &&
std::get<Mod*>(item.cause) == myEpicMod;
})) {
m_problems.push_back({
LoadProblem::Type::Unknown,
mod,
""
});
log::error("{} failed to load for an unknown reason", id);
}
log::popNest();
}
}
void Loader::Impl::refreshModGraph() {
log::info("Refreshing mod graph...");
log::pushNest();
if (m_mods.size() > 1) {
log::error("Cannot refresh mod graph after startup");
log::popNest();
return;
}
m_problems.clear();
log::debug("Queueing mods");
log::pushNest();
std::vector<ModMetadata> modQueue;
this->queueMods(modQueue);
log::popNest();
log::debug("Populating mod list");
log::pushNest();
this->populateModList(modQueue);
modQueue.clear();
log::popNest();
log::debug("Building mod graph");
log::pushNest();
this->buildModGraph();
log::popNest();
// TODO: not early load
log::debug("Loading mods");
log::pushNest();
for (auto const& dep : Mod::get()->m_impl->m_dependants) {
this->loadModGraph(dep);
}
log::popNest();
log::debug("Finding problems");
log::pushNest();
this->findProblems();
log::popNest();
m_earlyLoadFinished = true;
m_earlyLoadFinishedCV.notify_all();
log::popNest();
}
std::vector<LoadProblem> Loader::Impl::getProblems() const {
return m_problems;
}
void Loader::Impl::waitForModsToBeLoaded() {
auto lock = std::unique_lock(m_earlyLoadFinishedMutex);
log::debug("Waiting for mods to be loaded... {}", bool(m_earlyLoadFinished));

View file

@ -54,8 +54,7 @@ namespace geode {
mutable std::mutex m_mutex;
std::vector<ghc::filesystem::path> m_modSearchDirectories;
std::vector<ModInfo> m_modsToLoad;
std::vector<InvalidGeodeFile> m_invalidMods;
std::vector<LoadProblem> m_problems;
std::unordered_map<std::string, Mod*> m_mods;
std::vector<ghc::filesystem::path> m_texturePaths;
bool m_isSetup = false;
@ -113,7 +112,7 @@ namespace geode {
friend void GEODE_CALL ::geode_implicit_load(Mod*);
Result<Mod*> loadModFromInfo(ModInfo const& info);
[[deprecated]] Result<Mod*> loadModFromInfo(ModInfo const& info);
Result<> setup();
void forceReset();
@ -126,17 +125,24 @@ namespace geode {
VersionInfo maxModVersion();
bool isModVersionSupported(VersionInfo const& version);
Result<Mod*> loadModFromFile(ghc::filesystem::path const& file);
void loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive = true);
void refreshModsList();
[[deprecated]] Result<Mod*> loadModFromFile(ghc::filesystem::path const& file);
[[deprecated]] void loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive = true);
[[deprecated]] void refreshModsList();
void queueMods(std::vector<ModMetadata>& modQueue);
void populateModList(std::vector<ModMetadata>& modQueue);
void buildModGraph();
void loadModGraph(Mod* node);
void findProblems();
void refreshModGraph();
bool isModInstalled(std::string const& id) const;
Mod* getInstalledMod(std::string const& id) const;
bool isModLoaded(std::string const& id) const;
Mod* getLoadedMod(std::string const& id) const;
std::vector<Mod*> getAllMods();
Mod* getModImpl();
void updateAllDependencies();
std::vector<InvalidGeodeFile> getFailedMods() const;
[[deprecated]] Mod* getModImpl();
[[deprecated]] void updateAllDependencies();
[[deprecated]] std::vector<InvalidGeodeFile> getFailedMods() const;
std::vector<LoadProblem> getProblems() const;
void updateResources();
void updateResources(bool forceReload);

View file

@ -4,6 +4,7 @@
using namespace geode::prelude;
#pragma warning(suppress : 4996)
Mod::Mod(ModInfo const& info) : m_impl(std::make_unique<Impl>(this, info)) {}
Mod::Mod(ModMetadata const& metadata) : m_impl(std::make_unique<Impl>(this, metadata)) {}
@ -53,6 +54,14 @@ bool Mod::supportsDisabling() const {
return m_impl->supportsDisabling();
}
bool Mod::canDisable() const {
return m_impl->canDisable();
}
bool Mod::canEnable() const {
return m_impl->canEnable();
}
bool Mod::supportsUnloading() const {
return false;
}
@ -153,11 +162,11 @@ Result<> Mod::unpatch(Patch* patch) {
}
Result<> Mod::loadBinary() {
return m_impl->loadBinary();
return Err("Load mod binaries after startup is not supported");
}
Result<> Mod::unloadBinary() {
return m_impl->unloadBinary();
return Err("Unloading mod binaries is not supported");
}
Result<> Mod::enable() {
@ -180,14 +189,19 @@ bool Mod::depends(std::string const& id) const {
return m_impl->depends(id);
}
bool Mod::hasUnresolvedDependencies() const {
return m_impl->hasUnresolvedDependencies();
}
Result<> Mod::updateDependencies() {
return m_impl->updateDependencies();
}
bool Mod::hasUnresolvedDependencies() const {
return m_impl->hasUnresolvedDependencies();
}
bool Mod::hasUnresolvedIncompatibilities() const {
return m_impl->hasUnresolvedIncompatibilities();
}
#pragma warning(suppress : 4996)
std::vector<Dependency> Mod::getUnresolvedDependencies() {
return m_impl->getUnresolvedDependencies();
}

View file

@ -28,9 +28,7 @@ Mod::Impl* ModImpl::getImpl(Mod* mod) {
Mod::Impl::Impl(Mod* self, ModMetadata const& metadata) : m_self(self), m_metadata(metadata) {
}
Mod::Impl::~Impl() {
(void)this->unloadBinary();
}
Mod::Impl::~Impl() = default;
Result<> Mod::Impl::setup() {
m_saveDirPath = dirs::getModsSaveDir() / m_metadata.getID();
@ -110,11 +108,23 @@ bool Mod::Impl::isLoaded() const {
}
bool Mod::Impl::supportsDisabling() const {
return m_info.supportsDisabling();
return m_metadata.getID() != "geode.loader" && !m_metadata.isAPI();
}
bool Mod::Impl::supportsUnloading() const {
return m_info.supportsUnloading();
bool Mod::Impl::canDisable() const {
auto deps = m_dependants;
return this->supportsDisabling() &&
(deps.empty() || std::all_of(deps.begin(), deps.end(), [&](auto& item) {
return item->canDisable();
}));
}
bool Mod::Impl::canEnable() const {
auto deps = m_metadata.getDependencies();
return !this->isUninstalled() &&
(deps.empty() || std::all_of(deps.begin(), deps.end(), [&](auto& item) {
return item.isResolved();
}));
}
bool Mod::Impl::wasSuccessfullyLoaded() const {
@ -304,11 +314,6 @@ Result<> Mod::Impl::loadBinary() {
log::debug("Loading binary for mod {}", m_metadata.getID());
if (m_binaryLoaded)
return Ok();
}
if (this->hasUnresolvedDependencies()) {
return Err("Mod has unresolved dependencies");
}
LoaderImpl::get()->provideNextMod(m_self);
@ -327,54 +332,30 @@ Result<> Mod::Impl::loadBinary() {
ModStateEvent(m_self, ModEventType::Loaded).post();
});
Loader::get()->updateAllDependencies();
log::debug("Enabling mod {}", m_info.id());
GEODE_UNWRAP(this->enable());
return Ok();
}
Result<> Mod::Impl::unloadBinary() {
if (!m_binaryLoaded) {
return Ok();
}
if (!m_info.supportsUnloading()) {
return Err("Mod does not support unloading");
}
GEODE_UNWRAP(this->saveData());
GEODE_UNWRAP(this->disable());
Loader::get()->queueInGDThread([&]() {
ModStateEvent(m_self, 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::Impl::enable() {
if (!m_binaryLoaded) {
return this->loadBinary();
if (!m_binaryLoaded)
return Err("Tried to enable {} but its binary is not loaded", m_metadata.getID());
bool enabledDependencies = true;
for (auto const& item : m_metadata.getDependencies()) {
if (!item.isResolved())
continue;
auto res = item.mod->enable();
if (!res) {
enabledDependencies = false;
log::error("Failed to enable {}: {}", item.id, res.unwrapErr());
}
}
if (!enabledDependencies)
return Err("Mod cannot be enabled because one or more of its dependencies cannot be enabled.");
if (!this->canEnable())
return Err("Mod cannot be enabled because it has unresolved dependencies.");
for (auto const& hook : m_hooks) {
if (!hook) {
log::warn("Hook is null in mod \"{}\"", m_metadata.getName());
@ -401,41 +382,66 @@ Result<> Mod::Impl::enable() {
}
Result<> Mod::Impl::disable() {
if (!m_enabled) {
if (!m_enabled)
return Ok();
if (!this->supportsDisabling())
return Err("Mod does not support disabling.");
if (!this->canDisable())
return Err("Mod cannot be disabled because one or more of its dependants cannot be disabled.");
// disable dependants
bool disabledDependants = true;
for (auto& item : m_dependants) {
auto res = item->disable();
if (res)
continue;
disabledDependants = false;
log::error("Failed to disable {}: {}", item->getID(), res.unwrapErr());
}
if (!m_info.supportsDisabling()) {
return Err("Mod does not support disabling");
}
if (!disabledDependants)
return Err("Mod cannot be disabled because one or more of its dependants cannot be disabled.");
Loader::get()->queueInGDThread([&]() {
ModStateEvent(m_self, ModEventType::Disabled).post();
});
std::vector<std::string> errors;
for (auto const& hook : m_hooks) {
GEODE_UNWRAP(this->disableHook(hook));
auto res = this->disableHook(hook);
if (!res)
errors.push_back(res.unwrapErr());
}
for (auto const& patch : m_patches) {
if (!patch->restore()) {
return Err("Unable to restore patch at " + std::to_string(patch->getAddress()));
}
auto res = this->unpatch(patch);
if (!res)
errors.push_back(res.unwrapErr());
}
m_enabled = false;
if (!errors.empty())
return Err(utils::string::join(errors, "\n"));
return Ok();
}
Result<> Mod::Impl::uninstall() {
if (m_info.supportsDisabling()) {
if (supportsDisabling()) {
GEODE_UNWRAP(this->disable());
if (m_info.supportsUnloading()) {
GEODE_UNWRAP(this->unloadBinary());
}
else {
for (auto& item : m_dependants) {
if (!item->canDisable())
continue;
GEODE_UNWRAP(item->disable());
}
}
try {
ghc::filesystem::remove(m_info.path());
ghc::filesystem::remove(m_metadata.getPath());
}
catch (std::exception& e) {
return Err(
@ -455,51 +461,12 @@ bool Mod::Impl::isUninstalled() const {
// Dependencies
Result<> Mod::Impl::updateDependencies() {
bool hasUnresolved = false;
for (auto& dep : m_info.dependencies()) {
// 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;
}
}
// 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}"));
}
}
}
// check if the dependency is resolved now
if (!dep.isResolved()) {
GEODE_UNWRAP(this->unloadBinary().expect("Unable to unload mod: {error}"));
hasUnresolved = true;
}
}
// load if there weren't any unresolved dependencies
if (!hasUnresolved && !m_binaryLoaded) {
log::debug("All dependencies for {} found", m_info.id());
if (m_enabled) {
log::debug("Resolved & loading {}", m_info.id());
GEODE_UNWRAP(this->loadBinary());
}
else {
log::debug("Resolved {}, however not loading it as it is disabled", m_info.id());
}
}
return Ok();
return Err("Mod::updateDependencies is no longer needed, "
"as this is handled by Loader::refreshModGraph");
}
bool Mod::Impl::hasUnresolvedDependencies() const {
for (auto const& dep : m_info.dependencies()) {
for (auto const& dep : m_metadata.getDependencies()) {
if (!dep.isResolved()) {
return true;
}
@ -507,10 +474,23 @@ bool Mod::Impl::hasUnresolvedDependencies() const {
return false;
}
bool Mod::Impl::hasUnresolvedIncompatibilities() const {
for (auto const& dep : m_metadata.getIncompatibilities()) {
if (!dep.isResolved()) {
return true;
}
}
return false;
}
// msvc stop fucking screaming please i BEG YOU
#pragma warning(suppress : 4996)
std::vector<Dependency> Mod::Impl::getUnresolvedDependencies() {
#pragma warning(suppress : 4996)
std::vector<Dependency> unresolved;
for (auto const& dep : m_metadata.getDependencies()) {
if (!dep.isResolved()) {
#pragma warning(suppress : 4996)
unresolved.push_back(dep);
}
}
@ -541,7 +521,7 @@ Result<> Mod::Impl::disableHook(Hook* hook) {
Result<Hook*> Mod::Impl::addHook(Hook* hook) {
m_hooks.push_back(hook);
if (LoaderImpl::get()->isReadyToHook()) {
if (hook->getAutoEnable()) {
if (this->isEnabled() && hook->getAutoEnable()) {
auto res = this->enableHook(hook);
if (!res) {
delete hook;
@ -582,21 +562,20 @@ Result<Patch*> Mod::Impl::patch(void* address, ByteVector const& data) {
p->m_original = readMemory(address, data.size());
p->m_owner = m_self;
p->m_patch = data;
if (!p->apply()) {
if (this->isEnabled() && !p->apply()) {
delete p;
return Err("Unable to enable patch at " + std::to_string(p->getAddress()));
return Err("Unable to enable patch at " + std::to_string(reinterpret_cast<uintptr_t>(address)));
}
m_patches.push_back(p);
return Ok(p);
}
Result<> Mod::Impl::unpatch(Patch* patch) {
if (patch->restore()) {
ranges::remove(m_patches, patch);
delete patch;
return Ok();
}
return Err("Unable to restore patch!");
if (!patch->restore())
return Err("Unable to restore patch at " + std::to_string(patch->getAddress()));
ranges::remove(m_patches, patch);
delete patch;
return Ok();
}
// Misc.
@ -697,27 +676,24 @@ static Result<ModMetadata> getModImplInfo() {
Mod* Loader::Impl::createInternalMod() {
auto& mod = Mod::sharedMod<>;
if (!mod) {
auto infoRes = getModImplInfo();
if (!infoRes) {
LoaderImpl::get()->platformMessageBox(
"Fatal Internal Error",
"Unable to create internal mod info: \"" + infoRes.unwrapErr() +
"\"\n"
"This is a fatal internal error in the loader, please "
"contact Geode developers immediately!"
);
auto info = ModInfo();
info.id() = "geode.loader";
mod = new Mod(info);
}
else {
mod = new Mod(infoRes.unwrap());
}
mod->m_impl->m_binaryLoaded = true;
mod->m_impl->m_enabled = true;
m_mods.insert({ mod->getID(), mod });
if (mod) return mod;
auto infoRes = getModImplInfo();
if (!infoRes) {
LoaderImpl::get()->platformMessageBox(
"Fatal Internal Error",
"Unable to create internal mod info: \"" + infoRes.unwrapErr() +
"\"\n"
"This is a fatal internal error in the loader, please "
"contact Geode developers immediately!"
);
mod = new Mod(ModMetadata("geode.loader"));
}
else {
mod = new Mod(infoRes.unwrap());
}
mod->m_impl->m_binaryLoaded = true;
mod->m_impl->m_enabled = true;
m_mods.insert({ mod->getID(), mod });
return mod;
}

View file

@ -39,12 +39,11 @@ namespace geode {
*/
ghc::filesystem::path m_saveDirPath;
/**
* Pointers to mods that depend on
* this Mod. Makes it possible to
* enable / disable them automatically,
* Pointers to mods that depend on this Mod.
* Makes it possible to enable / disable them automatically,
* when their dependency is disabled.
*/
std::vector<Mod*> m_parentDependencies;
std::vector<Mod*> m_dependants;
/**
* Saved values
*/
@ -84,8 +83,8 @@ namespace geode {
bool isEnabled() const;
bool isLoaded() const;
bool supportsDisabling() const;
bool wasSuccesfullyLoaded() const;
ModInfo getModInfo() const;
bool canDisable() const;
bool canEnable() const;
bool wasSuccessfullyLoaded() const;
ModMetadata getMetadata() const;
ghc::filesystem::path getTempDir() const;
@ -113,16 +112,17 @@ namespace geode {
Result<> removeHook(Hook* hook);
Result<Patch*> patch(void* address, ByteVector const& data);
Result<> unpatch(Patch* patch);
Result<> loadBinary();
Result<> unloadBinary();
Result<> enable();
Result<> disable();
Result<> uninstall();
bool isUninstalled() const;
bool depends(std::string const& id) const;
bool hasUnresolvedDependencies() const;
Result<> updateDependencies();
std::vector<Dependency> getUnresolvedDependencies();
bool hasUnresolvedDependencies() const;
bool hasUnresolvedIncompatibilities() const;
[[deprecated]] std::vector<Dependency> getUnresolvedDependencies();
Result<> loadBinary();
char const* expandSpriteName(char const* name);
ModJson getRuntimeInfo() const;

View file

@ -5,8 +5,7 @@
#include "ModInfoImpl.hpp"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#pragma warning(disable : 4996) // deprecation
using namespace geode::prelude;
@ -20,34 +19,6 @@ bool Dependency::isResolved() const {
this->version.compare(this->mod->getVersion()));
}
bool ModInfo::Impl::validateID(std::string const& id) {
return ModMetadata::Impl::validateID(id);
}
Result<ModInfo> ModInfo::Impl::create(ModJson const& json) {
return ModMetadata::Impl::create(json);
}
Result<ModInfo> ModInfo::Impl::createFromFile(ghc::filesystem::path const& path) {
return ModMetadata::Impl::createFromFile(path);
}
Result<ModInfo> ModInfo::Impl::createFromGeodeFile(ghc::filesystem::path const& path) {
return ModMetadata::Impl::createFromGeodeFile(path);
}
Result<ModInfo> ModInfo::Impl::createFromGeodeZip(file::Unzip& unzip) {
return ModMetadata::Impl::createFromGeodeZip(unzip);
}
ModJson ModInfo::Impl::toJSON() const {
return m_metadata.m_rawJSON;
}
ModJson ModInfo::Impl::getRawJSON() const {
return m_metadata.m_rawJSON;
}
bool ModInfo::Impl::operator==(ModInfo::Impl const& other) const {
return this->m_metadata.m_id == other.m_metadata.m_id;
}
@ -186,33 +157,34 @@ bool const& ModInfo::isAPI() const {
}
Result<ModInfo> ModInfo::createFromGeodeZip(utils::file::Unzip& zip) {
return Impl::createFromGeodeZip(zip);
return ModMetadata::Impl::createFromGeodeZip(zip);
}
Result<ModInfo> ModInfo::createFromGeodeFile(ghc::filesystem::path const& path) {
return Impl::createFromGeodeFile(path);
return ModMetadata::Impl::createFromGeodeFile(path);
}
Result<ModInfo> ModInfo::createFromFile(ghc::filesystem::path const& path) {
return Impl::createFromFile(path);
return ModMetadata::Impl::createFromFile(path);
}
Result<ModInfo> ModInfo::create(ModJson const& json) {
return Impl::create(json);
return ModMetadata::Impl::create(json);
}
ModJson ModInfo::toJSON() const {
return m_impl->toJSON();
return m_impl->m_metadata.m_rawJSON;
}
ModJson ModInfo::getRawJSON() const {
return m_impl->getRawJSON();
return m_impl->m_metadata.m_rawJSON;
}
bool ModInfo::operator==(ModInfo const& other) const {
return m_impl->operator==(*other.m_impl);
}
#pragma warning(suppress : 4996)
ModInfo::ModInfo() : m_impl(std::make_unique<Impl>()) {}
ModInfo::ModInfo(ModInfo const& other) : m_impl(std::make_unique<Impl>(*other.m_impl)) {}
@ -247,6 +219,26 @@ ModInfo::operator ModMetadata() const {
return metadata;
}
ModInfo::~ModInfo() = default;
ModJson& ModInfo::rawJSON() {
return m_impl->m_metadata.m_rawJSON;
}
ModJson const& ModInfo::rawJSON() const {
return m_impl->m_metadata.m_rawJSON;
}
#pragma clang diagnostic pop
Result<ModInfo> ModInfo::createFromSchemaV010(geode::ModJson const& json) {
return ModMetadata::Impl::createFromSchemaV010(json);
}
Result<> ModInfo::addSpecialFiles(ghc::filesystem::path const& dir) {
return m_impl->m_metadata.addSpecialFiles(dir);
}
Result<> ModInfo::addSpecialFiles(utils::file::Unzip& zip) {
return m_impl->m_metadata.addSpecialFiles(zip);
}
std::vector<std::pair<std::string, std::optional<std::string>*>> ModInfo::getSpecialFiles() {
return m_impl->m_metadata.getSpecialFiles();
}
ModInfo::~ModInfo() = default;

View file

@ -7,10 +7,12 @@
#include <Geode/utils/JsonValidation.hpp>
#include <Geode/utils/VersionInfo.hpp>
#pragma warning(disable : 4996) // deprecation
using namespace geode::prelude;
namespace geode {
class ModInfo::Impl {
class [[deprecated]] ModInfo::Impl {
public:
ModMetadata::Impl m_metadata;
std::optional<IssuesInfo> m_issues;
@ -18,31 +20,11 @@ namespace geode {
bool m_supportsDisabling = true;
bool m_supportsUnloading = false;
static Result<ModInfo> createFromGeodeZip(utils::file::Unzip& zip);
static Result<ModInfo> createFromGeodeFile(ghc::filesystem::path const& path);
static Result<ModInfo> createFromFile(ghc::filesystem::path const& path);
static Result<ModInfo> create(ModJson const& json);
ModJson toJSON() const;
ModJson getRawJSON() const;
bool operator==(ModInfo::Impl const& other) const;
static bool validateID(std::string const& id);
static Result<ModInfo> createFromSchemaV010(ModJson const& json);
Result<> addSpecialFiles(ghc::filesystem::path const& dir);
Result<> addSpecialFiles(utils::file::Unzip& zip);
std::vector<std::pair<std::string, std::optional<std::string>*>> getSpecialFiles();
};
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
class ModInfoImpl {
class [[deprecated]] ModInfoImpl {
public:
static ModInfo::Impl& getImpl(ModInfo& info);
};
#pragma clang diagnostic pop
}

View file

@ -22,11 +22,9 @@ bool ModMetadata::Dependency::isResolved() const {
}
bool ModMetadata::Incompatibility::isResolved() const {
return !this->mod || !this->mod->isLoaded() || !this->version.compare(this->mod->getVersion());
return !this->mod || !this->version.compare(this->mod->getVersion());
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
ModMetadata::Dependency::operator geode::Dependency() {
return {id, version, importance == Importance::Required, mod};
}
@ -53,7 +51,6 @@ ModMetadata::Dependency ModMetadata::Dependency::fromDeprecated(geode::Dependenc
ModMetadata::IssuesInfo ModMetadata::IssuesInfo::fromDeprecated(geode::IssuesInfo const& value) {
return {value.info, value.url};
}
#pragma clang diagnostic pop
static std::string sanitizeDetailsData(std::string const& str) {
// delete CRLF
@ -473,8 +470,6 @@ ModMetadata& ModMetadata::operator=(ModMetadata&& other) noexcept {
return *this;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
ModMetadata::operator ModInfo() {
ModInfo info;
auto infoImpl = ModInfoImpl::getImpl(info);
@ -493,7 +488,6 @@ ModMetadata::operator ModInfo() const {
infoImpl.m_dependencies.push_back(dep);
return info;
}
#pragma clang diagnostic pop
ModMetadata::~ModMetadata() = default;
@ -504,6 +498,7 @@ struct json::Serialize<geode::ModMetadata::Dependency::Importance> {
case geode::ModMetadata::Dependency::Importance::Required: return {"required"};
case geode::ModMetadata::Dependency::Importance::Recommended: return {"recommended"};
case geode::ModMetadata::Dependency::Importance::Suggested: return {"suggested"};
default: return {"unknown"};
}
}
static geode::ModMetadata::Dependency::Importance GEODE_DLL from_json(json::Value const& importance) {

View file

@ -573,9 +573,9 @@ void LocalModInfoPopup::onEnableMod(CCObject* sender) {
)->show();
}
if (as<CCMenuItemToggler*>(sender)->isToggled()) {
auto res = m_mod->loadBinary();
auto res = m_mod->enable();
if (!res) {
FLAlertLayer::create(nullptr, "Error Loading Mod", res.unwrapErr(), "OK", nullptr)->show();
FLAlertLayer::create(nullptr, "Error Enabling Mod", res.unwrapErr(), "OK", nullptr)->show();
}
}
else {
@ -585,7 +585,7 @@ void LocalModInfoPopup::onEnableMod(CCObject* sender) {
}
}
if (m_layer) {
m_layer->updateAllStates(nullptr);
m_layer->updateAllStates();
}
as<CCMenuItemToggler*>(sender)->toggle(m_mod->isEnabled());
}

View file

@ -31,7 +31,8 @@ float ModListCell::getLogoSize() const {
void ModListCell::setupInfo(
ModMetadata const& metadata,
bool spaceForTags,
ModListDisplay display
ModListDisplay display,
bool inactive
) {
m_menu = CCMenu::create();
m_menu->setPosition(m_width - 40.f, m_height / 2);
@ -41,6 +42,10 @@ void ModListCell::setupInfo(
auto logoSpr = this->createLogo({ logoSize, logoSize });
logoSpr->setPosition({ logoSize / 2 + 12.f, m_height / 2 });
auto logoSprColor = typeinfo_cast<CCRGBAProtocol*>(logoSpr);
if (inactive && logoSprColor) {
logoSprColor->setColor({ 163, 163, 163 });
}
this->addChild(logoSpr);
bool hasDesc =
@ -63,6 +68,9 @@ void ModListCell::setupInfo(
titleLabel->setPositionY(m_height / 2 + 7.f);
}
titleLabel->limitLabelWidth(m_width / 2 - 40.f, .5f, .1f);
if (inactive) {
titleLabel->setColor({ 163, 163, 163 });
}
this->addChild(titleLabel);
auto versionLabel = CCLabelBMFont::create(
@ -76,6 +84,9 @@ void ModListCell::setupInfo(
titleLabel->getPositionY() - 1.f
);
versionLabel->setColor({ 0, 255, 0 });
if (inactive) {
versionLabel->setColor({ 0, 163, 0 });
}
this->addChild(versionLabel);
if (auto tag = metadata.getVersion().getTag()) {
@ -93,6 +104,9 @@ void ModListCell::setupInfo(
auto creatorStr = "by " + metadata.getDeveloper();
auto creatorLabel = CCLabelBMFont::create(creatorStr.c_str(), "goldFont.fnt");
creatorLabel->setScale(.43f);
if (inactive) {
creatorLabel->setColor({ 163, 163, 163 });
}
m_developerBtn = CCMenuItemSpriteExtra::create(
creatorLabel, this, menu_selector(ModListCell::onViewDev)
@ -133,6 +147,9 @@ void ModListCell::setupInfo(
m_description->setAnchorPoint({ .0f, .5f });
m_description->setPosition(m_height / 2 + logoSize / 2 + 18.f, descBG->getPositionY());
m_description->limitLabelWidth(m_width / 2 - 10.f, .5f, .1f);
if (inactive) {
m_description->setColor({ 163, 163, 163 });
}
this->addChild(m_description);
}
}
@ -187,11 +204,14 @@ void ModCell::onEnable(CCObject* sender) {
else {
tryOrAlert(m_mod->disable(), "Error disabling mod");
}
if (m_layer) {
m_layer->updateAllStates(this);
}
Loader::get()->queueInGDThread([this]() {
if (m_layer) {
m_layer->updateAllStates();
}
});
}
// TODO: for fod maybe :3 show problems related to this mod
void ModCell::onUnresolvedInfo(CCObject*) {
std::string info =
"This mod has the following "
@ -211,6 +231,11 @@ void ModCell::onInfo(CCObject*) {
LocalModInfoPopup::create(m_mod, m_layer)->show();
}
void ModCell::onRestart(CCObject*) {
utils::game::restart();
}
// TODO: for fod maybe :3 check if there are any problems related to this mod
void ModCell::updateState() {
bool unresolved = m_mod->hasUnresolvedDependencies();
if (m_enableToggle) {
@ -232,18 +257,50 @@ bool ModCell::init(
) {
if (!ModListCell::init(list, size))
return false;
m_mod = mod;
this->setupInfo(mod->getMetadata(), false, display, m_mod->isUninstalled());
auto viewSpr = ButtonSprite::create("View", "bigFont.fnt", "GJ_button_01.png", .8f);
viewSpr->setScale(.65f);
if (mod->isUninstalled()) {
auto restartSpr = ButtonSprite::create("Restart", "bigFont.fnt", "GJ_button_03.png", .8f);
restartSpr->setScale(.65f);
auto viewBtn = CCMenuItemSpriteExtra::create(viewSpr, this, menu_selector(ModCell::onInfo));
m_menu->addChild(viewBtn);
auto restartBtn = CCMenuItemSpriteExtra::create(restartSpr, this, menu_selector(ModCell::onRestart));
restartBtn->setPositionX(-16.f);
m_menu->addChild(restartBtn);
}
else {
auto viewSpr = ButtonSprite::create("View", "bigFont.fnt", "GJ_button_01.png", .8f);
viewSpr->setScale(.65f);
if (m_mod->wasSuccesfullyLoaded() && m_mod->supportsDisabling()) {
auto viewBtn = CCMenuItemSpriteExtra::create(viewSpr, this, menu_selector(ModCell::onInfo));
m_menu->addChild(viewBtn);
if (m_mod->wasSuccessfullyLoaded()) {
auto latestIndexItem = Index::get()->getMajorItem(
mod->getMetadata().getID()
);
if (latestIndexItem && Index::get()->isUpdateAvailable(latestIndexItem)) {
viewSpr->updateBGImage("GE_button_01.png"_spr);
auto minorIndexItem = Index::get()->getItem(
mod->getMetadata().getID(),
ComparableVersionInfo(mod->getMetadata().getVersion(), VersionCompare::MoreEq)
);
if (latestIndexItem->getMetadata().getVersion().getMajor() > minorIndexItem->getMetadata().getVersion().getMajor()) {
auto updateIcon = CCSprite::createWithSpriteFrameName("updates-available.png"_spr);
updateIcon->setPosition(viewSpr->getContentSize() - CCSize { 2.f, 2.f });
updateIcon->setZOrder(99);
updateIcon->setScale(.5f);
viewSpr->addChild(updateIcon);
}
}
}
}
if (m_mod->wasSuccessfullyLoaded() && m_mod->supportsDisabling() && !m_mod->isUninstalled()) {
m_enableToggle =
CCMenuItemToggler::createWithStandardSprites(this, menu_selector(ModCell::onEnable), .7f);
m_enableToggle->setPosition(-45.f, 0.f);
@ -259,30 +316,6 @@ bool ModCell::init(
m_unresolvedExMark->setVisible(false);
m_menu->addChild(m_unresolvedExMark);
if (m_mod->wasSuccesfullyLoaded()) {
auto latestIndexItem = Index::get()->getMajorItem(
mod->getModInfo().id()
);
if (latestIndexItem && Index::get()->isUpdateAvailable(latestIndexItem)) {
viewSpr->updateBGImage("GE_button_01.png"_spr);
auto minorIndexItem = Index::get()->getItem(
mod->getModInfo().id(),
ComparableVersionInfo(mod->getModInfo().version(), VersionCompare::MoreEq)
);
if (latestIndexItem->getModInfo().version().getMajor() > minorIndexItem->getModInfo().version().getMajor()) {
auto updateIcon = CCSprite::createWithSpriteFrameName("updates-available.png"_spr);
updateIcon->setPosition(viewSpr->getContentSize() - CCSize { 2.f, 2.f });
updateIcon->setZOrder(99);
updateIcon->setScale(.5f);
viewSpr->addChild(updateIcon);
}
}
}
this->updateState();
return true;
@ -302,6 +335,10 @@ void IndexItemCell::onInfo(CCObject*) {
IndexItemInfoPopup::create(m_item, m_layer)->show();
}
void IndexItemCell::onRestart(CCObject*) {
utils::game::restart();
}
IndexItemCell* IndexItemCell::create(
IndexItemHandle item,
ModListLayer* list,
@ -327,11 +364,24 @@ bool IndexItemCell::init(
m_item = item;
this->setupInfo(item->getModInfo(), item->getTags().size(), display);
this->setupInfo(item->getMetadata(), item->getTags().size(), display, item->isInstalled());
auto viewBtn = CCMenuItemSpriteExtra::create(viewSpr, this, menu_selector(IndexItemCell::onInfo));
m_menu->addChild(viewBtn);
if (item->isInstalled()) {
auto restartSpr = ButtonSprite::create("Restart", "bigFont.fnt", "GJ_button_03.png", .8f);
restartSpr->setScale(.65f);
auto restartBtn = CCMenuItemSpriteExtra::create(restartSpr, this, menu_selector(IndexItemCell::onRestart));
restartBtn->setPositionX(-16.f);
m_menu->addChild(restartBtn);
}
else {
auto viewSpr = ButtonSprite::create("View", "bigFont.fnt", "GJ_button_01.png", .8f);
viewSpr->setScale(.65f);
auto viewBtn =
CCMenuItemSpriteExtra::create(viewSpr, this, menu_selector(IndexItemCell::onInfo));
m_menu->addChild(viewBtn);
}
if (item->getTags().size()) {
float x = m_height / 2 + this->getLogoSize() / 2 + 13.f;
@ -401,7 +451,6 @@ void InvalidGeodeFileCell::FLAlert_Clicked(FLAlertLayer*, bool btn2) {
)
->show();
}
Loader::get()->refreshModsList();
if (m_layer) {
m_layer->reloadList();
}

View file

@ -55,6 +55,7 @@ protected:
);
void onInfo(CCObject*);
void onRestart(CCObject*);
void onEnable(CCObject*);
void onUnresolvedInfo(CCObject*);
@ -86,6 +87,7 @@ protected:
);
void onInfo(CCObject*);
void onRestart(CCObject*);
public:
static IndexItemCell* create(

View file

@ -155,26 +155,32 @@ CCArray* ModListLayer::createModCells(ModListType type, ModListQuery const& quer
));
}
// sort the mods by match score
std::multimap<int, Mod*> sorted;
// sort the mods by match score
std::multimap<int, ModListCell*> sorted;
// then other mods
// newly installed
for (auto const& item : Index::get()->getItems()) {
if (!item->isInstalled())
continue;
if (auto match = queryMatch(query, item)) {
auto cell = IndexItemCell::create(item, this, m_display, this->getCellSize());
sorted.insert({ match.value(), cell });
}
}
// loaded
for (auto const& mod : Loader::get()->getAllMods()) {
// if the mod is no longer installed nor
// loaded, it's as good as not existing
// (because it doesn't)
if (mod->isUninstalled() && !mod->isLoaded()) continue;
// only show mods that match query in list
if (auto match = queryMatch(query, mod)) {
sorted.insert({ match.value(), mod });
auto cell = ModCell::create(mod, this, m_display, this->getCellSize());
sorted.insert({ match.value(), cell });
}
}
// add the mods sorted
for (auto& [score, mod] : ranges::reverse(sorted)) {
mods->addObject(ModCell::create(
mod, this, m_display, this->getCellSize()
));
for (auto& [score, cell] : ranges::reverse(sorted)) {
mods->addObject(cell);
}
} break;
@ -546,14 +552,11 @@ void ModListLayer::reloadList(std::optional<ModListQuery> const& query) {
}
}
void ModListLayer::updateAllStates(ModListCell* toggled) {
void ModListLayer::updateAllStates() {
for (auto cell : CCArrayExt<GenericListCell>(
m_list->m_listView->m_tableView->m_cellArray
)) {
auto node = static_cast<ModListCell*>(cell->getChildByID("mod-list-cell"));
if (toggled != node) {
node->updateState();
}
static_cast<ModListCell*>(cell->getChildByID("mod-list-cell"))->updateState();
}
}
@ -612,7 +615,6 @@ void ModListLayer::onExit(CCObject*) {
}
void ModListLayer::onReload(CCObject*) {
Loader::get()->refreshModsList();
this->reloadList();
}

View file

@ -84,7 +84,7 @@ protected:
public:
static ModListLayer* create();
static ModListLayer* scene();
void updateAllStates(ModListCell* except = nullptr);
void updateAllStates();
ModListDisplay getDisplay() const;
ModListQuery& getQuery();