mirror of
https://github.com/geode-sdk/geode.git
synced 2025-03-24 03:39:56 -04:00
huge dependency stuff refactor
This commit is contained in:
parent
7f449b996e
commit
5200128544
22 changed files with 907 additions and 710 deletions
loader
include/Geode/loader
src
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -84,7 +84,7 @@ protected:
|
|||
public:
|
||||
static ModListLayer* create();
|
||||
static ModListLayer* scene();
|
||||
void updateAllStates(ModListCell* except = nullptr);
|
||||
void updateAllStates();
|
||||
|
||||
ModListDisplay getDisplay() const;
|
||||
ModListQuery& getQuery();
|
||||
|
|
Loading…
Add table
Reference in a new issue