From c8fed17ea428ce245155bc9efe23fe3392cace66 Mon Sep 17 00:00:00 2001 From: altalk23 <45172705+altalk23@users.noreply.github.com> Date: Sat, 10 Dec 2022 17:08:25 +0300 Subject: [PATCH 1/4] add pad to playerobject --- bindings/GeometryDash.bro | 1 + 1 file changed, 1 insertion(+) diff --git a/bindings/GeometryDash.bro b/bindings/GeometryDash.bro index cf9fafb1..6e374aab 100644 --- a/bindings/GeometryDash.bro +++ b/bindings/GeometryDash.bro @@ -4747,6 +4747,7 @@ class PlayerObject : GameObject, AnimatedSpriteDelegate { cocos2d::CCSprite* m_unk500; cocos2d::CCSprite* m_vehicleSpriteWhitener; cocos2d::CCSprite* m_vehicleGlow; + PAD = mac 0x8; // idk about windows cocos2d::CCMotionStreak* m_regularTrail; HardStreak* m_waveTrail; double m_xAccel; From 3a31efe1132f8383c5362f6954545544fd5aafbe Mon Sep 17 00:00:00 2001 From: altalk23 <45172705+altalk23@users.noreply.github.com> Date: Sat, 10 Dec 2022 19:30:14 +0300 Subject: [PATCH 2/4] implement pimpl for loader --- loader/CMakeLists.txt | 2 + loader/include/Geode/loader/Loader.hpp | 56 +- loader/include/Geode/loader/Mod.hpp | 52 +- loader/include/Geode/loader/Types.hpp | 6 +- loader/include/Geode/utils/cocos.hpp | 2 +- loader/src/hooks/LoadingLayer.cpp | 64 +- loader/src/hooks/MenuLayer.cpp | 207 +++--- loader/src/hooks/save.cpp | 6 +- loader/src/hooks/update.cpp | 4 +- loader/src/internal/InternalLoader.cpp | 250 -------- loader/src/internal/InternalLoader.hpp | 89 --- loader/src/internal/InternalMod.cpp | 10 +- loader/src/loader/Dirs.cpp | 3 +- loader/src/loader/Hook.cpp | 1 - loader/src/loader/IPC.cpp | 29 +- loader/src/loader/Loader.cpp | 514 ++++----------- loader/src/loader/LoaderImpl.cpp | 590 ++++++++++++++++++ loader/src/loader/LoaderImpl.hpp | 142 +++++ loader/src/loader/Log.cpp | 19 +- loader/src/loader/Mod.cpp | 17 +- loader/src/loader/ModInfo.cpp | 30 +- loader/src/main.cpp | 34 +- loader/src/platform/ios/InternalLoader.cpp | 40 -- loader/src/platform/ios/LoaderImpl.cpp | 35 ++ .../{InternalLoader.cpp => LoaderImpl.cpp} | 12 +- .../{InternalLoader.cpp => LoaderImpl.cpp} | 12 +- loader/src/ui/internal/GeodeUI.cpp | 36 +- loader/src/ui/internal/info/ModInfoPopup.cpp | 284 ++++----- loader/src/ui/internal/list/ModListCell.cpp | 155 ++--- loader/src/ui/internal/list/ModListView.cpp | 22 +- 30 files changed, 1358 insertions(+), 1365 deletions(-) delete mode 100644 loader/src/internal/InternalLoader.cpp delete mode 100644 loader/src/internal/InternalLoader.hpp create mode 100644 loader/src/loader/LoaderImpl.cpp create mode 100644 loader/src/loader/LoaderImpl.hpp delete mode 100644 loader/src/platform/ios/InternalLoader.cpp create mode 100644 loader/src/platform/ios/LoaderImpl.cpp rename loader/src/platform/mac/{InternalLoader.cpp => LoaderImpl.cpp} (82%) rename loader/src/platform/windows/{InternalLoader.cpp => LoaderImpl.cpp} (86%) diff --git a/loader/CMakeLists.txt b/loader/CMakeLists.txt index 4078d47f..b715a2cd 100644 --- a/loader/CMakeLists.txt +++ b/loader/CMakeLists.txt @@ -106,6 +106,8 @@ package_geode_resources_now( ) target_include_directories(${PROJECT_NAME} PRIVATE + src/ + src/loader/ src/internal/ src/platform/ src/gui/ diff --git a/loader/include/Geode/loader/Loader.hpp b/loader/include/Geode/loader/Loader.hpp index 3139f3e2..2e38cc1c 100644 --- a/loader/include/Geode/loader/Loader.hpp +++ b/loader/include/Geode/loader/Loader.hpp @@ -1,12 +1,13 @@ #pragma once -#include "Types.hpp" -#include "Log.hpp" #include "../external/filesystem/fs/filesystem.hpp" -#include <mutex> -#include <atomic> #include "../utils/Result.hpp" +#include "Log.hpp" #include "ModInfo.hpp" +#include "Types.hpp" + +#include <atomic> +#include <mutex> namespace geode { using ScheduledFunction = std::function<void GEODE_CALL(void)>; @@ -16,18 +17,16 @@ namespace geode { std::string m_reason; }; - class GEODE_DLL Loader { - protected: - std::vector<ghc::filesystem::path> m_modSearchDirectories; - std::vector<ModInfo> m_modsToLoad; - std::vector<InvalidGeodeFile> m_invalidMods; - std::unordered_map<std::string, Mod*> m_mods; - std::vector<ghc::filesystem::path> m_texturePaths; - std::vector<ScheduledFunction> m_scheduledFunctions; - mutable std::mutex m_scheduledFunctionsMutex; - bool m_isSetup = false; - std::atomic_bool m_earlyLoadFinished = false; + class LoaderImpl; + class GEODE_DLL Loader { + private: + class Impl; + std::unique_ptr<Impl> m_impl; + Loader(); + ~Loader(); + + protected: void createDirectories(); void updateModResources(Mod* mod); @@ -39,31 +38,26 @@ namespace geode { Result<Mod*> loadModFromInfo(ModInfo const& info); public: - ~Loader(); + // TODO: do we want to expose all of these functions? static Loader* get(); - Result<> setup(); - Result<> saveData(); Result<> loadData(); - static VersionInfo getVersion(); - static VersionInfo minModVersion(); - static VersionInfo maxModVersion(); - static bool isModVersionSupported(VersionInfo const& version); + VersionInfo getVersion(); + VersionInfo minModVersion(); + VersionInfo maxModVersion(); + bool isModVersionSupported(VersionInfo const& version); Result<Mod*> loadModFromFile(ghc::filesystem::path const& file); - Result<> loadModsFromDirectory( - ghc::filesystem::path const& dir, - bool recursive = true - ); + Result<> loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive = true); Result<> 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(); - static Mod* getInternalMod(); + Mod* getInternalMod(); void updateAllDependencies(); std::vector<InvalidGeodeFile> getFailedMods() const; @@ -72,16 +66,18 @@ namespace geode { void queueInGDThread(ScheduledFunction func); void scheduleOnModLoad(Mod* mod, ScheduledFunction func); void waitForModsToBeLoaded(); - + /** * Open the platform-specific external console (if one exists) */ - static void openPlatformConsole(); + void openPlatformConsole(); /** * Close the platform-specific external console (if one exists) */ - static void closePlatfromConsole(); + void closePlatformConsole(); bool didLastLaunchCrash() const; + + friend class LoaderImpl; }; } diff --git a/loader/include/Geode/loader/Mod.hpp b/loader/include/Geode/loader/Mod.hpp index d55147cf..bf96122d 100644 --- a/loader/include/Geode/loader/Mod.hpp +++ b/loader/include/Geode/loader/Mod.hpp @@ -1,16 +1,16 @@ #pragma once -#include "Types.hpp" -#include "Hook.hpp" -#include "Setting.hpp" -#include "ModInfo.hpp" - #include "../DefaultInclude.hpp" +#include "../cocos/support/zip_support/ZipUtils.h" +#include "../external/json/json.hpp" #include "../utils/Result.hpp" #include "../utils/VersionInfo.hpp" -#include "../external/json/json.hpp" #include "../utils/general.hpp" -#include "../cocos/support/zip_support/ZipUtils.h" +#include "Hook.hpp" +#include "ModInfo.hpp" +#include "Setting.hpp" +#include "Types.hpp" + #include <optional> #include <string_view> #include <type_traits> @@ -18,15 +18,14 @@ #include <vector> namespace geode { - template<class T> + template <class T> struct HandleToSaved : public T { Mod* m_mod; std::string m_key; - HandleToSaved(std::string const& key, Mod* mod, T const& value) - : T(value), - m_key(key), - m_mod(mod) {} + HandleToSaved(std::string const& key, Mod* mod, T const& value) : + T(value), m_key(key), m_mod(mod) {} + HandleToSaved(HandleToSaved const&) = delete; HandleToSaved(HandleToSaved&&) = delete; ~HandleToSaved(); @@ -108,7 +107,6 @@ namespace geode { friend class ::InternalMod; friend class Loader; - friend class ::InternalLoader; friend struct ModInfo; template <class = void> @@ -173,46 +171,50 @@ namespace geode { return false; } - template<class T> + template <class T> T getSavedValue(std::string const& key) { if (m_saved.count(key)) { try { // json -> T may fail return m_saved.at(key); - } catch(...) {} + } + catch (...) { + } } return T(); } - template<class T> + template <class T> T getSavedValue(std::string const& key, T const& defaultValue) { if (m_saved.count(key)) { try { // json -> T may fail return m_saved.at(key); - } catch(...) {} + } + catch (...) { + } } m_saved[key] = defaultValue; return defaultValue; } - template<class T> + template <class T> HandleToSaved<T> getSavedMutable(std::string const& key) { return HandleToSaved(key, this, this->getSavedValue<T>(key)); } - template<class T> + template <class T> HandleToSaved<T> getSavedMutable(std::string const& key, T const& defaultValue) { return HandleToSaved(key, this, this->getSavedValue<T>(key, defaultValue)); } /** - * Set the value of an automatically saved variable. When the game is + * Set the value of an automatically saved variable. When the game is * closed, the value is automatically saved under the key * @param key Key of the saved value * @param value Value */ - template<class T> + template <class T> void setSavedValue(std::string const& key, T const& value) { m_saved[key] = value; } @@ -338,9 +340,9 @@ 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 + * 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. * @returns Successful result on success, * errorful result with info on error @@ -388,7 +390,7 @@ namespace geode { ModJson getRuntimeInfo() const; }; - template<class T> + template <class T> HandleToSaved<T>::~HandleToSaved() { m_mod->setSavedValue(m_key, static_cast<T>(*this)); } diff --git a/loader/include/Geode/loader/Types.hpp b/loader/include/Geode/loader/Types.hpp index aabf344b..c7542983 100644 --- a/loader/include/Geode/loader/Types.hpp +++ b/loader/include/Geode/loader/Types.hpp @@ -2,9 +2,9 @@ #include "../DefaultInclude.hpp" #include "../platform/cplatform.h" + #include <string> -class InternalLoader; class InternalMod; namespace geode { @@ -149,7 +149,7 @@ namespace geode { }; constexpr std::string_view GEODE_MOD_EXTENSION = ".geode"; - + class Mod; class Setting; class Loader; @@ -165,7 +165,7 @@ namespace geode { template <class, class> class FieldIntermediate; } - + } /** diff --git a/loader/include/Geode/utils/cocos.hpp b/loader/include/Geode/utils/cocos.hpp index de4b2cce..18e36215 100644 --- a/loader/include/Geode/utils/cocos.hpp +++ b/loader/include/Geode/utils/cocos.hpp @@ -338,7 +338,7 @@ namespace geode { template <class C> static EventListenerNode* create( - C* cls, typename EventListener<Filter>::MemberFn<C> callback + C* cls, typename EventListener<Filter>::template MemberFn<C> callback ) { // for some reason msvc won't let me just call EventListenerNode::create... // it claims no return value... diff --git a/loader/src/hooks/LoadingLayer.cpp b/loader/src/hooks/LoadingLayer.cpp index e37d749b..7121519c 100644 --- a/loader/src/hooks/LoadingLayer.cpp +++ b/loader/src/hooks/LoadingLayer.cpp @@ -1,9 +1,9 @@ -#include <InternalLoader.hpp> -#include <array> #include <Geode/modify/LoadingLayer.hpp> -#include <fmt/format.h> #include <Geode/utils/cocos.hpp> +#include <array> +#include <fmt/format.h> +#include <loader/LoaderImpl.hpp> USE_GEODE_NAMESPACE(); @@ -19,23 +19,21 @@ struct CustomLoadingLayer : Modify<CustomLoadingLayer, LoadingLayer> { auto count = Loader::get()->getAllMods().size(); - auto label = CCLabelBMFont::create( - fmt::format("Geode: Loaded {} mods", count).c_str(), - "goldFont.fnt" - ); + auto label = + CCLabelBMFont::create(fmt::format("Geode: Loaded {} mods", count).c_str(), "goldFont.fnt"); label->setPosition(winSize.width / 2, 30.f); label->setScale(.45f); label->setID("geode-loaded-info"); this->addChild(label); - // for some reason storing the listener as a field caused the + // for some reason storing the listener as a field caused the // destructor for the field not to be run this->addChild(EventListenerNode<ResourceDownloadFilter>::create( this, &CustomLoadingLayer::updateResourcesProgress )); // verify loader resources - if (!InternalLoader::get()->verifyLoaderResources()) { + if (!LoaderImpl::get()->verifyLoaderResources()) { m_fields->m_updatingResources = true; this->setUpdateText("Downloading Resources"); } @@ -48,30 +46,30 @@ struct CustomLoadingLayer : Modify<CustomLoadingLayer, LoadingLayer> { } void updateResourcesProgress(ResourceDownloadEvent* event) { - std::visit(makeVisitor { - [&](UpdateProgress const& progress) { - this->setUpdateText(fmt::format( - "Downloading Resources: {}%", progress.first - )); - }, - [&](UpdateFinished) { - this->setUpdateText("Resources Downloaded"); - m_fields->m_updatingResources = false; - this->loadAssets(); - }, - [&](UpdateError const& error) { - InternalLoader::platformMessageBox( - "Error updating resources", - "Unable to update Geode resources: " + - error + ".\n" - "The game will be loaded as normal, but please be aware " - "that it may very likely crash." - ); - this->setUpdateText("Resource Download Failed"); - m_fields->m_updatingResources = false; - this->loadAssets(); - } - }, event->status); + std::visit( + makeVisitor{ + [&](UpdateProgress const& progress) { + this->setUpdateText(fmt::format("Downloading Resources: {}%", progress.first)); + }, + [&](UpdateFinished) { + this->setUpdateText("Resources Downloaded"); + m_fields->m_updatingResources = false; + this->loadAssets(); + }, + [&](UpdateError const& error) { + LoaderImpl::get()->platformMessageBox( + "Error updating resources", + "Unable to update Geode resources: " + error + + ".\n" + "The game will be loaded as normal, but please be aware " + "that it may very likely crash." + ); + this->setUpdateText("Resource Download Failed"); + m_fields->m_updatingResources = false; + this->loadAssets(); + }}, + event->status + ); } void loadAssets() { diff --git a/loader/src/hooks/MenuLayer.cpp b/loader/src/hooks/MenuLayer.cpp index af80cdad..9bc6cac2 100644 --- a/loader/src/hooks/MenuLayer.cpp +++ b/loader/src/hooks/MenuLayer.cpp @@ -1,17 +1,16 @@ -#include <Geode/utils/cocos.hpp> +#include "../ids/AddIDs.hpp" #include "../ui/internal/list/ModListLayer.hpp" + +#include <Geode/loader/Index.hpp> +#include <Geode/modify/MenuLayer.hpp> +#include <Geode/modify/Modify.hpp> #include <Geode/ui/BasedButtonSprite.hpp> -#include <Geode/ui/Notification.hpp> #include <Geode/ui/GeodeUI.hpp> +#include <Geode/ui/Notification.hpp> #include <Geode/ui/Popup.hpp> #include <Geode/utils/cocos.hpp> -#include <Geode/loader/Index.hpp> -#include <InternalLoader.hpp> -#include "../ids/AddIDs.hpp" #include <InternalMod.hpp> -#include <Geode/modify/Modify.hpp> -#include <Geode/modify/MenuLayer.hpp> USE_GEODE_NAMESPACE(); @@ -23,116 +22,112 @@ static Ref<Notification> INDEX_UPDATE_NOTIF = nullptr; static Ref<CCSprite> g_geodeButton = nullptr; struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> { - void destructor() { - g_geodeButton = nullptr; - MenuLayer::~MenuLayer(); - } + void destructor() { + g_geodeButton = nullptr; + MenuLayer::~MenuLayer(); + } - bool init() { - if (!MenuLayer::init()) - return false; - - // make sure to add the string IDs for nodes (Geode has no manual - // hook order support yet so gotta do this to ensure) - NodeIDs::provideFor(this); + bool init() { + if (!MenuLayer::init()) return false; - auto winSize = CCDirector::sharedDirector()->getWinSize(); + // make sure to add the string IDs for nodes (Geode has no manual + // hook order support yet so gotta do this to ensure) + NodeIDs::provideFor(this); - // add geode button - - g_geodeButton = SafeCreate<CCSprite>() - .with(CircleButtonSprite::createWithSpriteFrameName( - "geode-logo-outline-gold.png"_spr, - 1.0f, - CircleBaseColor::Green, - CircleBaseSize::Medium2 - )) - .orMake<ButtonSprite>("!!"); + auto winSize = CCDirector::sharedDirector()->getWinSize(); - auto bottomMenu = static_cast<CCMenu*>(this->getChildByID("bottom-menu")); + // add geode button - auto btn = CCMenuItemSpriteExtra::create( - g_geodeButton.data(), this, menu_selector(CustomMenuLayer::onGeode) - ); - btn->setID("geode-button"_spr); - bottomMenu->addChild(btn); + g_geodeButton = + SafeCreate<CCSprite>() + .with(CircleButtonSprite::createWithSpriteFrameName( + "geode-logo-outline-gold.png"_spr, 1.0f, CircleBaseColor::Green, CircleBaseSize::Medium2 + )) + .orMake<ButtonSprite>("!!"); - bottomMenu->updateLayout(); + auto bottomMenu = static_cast<CCMenu*>(this->getChildByID("bottom-menu")); - if (auto node = this->getChildByID("settings-gamepad-icon")) { - node->setPositionX(bottomMenu->getChildByID( - "settings-button" - )->getPositionX() + winSize.width / 2); - } + auto btn = CCMenuItemSpriteExtra::create( + g_geodeButton.data(), this, menu_selector(CustomMenuLayer::onGeode) + ); + btn->setID("geode-button"_spr); + bottomMenu->addChild(btn); - // show if some mods failed to load - static bool shownFailedNotif = false; - if (!shownFailedNotif) { - shownFailedNotif = true; - if (Loader::get()->getFailedMods().size()) { - Notification::create( - "Some mods failed to load", - NotificationIcon::Error - )->show(); - } + bottomMenu->updateLayout(); + + if (auto node = this->getChildByID("settings-gamepad-icon")) { + node->setPositionX( + bottomMenu->getChildByID("settings-button")->getPositionX() + winSize.width / 2 + ); } - // show crash info - static bool shownLastCrash = false; - if (Loader::get()->didLastLaunchCrash() && !shownLastCrash) { - shownLastCrash = true; - auto popup = createQuickPopup( - "Crashed", - "It appears that the last session crashed. Would you like to " - "send a <cy>crash report</c>?", - "No", "Send", - [](auto, bool btn2) { - if (btn2) { - geode::openIssueReportPopup(InternalMod::get()); - } - }, - false - ); - popup->m_scene = this; - popup->m_noElasticity = true; - popup->show(); - } + // show if some mods failed to load + static bool shownFailedNotif = false; + if (!shownFailedNotif) { + shownFailedNotif = true; + if (Loader::get()->getFailedMods().size()) { + Notification::create("Some mods failed to load", NotificationIcon::Error)->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(); - } - - return true; - } + // show crash info + static bool shownLastCrash = false; + if (Loader::get()->didLastLaunchCrash() && !shownLastCrash) { + shownLastCrash = true; + auto popup = createQuickPopup( + "Crashed", + "It appears that the last session crashed. Would you like to " + "send a <cy>crash report</c>?", + "No", + "Send", + [](auto, bool btn2) { + if (btn2) { + geode::openIssueReportPopup(InternalMod::get()); + } + }, + false + ); + popup->m_scene = this; + popup->m_noElasticity = true; + popup->show(); + } - void onIndexUpdate(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; - }, - [](UpdateError 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); - } + // 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(); + } - void onGeode(CCObject*) { - ModListLayer::scene(); - } + return true; + } + + void onIndexUpdate(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; + }, + [](UpdateError 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 + ); + } + + void onGeode(CCObject*) { + ModListLayer::scene(); + } }; diff --git a/loader/src/hooks/save.cpp b/loader/src/hooks/save.cpp index 23232e73..a0db4471 100644 --- a/loader/src/hooks/save.cpp +++ b/loader/src/hooks/save.cpp @@ -6,14 +6,14 @@ USE_GEODE_NAMESPACE(); struct SaveLoader : Modify<SaveLoader, AppDelegate> { void trySaveGame() { - log::log(Severity::Info, Loader::getInternalMod(), "Saving..."); + log::info("Saving..."); auto r = Loader::get()->saveData(); if (!r) { - log::log(Severity::Error, Loader::getInternalMod(), "{}", r.unwrapErr()); + log::info("{}", r.unwrapErr()); } - log::log(Severity::Info, Loader::getInternalMod(), "Saved"); + log::info("Saved"); return AppDelegate::trySaveGame(); } diff --git a/loader/src/hooks/update.cpp b/loader/src/hooks/update.cpp index 7f789d51..fa160c5a 100644 --- a/loader/src/hooks/update.cpp +++ b/loader/src/hooks/update.cpp @@ -1,4 +1,4 @@ -#include <InternalLoader.hpp> +#include "../loader/LoaderImpl.hpp" USE_GEODE_NAMESPACE(); @@ -6,7 +6,7 @@ USE_GEODE_NAMESPACE(); struct FunctionQueue : Modify<FunctionQueue, CCScheduler> { void update(float dt) { - InternalLoader::get()->executeGDThreadQueue(); + LoaderImpl::get()->executeGDThreadQueue(); return CCScheduler::update(dt); } }; diff --git a/loader/src/internal/InternalLoader.cpp b/loader/src/internal/InternalLoader.cpp deleted file mode 100644 index 1353eb27..00000000 --- a/loader/src/internal/InternalLoader.cpp +++ /dev/null @@ -1,250 +0,0 @@ -#include "InternalLoader.hpp" - -#include "InternalMod.hpp" -#include "resources.hpp" - -#include <Geode/loader/Loader.hpp> -#include <Geode/loader/IPC.hpp> -#include <Geode/loader/Log.hpp> -#include <Geode/loader/Dirs.hpp> -#include <Geode/utils/web.hpp> -#include <Geode/utils/file.hpp> -#include <fmt/format.h> -#include <hash.hpp> -#include <iostream> -#include <sstream> -#include <string> -#include <thread> -#include <vector> - -ResourceDownloadEvent::ResourceDownloadEvent( - UpdateStatus const& status -) : status(status) {} - -ListenerResult ResourceDownloadFilter::handle( - std::function<Callback> fn, - ResourceDownloadEvent* event -) { - fn(event); - return ListenerResult::Propagate; -} - -ResourceDownloadFilter::ResourceDownloadFilter() {} - -InternalLoader::InternalLoader() : Loader() {} - -InternalLoader::~InternalLoader() { - this->closePlatformConsole(); -} - -InternalLoader* InternalLoader::get() { - static auto g_geode = new InternalLoader; - return g_geode; -} - -bool InternalLoader::setup() { - log::log(Severity::Debug, InternalMod::get(), "Set up internal mod representation"); - log::log(Severity::Debug, InternalMod::get(), "Loading hooks... "); - - if (!this->loadHooks()) { - log::log( - Severity::Error, InternalMod::get(), - "There were errors loading some hooks, see console for details" - ); - } - - log::log(Severity::Debug, InternalMod::get(), "Loaded hooks"); - - log::log(Severity::Debug, InternalMod::get(), "Setting up IPC..."); - - this->setupIPC(); - - return true; -} - -bool InternalLoader::isReadyToHook() const { - return m_readyToHook; -} - -void InternalLoader::addInternalHook(Hook* hook, Mod* mod) { - m_internalHooks.push_back({hook, mod}); -} - -bool InternalLoader::loadHooks() { - m_readyToHook = true; - auto thereWereErrors = false; - for (auto const& hook : m_internalHooks) { - auto res = hook.second->addHook(hook.first); - if (!res) { - log::log(Severity::Error, hook.second, "{}", res.unwrapErr()); - thereWereErrors = true; - } - } - // free up memory - m_internalHooks.clear(); - return !thereWereErrors; -} - -void InternalLoader::queueInGDThread(ScheduledFunction func) { - std::lock_guard<std::mutex> lock(m_gdThreadMutex); - m_gdThreadQueue.push_back(func); -} - -void InternalLoader::executeGDThreadQueue() { - // copy queue to avoid locking mutex if someone is - // running addToGDThread inside their function - m_gdThreadMutex.lock(); - auto queue = m_gdThreadQueue; - m_gdThreadQueue.clear(); - m_gdThreadMutex.unlock(); - - // call queue - for (auto const& func : queue) { - func(); - } -} - -void InternalLoader::logConsoleMessage(std::string const& msg) { - if (m_platformConsoleOpen) { - // TODO: make flushing optional - std::cout << msg << '\n' << std::flush; - } -} - -bool InternalLoader::platformConsoleOpen() const { - return m_platformConsoleOpen; -} - -bool InternalLoader::shownInfoAlert(std::string const& key) { - if (m_shownInfoAlerts.count(key)) { - return true; - } - m_shownInfoAlerts.insert(key); - return false; -} - -void InternalLoader::saveInfoAlerts(nlohmann::json& json) { - json["alerts"] = m_shownInfoAlerts; -} - -void InternalLoader::loadInfoAlerts(nlohmann::json& json) { - m_shownInfoAlerts = json["alerts"].get<std::unordered_set<std::string>>(); -} - -void InternalLoader::downloadLoaderResources() { - auto version = this->getVersion().toString(); - auto tempResourcesZip = dirs::getTempDir() / "new.zip"; - auto resourcesDir = dirs::getGeodeResourcesDir() / InternalMod::get()->getID(); - - web::AsyncWebRequest() - .join("update-geode-loader-resources") - .fetch(fmt::format( - "https://github.com/geode-sdk/geode/releases/download/{}/resources.zip", version - )) - .into(tempResourcesZip) - .then([tempResourcesZip, resourcesDir](auto) { - // unzip resources zip - auto unzip = file::Unzip::intoDir(tempResourcesZip, resourcesDir, true); - if (!unzip) { - return ResourceDownloadEvent( - UpdateError("Unable to unzip new resources: " + unzip.unwrapErr()) - ).post(); - } - ResourceDownloadEvent(UpdateFinished()).post(); - }) - .expect([](std::string const& info) { - ResourceDownloadEvent( - UpdateError("Unable to download resources: " + info) - ).post(); - }) - .progress([](auto&, double now, double total) { - ResourceDownloadEvent( - UpdateProgress( - static_cast<uint8_t>(now / total * 100.0), - "Downloading resources" - ) - ).post(); - }); -} - -bool InternalLoader::verifyLoaderResources() { - static std::optional<bool> CACHED = std::nullopt; - if (CACHED.has_value()) { - return CACHED.value(); - } - - // geode/resources/geode.loader - auto resourcesDir = dirs::getGeodeResourcesDir() / InternalMod::get()->getID(); - - // if the resources dir doesn't exist, then it's probably incorrect - if (!( - ghc::filesystem::exists(resourcesDir) && - ghc::filesystem::is_directory(resourcesDir) - )) { - this->downloadLoaderResources(); - return false; - } - - // make sure every file was covered - size_t coverage = 0; - - // verify hashes - for (auto& file : ghc::filesystem::directory_iterator(resourcesDir)) { - auto name = file.path().filename().string(); - // skip unknown files - if (!LOADER_RESOURCE_HASHES.count(name)) { - continue; - } - // verify hash - auto hash = calculateSHA256(file.path()); - if (hash != LOADER_RESOURCE_HASHES.at(name)) { - log::debug( - "compare {} {} {}", file.path().string(), hash, LOADER_RESOURCE_HASHES.at(name) - ); - this->downloadLoaderResources(); - return false; - } - coverage += 1; - } - - // make sure every file was found - if (coverage != LOADER_RESOURCE_HASHES.size()) { - this->downloadLoaderResources(); - return false; - } - - return true; -} - -std::string InternalLoader::processRawIPC(void* rawHandle, std::string const& buffer) { - std::string reply; - - try { - std::optional<std::string> replyID = std::nullopt; - - // parse received message - auto json = nlohmann::json::parse(buffer); - if (!json.contains("mod") || !json["mod"].is_string()) { - log::warn("Received IPC message without 'mod' field"); - return reply; - } - if (!json.contains("message") || !json["message"].is_string()) { - log::warn("Received IPC message without 'message' field"); - return reply; - } - if (json.contains("reply") && json["reply"].is_string()) { - replyID = json["reply"]; - } - nlohmann::json data; - if (json.contains("data")) { - data = json["data"]; - } - // log::debug("Posting IPC event"); - // ! warning: if the event system is ever made asynchronous this will break! - IPCEvent(rawHandle, json["mod"], json["message"], data, &reply).post(); - } catch(...) { - log::warn("Received IPC message that isn't valid JSON"); - } - - return reply; -} diff --git a/loader/src/internal/InternalLoader.hpp b/loader/src/internal/InternalLoader.hpp deleted file mode 100644 index 57707acc..00000000 --- a/loader/src/internal/InternalLoader.hpp +++ /dev/null @@ -1,89 +0,0 @@ -#pragma once - -#include "FileWatcher.hpp" - -#include <Geode/loader/Index.hpp> -#include <Geode/loader/Loader.hpp> -#include <Geode/loader/Log.hpp> -#include <Geode/utils/Result.hpp> -#include <Geode/external/json/json.hpp> - -#include <mutex> -#include <optional> -#include <unordered_map> -#include <unordered_set> -#include <vector> -#include <thread> - -USE_GEODE_NAMESPACE(); - -struct ResourceDownloadEvent : public Event { - const UpdateStatus status; - ResourceDownloadEvent(UpdateStatus const& status); -}; - -class GEODE_DLL ResourceDownloadFilter : public EventFilter<ResourceDownloadEvent> { -public: - using Callback = void(ResourceDownloadEvent*); - - ListenerResult handle(std::function<Callback> fn, ResourceDownloadEvent* event); - ResourceDownloadFilter(); -}; - -/** - * Internal extension of Loader for private information - * @class InternalLoader - */ -class InternalLoader : public Loader { -protected: - std::vector<std::function<void(void)>> m_gdThreadQueue; - mutable std::mutex m_gdThreadMutex; - bool m_platformConsoleOpen = false; - std::unordered_set<std::string> m_shownInfoAlerts; - - std::vector<std::pair<Hook*, Mod*>> m_internalHooks; - bool m_readyToHook; - - void saveInfoAlerts(nlohmann::json& json); - void loadInfoAlerts(nlohmann::json& json); - - void downloadLoaderResources(); - - bool loadHooks(); - void setupIPC(); - - InternalLoader(); - ~InternalLoader(); - - friend class Loader; - -public: - static InternalLoader* get(); - - bool setup(); - - static std::string processRawIPC(void* rawHandle, std::string const& buffer); - - /** - * Check if a one-time event has been shown to the user, - * and set it to true if not. Will return the previous - * state of the event before setting it to true - */ - bool shownInfoAlert(std::string const& key); - - void queueInGDThread(ScheduledFunction func); - void executeGDThreadQueue(); - - void logConsoleMessage(std::string const& msg); - bool platformConsoleOpen() const; - void openPlatformConsole(); - void closePlatformConsole(); - static void platformMessageBox(char const* title, std::string const& info); - - bool verifyLoaderResources(); - - bool isReadyToHook() const; - void addInternalHook(Hook* hook, Mod* mod); - - friend int geodeEntry(void* platformData); -}; diff --git a/loader/src/internal/InternalMod.cpp b/loader/src/internal/InternalMod.cpp index 9575ebdb..cab08cd1 100644 --- a/loader/src/internal/InternalMod.cpp +++ b/loader/src/internal/InternalMod.cpp @@ -1,8 +1,10 @@ #include "InternalMod.hpp" -#include <Geode/loader/Dirs.hpp> -#include "InternalLoader.hpp" + #include "about.hpp" +#include <Geode/loader/Dirs.hpp> +#include <LoaderImpl.hpp> + static constexpr char const* SUPPORT_INFO = R"MD( **Geode** is funded through your gracious <cy>**donations**</c>! You can support our work by sending <cp>**catgirl pictures**</c> to [HJfod](user:104257) :)) @@ -13,7 +15,7 @@ static ModInfo getInternalModInfo() { auto json = ModJson::parse(LOADER_MOD_JSON); auto infoRes = ModInfo::create(json); if (infoRes.isErr()) { - InternalLoader::platformMessageBox( + LoaderImpl::get()->platformMessageBox( "Fatal Internal Error", "Unable to parse loader mod.json: \"" + infoRes.unwrapErr() + "\"\n" @@ -29,7 +31,7 @@ static ModInfo getInternalModInfo() { return info; } catch (std::exception& e) { - InternalLoader::platformMessageBox( + LoaderImpl::get()->platformMessageBox( "Fatal Internal Error", "Unable to parse loader mod.json: \"" + std::string(e.what()) + "\"\n" diff --git a/loader/src/loader/Dirs.cpp b/loader/src/loader/Dirs.cpp index 4ef28cf2..275802fc 100644 --- a/loader/src/loader/Dirs.cpp +++ b/loader/src/loader/Dirs.cpp @@ -14,8 +14,9 @@ ghc::filesystem::path dirs::getSaveDir() { // not using ~/Library/Caches return ghc::filesystem::path("/Users/Shared/Geode"); #elif defined(GEODE_IS_WINDOWS) + // this is std::filesystem intentionally because ghc version doesnt want to work with softlinked directories return ghc::filesystem::path( - ghc::filesystem::weakly_canonical( + std::filesystem::weakly_canonical( CCFileUtils::sharedFileUtils()->getWritablePath().c_str() ).string() ); diff --git a/loader/src/loader/Hook.cpp b/loader/src/loader/Hook.cpp index 79d666f9..72188dac 100644 --- a/loader/src/loader/Hook.cpp +++ b/loader/src/loader/Hook.cpp @@ -5,7 +5,6 @@ #include <Geode/utils/ranges.hpp> #include <vector> // #include <hook/hook.hpp> -#include "InternalLoader.hpp" #include "InternalMod.hpp" #include <Geode/hook-core/Hook.hpp> diff --git a/loader/src/loader/IPC.cpp b/loader/src/loader/IPC.cpp index ff1d0293..74e7d225 100644 --- a/loader/src/loader/IPC.cpp +++ b/loader/src/loader/IPC.cpp @@ -1,24 +1,17 @@ #include <Geode/loader/IPC.hpp> -#include <InternalLoader.hpp> USE_GEODE_NAMESPACE(); IPCEvent::IPCEvent( - void* rawPipeHandle, - std::string const& targetModID, - std::string const& messageID, - nlohmann::json const& messageData, - std::string* replyString -) : m_rawPipeHandle(rawPipeHandle), - m_targetModID(targetModID), - m_messageID(messageID), - m_replyString(replyString), + void* rawPipeHandle, std::string const& targetModID, std::string const& messageID, + nlohmann::json const& messageData, std::string* replyString +) : + m_rawPipeHandle(rawPipeHandle), + m_targetModID(targetModID), m_messageID(messageID), m_replyString(replyString), m_messageData(messageData) {} IPCEvent::~IPCEvent() {} - - std::string IPCEvent::getTargetModID() const { return m_targetModID; } @@ -40,18 +33,12 @@ nlohmann::json IPCEvent::getMessageData() const { } ListenerResult IPCFilter::handle(std::function<Callback> fn, IPCEvent* event) { - if ( - event->getTargetModID() == m_modID && - event->getMessageID() == m_messageID - ) { + if (event->getTargetModID() == m_modID && event->getMessageID() == m_messageID) { event->setReplyString(fn(event)); return ListenerResult::Stop; } return ListenerResult::Propagate; } -IPCFilter::IPCFilter( - std::string const& modID, - std::string const& messageID -) : m_modID(modID), - m_messageID(messageID) {} +IPCFilter::IPCFilter(std::string const& modID, std::string const& messageID) : + m_modID(modID), m_messageID(messageID) {} diff --git a/loader/src/loader/Loader.cpp b/loader/src/loader/Loader.cpp index 5d542fdb..e4193e2d 100644 --- a/loader/src/loader/Loader.cpp +++ b/loader/src/loader/Loader.cpp @@ -1,388 +1,126 @@ - -#include <Geode/loader/Loader.hpp> -#include <Geode/loader/Mod.hpp> -#include <Geode/loader/Dirs.hpp> -#include <InternalLoader.hpp> -#include <InternalMod.hpp> -#include <about.hpp> -#include <Geode/utils/ranges.hpp> -#include <Geode/utils/map.hpp> -#include <crashlog.hpp> - -USE_GEODE_NAMESPACE(); - -Loader* Loader::get() { - return InternalLoader::get(); -} - -Loader::~Loader() { - for (auto& [_, mod] : m_mods) { - delete mod; - } - m_mods.clear(); - log::Logs::clear(); - ghc::filesystem::remove_all(dirs::getModRuntimeDir()); - ghc::filesystem::remove_all(dirs::getTempDir()); -} - -// Initialization - -void Loader::createDirectories() { -#ifdef GEODE_IS_MACOS - ghc::filesystem::create_directory(dirs::getSaveDir()); -#endif - - ghc::filesystem::create_directories(dirs::getGeodeResourcesDir()); - ghc::filesystem::create_directory(dirs::getModConfigDir()); - ghc::filesystem::create_directory(dirs::getModsDir()); - ghc::filesystem::create_directory(dirs::getGeodeLogDir()); - ghc::filesystem::create_directory(dirs::getTempDir()); - ghc::filesystem::create_directory(dirs::getModRuntimeDir()); - - if (!ranges::contains(m_modSearchDirectories, dirs::getModsDir())) { - m_modSearchDirectories.push_back(dirs::getModsDir()); - } -} - -Result<> Loader::setup() { - if (m_isSetup) { - return Ok(); - } - - log::Logs::setup(); - - if (crashlog::setupPlatformHandler()) { - log::debug("Set up platform crash logger"); - } - else { - log::debug("Unable to set up platform crash logger"); - } - - log::debug("Setting up Loader..."); - - this->createDirectories(); - auto sett = this->loadData(); - if (!sett) { - log::warn("Unable to load loader settings: {}", sett.unwrapErr()); - } - GEODE_UNWRAP(this->refreshModsList()); - - this->queueInGDThread([]() { - Loader::get()->addSearchPaths(); - }); - - m_isSetup = true; - - return Ok(); -} - -void Loader::addSearchPaths() { - CCFileUtils::get()->addPriorityPath(dirs::getGeodeResourcesDir().string().c_str()); - CCFileUtils::get()->addPriorityPath(dirs::getModRuntimeDir().string().c_str()); -} - -void Loader::updateResources() { - log::debug("Adding resources"); - - // add own spritesheets - this->updateModResources(InternalMod::get()); - - // add mods' spritesheets - for (auto const& [_, mod] : m_mods) { - this->updateModResources(mod); - } -} - -std::vector<Mod*> Loader::getAllMods() { - return map::values(m_mods); -} - -Mod* Loader::getInternalMod() { - return InternalMod::get(); -} - -std::vector<InvalidGeodeFile> Loader::getFailedMods() const { - return m_invalidMods; -} - -// Version info - -VersionInfo Loader::getVersion() { - return LOADER_VERSION; -} - -VersionInfo Loader::minModVersion() { - return VersionInfo { 0, 3, 1 }; -} - -VersionInfo Loader::maxModVersion() { - return VersionInfo { - Loader::getVersion().getMajor(), - Loader::getVersion().getMinor(), - // todo: dynamic version info (vM.M.*) - 99999999, - }; -} - -bool Loader::isModVersionSupported(VersionInfo const& version) { - return - version >= Loader::minModVersion() && - version <= Loader::maxModVersion(); -} - -// Data saving - -Result<> Loader::saveData() { - // save mods' data - for (auto& [_, mod] : m_mods) { - auto r = mod->saveData(); - if (!r) { - log::warn("Unable to save data for mod \"{}\": {}", mod->getID(), r.unwrapErr()); - } - } - // save loader data - GEODE_UNWRAP(InternalMod::get()->saveData()); - - return Ok(); -} - -Result<> Loader::loadData() { - auto e = InternalMod::get()->loadData(); - if (!e) { - log::warn("Unable to load loader settings: {}", e.unwrapErr()); - } - for (auto& [_, mod] : m_mods) { - auto r = mod->loadData(); - if (!r) { - log::warn("Unable to load data for mod \"{}\": {}", mod->getID(), r.unwrapErr()); - } - } - return Ok(); -} - -// Mod loading - -Result<Mod*> Loader::loadModFromInfo(ModInfo const& info) { - if (m_mods.count(info.m_id)) { - return Err(fmt::format("Mod with ID '{}' already loaded", info.m_id)); - } - - // create Mod instance - auto mod = new Mod(info); - m_mods.insert({ info.m_id, mod }); - mod->m_enabled = InternalMod::get()->getSavedValue<bool>( - "should-load-" + info.m_id, true - ); - // this loads the mod if its dependencies are resolved - mod->updateDependencyStates(); - - // 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::loadModFromFile(ghc::filesystem::path const& file) { - auto res = ModInfo::createFromGeodeFile(file); - if (!res) { - m_invalidMods.push_back(InvalidGeodeFile { - .m_path = file, - .m_reason = res.unwrapErr(), - }); - return Err(res.unwrapErr()); - } - return this->loadModFromInfo(res.unwrap()); -} - -bool Loader::isModInstalled(std::string const& id) const { - return m_mods.count(id) && !m_mods.at(id)->isUninstalled(); -} - -Mod* Loader::getInstalledMod(std::string const& id) const { - if (m_mods.count(id) && !m_mods.at(id)->isUninstalled()) { - return m_mods.at(id); - } - return nullptr; -} - -bool Loader::isModLoaded(std::string const& id) const { - return m_mods.count(id) && m_mods.at(id)->isLoaded(); -} - -Mod* Loader::getLoadedMod(std::string const& id) const { - if (m_mods.count(id)) { - auto mod = m_mods.at(id); - if (mod->isLoaded()) { - return mod; - } - } - return nullptr; -} - -void Loader::dispatchScheduledFunctions(Mod* mod) { - std::lock_guard _(m_scheduledFunctionsMutex); - for (auto& func : m_scheduledFunctions) { - func(); - } - m_scheduledFunctions.clear(); -} - -void Loader::scheduleOnModLoad(Mod* mod, ScheduledFunction func) { - std::lock_guard _(m_scheduledFunctionsMutex); - if (mod) { - return func(); - } - m_scheduledFunctions.push_back(func); -} - -void Loader::updateModResources(Mod* mod) { - if (!mod->m_info.m_spritesheets.size()) { - return; - } - - auto searchPath = dirs::getModRuntimeDir() / mod->getID() / "resources"; - - log::debug("Adding resources for {}", mod->getID()); - - // add spritesheets - for (auto const& sheet : mod->m_info.m_spritesheets) { - log::debug("Adding sheet {}", sheet); - auto png = sheet + ".png"; - auto plist = sheet + ".plist"; - auto ccfu = CCFileUtils::get(); - - if (png == std::string(ccfu->fullPathForFilename(png.c_str(), false)) || - plist == std::string(ccfu->fullPathForFilename(plist.c_str(), false))) { - log::warn( - "The resource dir of \"{}\" is missing \"{}\" png and/or plist files", - mod->m_info.m_id, sheet - ); - } - else { - CCTextureCache::get()->addImage(png.c_str(), false); - CCSpriteFrameCache::get()->addSpriteFramesWithFile(plist.c_str()); - } - } -} - -// Dependencies and refreshing - -Result<> Loader::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) { - GEODE_UNWRAP(this->loadModsFromDirectory(entry.path(), true)); - continue; - } - - // skip this entry if it's not a file - if (!ghc::filesystem::is_regular_file(entry)) { - continue; - } - - // 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_info.m_path == entry.path(); - })) { - continue; - } - - // if mods should be loaded immediately, do that - if (m_earlyLoadFinished) { - GEODE_UNWRAP(this->loadModFromFile(entry)); - } - // 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 { - .m_path = entry.path(), - .m_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); - } - } - return Ok(); -} - -Result<> Loader::refreshModsList() { - log::debug("Loading mods..."); - - // find mods - for (auto& dir : m_modSearchDirectories) { - GEODE_UNWRAP(this->loadModsFromDirectory(dir)); - } - - // load early-load mods first - for (auto& mod : m_modsToLoad) { - if (mod.m_needsEarlyLoad) { - GEODE_UNWRAP(this->loadModFromInfo(mod)); - } - } - - // UI can be loaded now - m_earlyLoadFinished = true; - - // load the rest of the mods - for (auto& mod : m_modsToLoad) { - if (!mod.m_needsEarlyLoad) { - GEODE_UNWRAP(this->loadModFromInfo(mod)); - } - } - m_modsToLoad.clear(); - - return Ok(); -} - -void Loader::updateAllDependencies() { - for (auto const& [_, mod] : m_mods) { - mod->updateDependencyStates(); - } -} - -void Loader::waitForModsToBeLoaded() { - while (!m_earlyLoadFinished) {} -} - -// Misc - -void Loader::queueInGDThread(ScheduledFunction func) { - InternalLoader::get()->queueInGDThread(func); -} - -bool Loader::didLastLaunchCrash() const { - return crashlog::didLastLaunchCrash(); -} - -void Loader::openPlatformConsole() { - InternalLoader::get()->openPlatformConsole(); -} - -void Loader::closePlatfromConsole() { - InternalLoader::get()->closePlatformConsole(); -} +#include "LoaderImpl.hpp" + +Loader::Loader() : m_impl(new Impl) {} + +Loader::~Loader() {} + +Loader* Loader::get() { + static auto g_geode = new Loader; + return g_geode; +} + +void Loader::createDirectories() { + return m_impl->createDirectories(); +} + +void Loader::updateModResources(Mod* mod) { + return m_impl->updateModResources(mod); +} + +void Loader::addSearchPaths() { + return m_impl->addSearchPaths(); +} + +void Loader::dispatchScheduledFunctions(Mod* mod) { + return m_impl->dispatchScheduledFunctions(mod); +} + +Result<Mod*> Loader::loadModFromInfo(ModInfo const& info) { + return m_impl->loadModFromInfo(info); +} + +Result<> Loader::saveData() { + return m_impl->saveData(); +} + +Result<> Loader::loadData() { + return m_impl->loadData(); +} + +VersionInfo Loader::getVersion() { + return m_impl->getVersion(); +} + +VersionInfo Loader::minModVersion() { + return m_impl->minModVersion(); +} + +VersionInfo Loader::maxModVersion() { + return m_impl->maxModVersion(); +} + +bool Loader::isModVersionSupported(VersionInfo const& version) { + return m_impl->isModVersionSupported(version); +} + +Result<Mod*> Loader::loadModFromFile(ghc::filesystem::path const& file) { + return m_impl->loadModFromFile(file); +} + +Result<> Loader::loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive) { + return m_impl->loadModsFromDirectory(dir, recursive); +} + +Result<> Loader::refreshModsList() { + return m_impl->refreshModsList(); +} + +bool Loader::isModInstalled(std::string const& id) const { + return m_impl->isModInstalled(id); +} + +Mod* Loader::getInstalledMod(std::string const& id) const { + return m_impl->getInstalledMod(id); +} + +bool Loader::isModLoaded(std::string const& id) const { + return m_impl->isModLoaded(id); +} + +Mod* Loader::getLoadedMod(std::string const& id) const { + return m_impl->getLoadedMod(id); +} + +std::vector<Mod*> Loader::getAllMods() { + return m_impl->getAllMods(); +} + +Mod* Loader::getInternalMod() { + return m_impl->getInternalMod(); +} + +void Loader::updateAllDependencies() { + return m_impl->updateAllDependencies(); +} + +std::vector<InvalidGeodeFile> Loader::getFailedMods() const { + return m_impl->getFailedMods(); +} + +void Loader::updateResources() { + return m_impl->updateResources(); +} + +void Loader::queueInGDThread(ScheduledFunction func) { + return m_impl->queueInGDThread(func); +} + +void Loader::scheduleOnModLoad(Mod* mod, ScheduledFunction func) { + return m_impl->scheduleOnModLoad(mod, func); +} + +void Loader::waitForModsToBeLoaded() { + return m_impl->waitForModsToBeLoaded(); +} + +void Loader::openPlatformConsole() { + return m_impl->openPlatformConsole(); +} + +void Loader::closePlatformConsole() { + return m_impl->closePlatformConsole(); +} + +bool Loader::didLastLaunchCrash() const { + return m_impl->didLastLaunchCrash(); +} \ No newline at end of file diff --git a/loader/src/loader/LoaderImpl.cpp b/loader/src/loader/LoaderImpl.cpp new file mode 100644 index 00000000..f6bd4212 --- /dev/null +++ b/loader/src/loader/LoaderImpl.cpp @@ -0,0 +1,590 @@ + +#include "LoaderImpl.hpp" + +#include <Geode/loader/Dirs.hpp> +#include <Geode/loader/IPC.hpp> +#include <Geode/loader/Loader.hpp> +#include <Geode/loader/Log.hpp> +#include <Geode/loader/Mod.hpp> +#include <Geode/utils/file.hpp> +#include <Geode/utils/map.hpp> +#include <Geode/utils/ranges.hpp> +#include <Geode/utils/web.hpp> +#include <InternalMod.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> + +USE_GEODE_NAMESPACE(); + +Loader::Impl* LoaderImpl::get() { + return Loader::get()->m_impl.get(); +} + +Loader::Impl::Impl() {} + +Loader::Impl::~Impl() {} + +void Loader::Impl::reset() { + this->closePlatformConsole(); + + for (auto& [_, mod] : m_mods) { + delete mod; + } + m_mods.clear(); + log::Logs::clear(); + ghc::filesystem::remove_all(dirs::getModRuntimeDir()); + ghc::filesystem::remove_all(dirs::getTempDir()); +} + +// Initialization + +void Loader::Impl::createDirectories() { +#ifdef GEODE_IS_MACOS + ghc::filesystem::create_directory(dirs::getSaveDir()); +#endif + + ghc::filesystem::create_directories(dirs::getGeodeResourcesDir()); + ghc::filesystem::create_directory(dirs::getModConfigDir()); + ghc::filesystem::create_directory(dirs::getModsDir()); + ghc::filesystem::create_directory(dirs::getGeodeLogDir()); + ghc::filesystem::create_directory(dirs::getTempDir()); + ghc::filesystem::create_directory(dirs::getModRuntimeDir()); + + if (!ranges::contains(m_modSearchDirectories, dirs::getModsDir())) { + m_modSearchDirectories.push_back(dirs::getModsDir()); + } +} + +Result<> Loader::Impl::setup() { + if (m_isSetup) { + return Ok(); + } + + log::debug("Set up internal mod representation"); + log::debug("Loading hooks... "); + + if (!this->loadHooks()) { + log::error("There were errors loading some hooks, see console for details"); + return Err("There were errors loading some hooks, see console for details"); + } + + log::debug("Loaded hooks"); + + log::debug("Setting up IPC..."); + + this->setupIPC(); + + log::Logs::setup(); + + if (crashlog::setupPlatformHandler()) { + log::debug("Set up platform crash logger"); + } + else { + log::debug("Unable to set up platform crash logger"); + } + + log::debug("Setting up Loader..."); + + this->createDirectories(); + auto sett = this->loadData(); + if (!sett) { + log::warn("Unable to load loader settings: {}", sett.unwrapErr()); + } + GEODE_UNWRAP(this->refreshModsList()); + + this->queueInGDThread([]() { + Loader::get()->addSearchPaths(); + }); + + m_isSetup = true; + + return Ok(); +} + +void Loader::Impl::addSearchPaths() { + CCFileUtils::get()->addPriorityPath(dirs::getGeodeResourcesDir().string().c_str()); + CCFileUtils::get()->addPriorityPath(dirs::getModRuntimeDir().string().c_str()); +} + +void Loader::Impl::updateResources() { + log::debug("Adding resources"); + + // add own spritesheets + this->updateModResources(InternalMod::get()); + + // add mods' spritesheets + for (auto const& [_, mod] : m_mods) { + this->updateModResources(mod); + } +} + +std::vector<Mod*> Loader::Impl::getAllMods() { + return map::values(m_mods); +} + +Mod* Loader::Impl::getInternalMod() { + return InternalMod::get(); +} + +std::vector<InvalidGeodeFile> Loader::Impl::getFailedMods() const { + return m_invalidMods; +} + +// Version info + +VersionInfo Loader::Impl::getVersion() { + return LOADER_VERSION; +} + +VersionInfo Loader::Impl::minModVersion() { + return VersionInfo{0, 3, 1}; +} + +VersionInfo Loader::Impl::maxModVersion() { + return VersionInfo{ + this->getVersion().getMajor(), + this->getVersion().getMinor(), + // todo: dynamic version info (vM.M.*) + 99999999, + }; +} + +bool Loader::Impl::isModVersionSupported(VersionInfo const& version) { + return version >= this->minModVersion() && version <= this->maxModVersion(); +} + +// Data saving + +Result<> Loader::Impl::saveData() { + // save mods' data + for (auto& [_, mod] : m_mods) { + auto r = mod->saveData(); + if (!r) { + log::warn("Unable to save data for mod \"{}\": {}", mod->getID(), r.unwrapErr()); + } + } + // save loader data + GEODE_UNWRAP(InternalMod::get()->saveData()); + + return Ok(); +} + +Result<> Loader::Impl::loadData() { + auto e = InternalMod::get()->loadData(); + if (!e) { + log::warn("Unable to load loader settings: {}", e.unwrapErr()); + } + for (auto& [_, mod] : m_mods) { + auto r = mod->loadData(); + if (!r) { + log::warn("Unable to load data for mod \"{}\": {}", mod->getID(), r.unwrapErr()); + } + } + return Ok(); +} + +// Mod loading + +Result<Mod*> Loader::Impl::loadModFromInfo(ModInfo const& info) { + if (m_mods.count(info.m_id)) { + return Err(fmt::format("Mod with ID '{}' already loaded", info.m_id)); + } + + // create Mod instance + auto mod = new Mod(info); + m_mods.insert({info.m_id, mod}); + mod->m_enabled = InternalMod::get()->getSavedValue<bool>("should-load-" + info.m_id, true); + // this loads the mod if its dependencies are resolved + mod->updateDependencyStates(); + + // 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{ + .m_path = file, + .m_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(); +} + +Mod* Loader::Impl::getInstalledMod(std::string const& id) const { + if (m_mods.count(id) && !m_mods.at(id)->isUninstalled()) { + return m_mods.at(id); + } + return nullptr; +} + +bool Loader::Impl::isModLoaded(std::string const& id) const { + return m_mods.count(id) && m_mods.at(id)->isLoaded(); +} + +Mod* Loader::Impl::getLoadedMod(std::string const& id) const { + if (m_mods.count(id)) { + auto mod = m_mods.at(id); + if (mod->isLoaded()) { + return mod; + } + } + return nullptr; +} + +void Loader::Impl::dispatchScheduledFunctions(Mod* mod) { + std::lock_guard _(m_scheduledFunctionsMutex); + for (auto& func : m_scheduledFunctions) { + func(); + } + m_scheduledFunctions.clear(); +} + +void Loader::Impl::scheduleOnModLoad(Mod* mod, ScheduledFunction func) { + std::lock_guard _(m_scheduledFunctionsMutex); + if (mod) { + return func(); + } + m_scheduledFunctions.push_back(func); +} + +void Loader::Impl::updateModResources(Mod* mod) { + if (!mod->m_info.m_spritesheets.size()) { + return; + } + + auto searchPath = dirs::getModRuntimeDir() / mod->getID() / "resources"; + + log::debug("Adding resources for {}", mod->getID()); + + // add spritesheets + for (auto const& sheet : mod->m_info.m_spritesheets) { + log::debug("Adding sheet {}", sheet); + auto png = sheet + ".png"; + auto plist = sheet + ".plist"; + auto ccfu = CCFileUtils::get(); + + if (png == std::string(ccfu->fullPathForFilename(png.c_str(), false)) || + plist == std::string(ccfu->fullPathForFilename(plist.c_str(), false))) { + log::warn( + "The resource dir of \"{}\" is missing \"{}\" png and/or plist files", + mod->m_info.m_id, + sheet + ); + } + else { + CCTextureCache::get()->addImage(png.c_str(), false); + CCSpriteFrameCache::get()->addSpriteFramesWithFile(plist.c_str()); + } + } +} + +// Dependencies and refreshing + +Result<> 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) { + GEODE_UNWRAP(this->loadModsFromDirectory(entry.path(), true)); + continue; + } + + // skip this entry if it's not a file + if (!ghc::filesystem::is_regular_file(entry)) { + continue; + } + + // 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_info.m_path == entry.path(); + })) { + continue; + } + + // if mods should be loaded immediately, do that + if (m_earlyLoadFinished) { + GEODE_UNWRAP(this->loadModFromFile(entry)); + } + // 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{ + .m_path = entry.path(), + .m_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); + } + } + return Ok(); +} + +Result<> Loader::Impl::refreshModsList() { + log::debug("Loading mods..."); + + // find mods + for (auto& dir : m_modSearchDirectories) { + GEODE_UNWRAP(this->loadModsFromDirectory(dir)); + } + + // load early-load mods first + for (auto& mod : m_modsToLoad) { + if (mod.m_needsEarlyLoad) { + GEODE_UNWRAP(this->loadModFromInfo(mod)); + } + } + + // UI can be loaded now + m_earlyLoadFinished = true; + + // load the rest of the mods + for (auto& mod : m_modsToLoad) { + if (!mod.m_needsEarlyLoad) { + GEODE_UNWRAP(this->loadModFromInfo(mod)); + } + } + m_modsToLoad.clear(); + + return Ok(); +} + +void Loader::Impl::updateAllDependencies() { + for (auto const& [_, mod] : m_mods) { + mod->updateDependencyStates(); + } +} + +void Loader::Impl::waitForModsToBeLoaded() { + while (!m_earlyLoadFinished) {} +} + +bool Loader::Impl::didLastLaunchCrash() const { + return crashlog::didLastLaunchCrash(); +} + +bool Loader::Impl::isReadyToHook() const { + return m_readyToHook; +} + +void Loader::Impl::addInternalHook(Hook* hook, Mod* mod) { + m_internalHooks.push_back({hook, mod}); +} + +bool Loader::Impl::loadHooks() { + m_readyToHook = true; + auto thereWereErrors = false; + for (auto const& hook : m_internalHooks) { + auto res = hook.second->addHook(hook.first); + if (!res) { + log::log(Severity::Error, hook.second, "{}", res.unwrapErr()); + thereWereErrors = true; + } + } + // free up memory + m_internalHooks.clear(); + return !thereWereErrors; +} + +void Loader::Impl::queueInGDThread(ScheduledFunction func) { + std::lock_guard<std::mutex> lock(m_gdThreadMutex); + m_gdThreadQueue.push_back(func); +} + +void Loader::Impl::executeGDThreadQueue() { + // copy queue to avoid locking mutex if someone is + // running addToGDThread inside their function + m_gdThreadMutex.lock(); + auto queue = m_gdThreadQueue; + m_gdThreadQueue.clear(); + m_gdThreadMutex.unlock(); + + // call queue + for (auto const& func : queue) { + func(); + } +} + +void Loader::Impl::logConsoleMessage(std::string const& msg) { + if (m_platformConsoleOpen) { + // TODO: make flushing optional + std::cout << msg << '\n' << std::flush; + } +} + +bool Loader::Impl::platformConsoleOpen() const { + return m_platformConsoleOpen; +} + +bool Loader::Impl::shownInfoAlert(std::string const& key) { + if (m_shownInfoAlerts.count(key)) { + return true; + } + m_shownInfoAlerts.insert(key); + return false; +} + +void Loader::Impl::saveInfoAlerts(nlohmann::json& json) { + json["alerts"] = m_shownInfoAlerts; +} + +void Loader::Impl::loadInfoAlerts(nlohmann::json& json) { + m_shownInfoAlerts = json["alerts"].get<std::unordered_set<std::string>>(); +} + +void Loader::Impl::downloadLoaderResources() { + auto version = this->getVersion().toString(); + auto tempResourcesZip = dirs::getTempDir() / "new.zip"; + auto resourcesDir = dirs::getGeodeResourcesDir() / InternalMod::get()->getID(); + + web::AsyncWebRequest() + .join("update-geode-loader-resources") + .fetch(fmt::format( + "https://github.com/geode-sdk/geode/releases/download/{}/resources.zip", version + )) + .into(tempResourcesZip) + .then([tempResourcesZip, resourcesDir](auto) { + // unzip resources zip + auto unzip = file::Unzip::intoDir(tempResourcesZip, resourcesDir, true); + if (!unzip) { + return ResourceDownloadEvent( + UpdateError("Unable to unzip new resources: " + unzip.unwrapErr()) + ) + .post(); + } + ResourceDownloadEvent(UpdateFinished()).post(); + }) + .expect([](std::string const& info) { + ResourceDownloadEvent(UpdateError("Unable to download resources: " + info)).post(); + }) + .progress([](auto&, double now, double total) { + ResourceDownloadEvent( + UpdateProgress(static_cast<uint8_t>(now / total * 100.0), "Downloading resources") + ) + .post(); + }); +} + +bool Loader::Impl::verifyLoaderResources() { + static std::optional<bool> CACHED = std::nullopt; + if (CACHED.has_value()) { + return CACHED.value(); + } + + // geode/resources/geode.loader + auto resourcesDir = dirs::getGeodeResourcesDir() / InternalMod::get()->getID(); + + // if the resources dir doesn't exist, then it's probably incorrect + if (!(ghc::filesystem::exists(resourcesDir) && ghc::filesystem::is_directory(resourcesDir))) { + this->downloadLoaderResources(); + return false; + } + + // make sure every file was covered + size_t coverage = 0; + + // verify hashes + for (auto& file : ghc::filesystem::directory_iterator(resourcesDir)) { + auto name = file.path().filename().string(); + // skip unknown files + if (!LOADER_RESOURCE_HASHES.count(name)) { + continue; + } + // verify hash + auto hash = calculateSHA256(file.path()); + if (hash != LOADER_RESOURCE_HASHES.at(name)) { + log::debug("compare {} {} {}", file.path().string(), hash, LOADER_RESOURCE_HASHES.at(name)); + this->downloadLoaderResources(); + return false; + } + coverage += 1; + } + + // make sure every file was found + if (coverage != LOADER_RESOURCE_HASHES.size()) { + this->downloadLoaderResources(); + return false; + } + + return true; +} + +std::string Loader::Impl::processRawIPC(void* rawHandle, std::string const& buffer) { + std::string reply; + + try { + std::optional<std::string> replyID = std::nullopt; + + // parse received message + auto json = nlohmann::json::parse(buffer); + if (!json.contains("mod") || !json["mod"].is_string()) { + log::warn("Received IPC message without 'mod' field"); + return reply; + } + if (!json.contains("message") || !json["message"].is_string()) { + log::warn("Received IPC message without 'message' field"); + return reply; + } + if (json.contains("reply") && json["reply"].is_string()) { + replyID = json["reply"]; + } + nlohmann::json data; + if (json.contains("data")) { + data = json["data"]; + } + // log::debug("Posting IPC event"); + // ! warning: if the event system is ever made asynchronous this will break! + IPCEvent(rawHandle, json["mod"], json["message"], data, &reply).post(); + } + catch (...) { + log::warn("Received IPC message that isn't valid JSON"); + } + + return reply; +} + +ResourceDownloadEvent::ResourceDownloadEvent(UpdateStatus const& status) : status(status) {} + +ListenerResult ResourceDownloadFilter::handle(std::function<Callback> fn, ResourceDownloadEvent* event) { + fn(event); + return ListenerResult::Propagate; +} + +ResourceDownloadFilter::ResourceDownloadFilter() {} diff --git a/loader/src/loader/LoaderImpl.hpp b/loader/src/loader/LoaderImpl.hpp new file mode 100644 index 00000000..6643f9ab --- /dev/null +++ b/loader/src/loader/LoaderImpl.hpp @@ -0,0 +1,142 @@ +#include "FileWatcher.hpp" + +#include <Geode/external/json/json.hpp> +#include <Geode/loader/Dirs.hpp> +#include <Geode/loader/Index.hpp> +#include <Geode/loader/Loader.hpp> +#include <Geode/loader/Log.hpp> +#include <Geode/loader/Mod.hpp> +#include <Geode/utils/Result.hpp> +#include <Geode/utils/map.hpp> +#include <Geode/utils/ranges.hpp> +#include <InternalMod.hpp> +#include <about.hpp> +#include <crashlog.hpp> +#include <mutex> +#include <optional> +#include <thread> +#include <unordered_map> +#include <unordered_set> +#include <vector> + +struct ResourceDownloadEvent : public Event { + const UpdateStatus status; + ResourceDownloadEvent(UpdateStatus const& status); +}; + +class GEODE_DLL ResourceDownloadFilter : public EventFilter<ResourceDownloadEvent> { +public: + using Callback = void(ResourceDownloadEvent*); + + ListenerResult handle(std::function<Callback> fn, ResourceDownloadEvent* event); + ResourceDownloadFilter(); +}; + +// TODO: Find a file convention for impl headers +namespace geode { + class LoaderImpl; +} + +class Loader::Impl { +public: + mutable std::mutex m_mutex; + + std::vector<ghc::filesystem::path> m_modSearchDirectories; + std::vector<ModInfo> m_modsToLoad; + std::vector<InvalidGeodeFile> m_invalidMods; + std::unordered_map<std::string, Mod*> m_mods; + std::vector<ghc::filesystem::path> m_texturePaths; + std::vector<ScheduledFunction> m_scheduledFunctions; + mutable std::mutex m_scheduledFunctionsMutex; + bool m_isSetup = false; + std::atomic_bool m_earlyLoadFinished = false; + + // InternalLoader + std::vector<std::function<void(void)>> m_gdThreadQueue; + mutable std::mutex m_gdThreadMutex; + bool m_platformConsoleOpen = false; + std::unordered_set<std::string> m_shownInfoAlerts; + + std::vector<std::pair<Hook*, Mod*>> m_internalHooks; + bool m_readyToHook = false; + + void saveInfoAlerts(nlohmann::json& json); + void loadInfoAlerts(nlohmann::json& json); + + void downloadLoaderResources(); + + bool loadHooks(); + void setupIPC(); + + Impl(); + ~Impl(); + + void createDirectories(); + + void updateModResources(Mod* mod); + void addSearchPaths(); + + void dispatchScheduledFunctions(Mod* mod); + friend void GEODE_CALL ::geode_implicit_load(Mod*); + + Result<Mod*> loadModFromInfo(ModInfo const& info); + + Result<> setup(); + void reset(); + + Result<> saveData(); + Result<> loadData(); + + VersionInfo getVersion(); + VersionInfo minModVersion(); + VersionInfo maxModVersion(); + bool isModVersionSupported(VersionInfo const& version); + + Result<Mod*> loadModFromFile(ghc::filesystem::path const& file); + Result<> loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive = true); + Result<> 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* getInternalMod(); + void updateAllDependencies(); + std::vector<InvalidGeodeFile> getFailedMods() const; + + void updateResources(); + + void queueInGDThread(ScheduledFunction func); + void scheduleOnModLoad(Mod* mod, ScheduledFunction func); + void waitForModsToBeLoaded(); + + bool didLastLaunchCrash() const; + + std::string processRawIPC(void* rawHandle, std::string const& buffer); + + /** + * Check if a one-time event has been shown to the user, + * and set it to true if not. Will return the previous + * state of the event before setting it to true + */ + bool shownInfoAlert(std::string const& key); + void executeGDThreadQueue(); + + void logConsoleMessage(std::string const& msg); + bool platformConsoleOpen() const; + void openPlatformConsole(); + void closePlatformConsole(); + void platformMessageBox(char const* title, std::string const& info); + + bool verifyLoaderResources(); + + bool isReadyToHook() const; + void addInternalHook(Hook* hook, Mod* mod); +}; + +namespace geode { + class LoaderImpl { + public: + static Loader::Impl* get(); + }; +} \ No newline at end of file diff --git a/loader/src/loader/Log.cpp b/loader/src/loader/Log.cpp index f3092882..6a597e8b 100644 --- a/loader/src/loader/Log.cpp +++ b/loader/src/loader/Log.cpp @@ -1,9 +1,10 @@ +#include "LoaderImpl.hpp" + #include <Geode/loader/Dirs.hpp> #include <Geode/loader/Log.hpp> #include <Geode/loader/Mod.hpp> #include <Geode/utils/casts.hpp> #include <Geode/utils/general.hpp> -#include <InternalLoader.hpp> #include <fmt/chrono.h> #include <fmt/format.h> #include <iomanip> @@ -46,8 +47,13 @@ std::string log::parse(CCNode* obj) { if (obj) { auto bb = obj->boundingBox(); return fmt::format( - "{{ {}, {}, ({}, {} | {} : {}) }}", typeid(*obj).name(), utils::intToHex(obj), - bb.origin.x, bb.origin.y, bb.size.width, bb.size.height + "{{ {}, {}, ({}, {} | {} : {}) }}", + typeid(*obj).name(), + utils::intToHex(obj), + bb.origin.x, + bb.origin.y, + bb.size.width, + bb.size.height ); } else { @@ -89,10 +95,7 @@ std::string log::parse(cocos2d::ccColor4B const& col) { return fmt::format("rgba({}, {}, {}, {})", col.r, col.g, col.b, col.a); } -Log::Log(Mod* mod, Severity sev) - : m_sender(mod), - m_time(log_clock::now()), - m_severity(sev) {} +Log::Log(Mod* mod, Severity sev) : m_sender(mod), m_time(log_clock::now()), m_severity(sev) {} bool Log::operator==(Log const& l) { return this == &l; @@ -121,7 +124,7 @@ void Logs::setup() { void Logs::push(Log&& log) { std::string logStr = log.toString(true); - InternalLoader::get()->logConsoleMessage(logStr); + LoaderImpl::get()->logConsoleMessage(logStr); s_logStream << logStr << std::endl; s_logs.emplace_back(std::forward<Log>(log)); diff --git a/loader/src/loader/Mod.cpp b/loader/src/loader/Mod.cpp index caa771bb..ac79f202 100644 --- a/loader/src/loader/Mod.cpp +++ b/loader/src/loader/Mod.cpp @@ -1,10 +1,11 @@ +#include "LoaderImpl.hpp" + +#include <Geode/loader/Dirs.hpp> #include <Geode/loader/Hook.hpp> #include <Geode/loader/Loader.hpp> -#include <Geode/loader/Dirs.hpp> #include <Geode/loader/Log.hpp> #include <Geode/loader/Mod.hpp> #include <Geode/utils/file.hpp> -#include <InternalLoader.hpp> #include <InternalMod.hpp> #include <optional> #include <string> @@ -408,7 +409,7 @@ Result<> Mod::disableHook(Hook* hook) { } Result<Hook*> Mod::addHook(Hook* hook) { - if (InternalLoader::get()->isReadyToHook()) { + if (LoaderImpl::get()->isReadyToHook()) { auto res = this->enableHook(hook); if (!res) { delete hook; @@ -416,7 +417,7 @@ Result<Hook*> Mod::addHook(Hook* hook) { } } else { - InternalLoader::get()->addInternalHook(hook, this); + LoaderImpl::get()->addInternalHook(hook, this); } return Ok(hook); @@ -477,7 +478,7 @@ Result<> Mod::createTempDir() { if (!file::createDirectoryAll(tempDir)) { return Err("Unable to create mods' runtime directory"); } - + // Create geode/temp/mod.id auto tempPath = tempDir / m_info.m_id; if (!file::createDirectoryAll(tempPath)) { @@ -487,9 +488,9 @@ Result<> Mod::createTempDir() { // Unzip .geode file into temp dir GEODE_UNWRAP_INTO(auto unzip, file::Unzip::create(m_info.m_path)); if (!unzip.hasEntry(m_info.m_binaryName)) { - return Err(fmt::format( - "Unable to find platform binary under the name \"{}\"", m_info.m_binaryName - )); + return Err( + fmt::format("Unable to find platform binary under the name \"{}\"", m_info.m_binaryName) + ); } GEODE_UNWRAP(unzip.extractAllTo(tempPath)); diff --git a/loader/src/loader/ModInfo.cpp b/loader/src/loader/ModInfo.cpp index c2c9f1e9..375291e1 100644 --- a/loader/src/loader/ModInfo.cpp +++ b/loader/src/loader/ModInfo.cpp @@ -49,7 +49,7 @@ Result<ModInfo> ModInfo::createFromSchemaV010(ModJson const& rawJson) { for (auto& dep : root.has("dependencies").iterate()) { auto obj = dep.obj(); - auto depobj = Dependency {}; + auto depobj = Dependency{}; obj.needs("id").validate(&ModInfo::validateID).into(depobj.m_id); obj.needs("version").validate(&VersionInfo::validate).intoAs<std::string>(depobj.m_version); obj.has("required").into(depobj.m_required); @@ -61,7 +61,7 @@ Result<ModInfo> ModInfo::createFromSchemaV010(ModJson const& rawJson) { for (auto& [key, value] : root.has("settings").items()) { GEODE_UNWRAP_INTO(auto sett, Setting::parse(key, value.json())); sett->m_modID = info.m_id; - info.m_settings.push_back({ key, sett }); + info.m_settings.push_back({key, sett}); } if (auto resources = root.has("resources").obj()) { @@ -120,19 +120,19 @@ Result<ModInfo> ModInfo::create(ModJson const& json) { "specified, or it is invalidally formatted (required: \"[v]X.X.X\")!" ); } - if (schema < Loader::minModVersion()) { + if (schema < Loader::get()->minModVersion()) { return Err( "[mod.json] is built for an older version (" + schema.toString() + - ") of Geode (current: " + Loader::getVersion().toString() + + ") of Geode (current: " + Loader::get()->getVersion().toString() + "). Please update the mod to the latest version, " "and if the problem persists, contact the developer " "to update it." ); } - if (schema > Loader::maxModVersion()) { + if (schema > Loader::get()->maxModVersion()) { return Err( "[mod.json] is built for a newer version (" + schema.toString() + - ") of Geode (current: " + Loader::getVersion().toString() + + ") of Geode (current: " + Loader::get()->getVersion().toString() + "). You need to update Geode in order to use " "this mod." ); @@ -182,8 +182,7 @@ Result<ModInfo> ModInfo::createFromGeodeZip(file::Unzip& unzip) { // Read mod.json & parse if possible GEODE_UNWRAP_INTO( - auto jsonData, - unzip.extract("mod.json").expect("Unable to read mod.json: {error}") + auto jsonData, unzip.extract("mod.json").expect("Unable to read mod.json: {error}") ); ModJson json; try { @@ -200,10 +199,7 @@ Result<ModInfo> ModInfo::createFromGeodeZip(file::Unzip& unzip) { auto info = res.unwrap(); info.m_path = unzip.getPath(); - GEODE_UNWRAP( - info.addSpecialFiles(unzip) - .expect("Unable to add extra files: {error}") - ); + GEODE_UNWRAP(info.addSpecialFiles(unzip).expect("Unable to add extra files: {error}")); return Ok(info); } @@ -212,9 +208,7 @@ Result<> ModInfo::addSpecialFiles(file::Unzip& unzip) { // unzip known MD files for (auto& [file, target] : getSpecialFiles()) { if (unzip.hasEntry(file)) { - GEODE_UNWRAP_INTO(auto data, unzip.extract(file).expect( - "Unable to extract \"{}\"", file - )); + GEODE_UNWRAP_INTO(auto data, unzip.extract(file).expect("Unable to extract \"{}\"", file)); *target = sanitizeDetailsData(std::string(data.begin(), data.end())); } } @@ -237,9 +231,9 @@ Result<> ModInfo::addSpecialFiles(ghc::filesystem::path const& dir) { std::vector<std::pair<std::string, std::optional<std::string>*>> ModInfo::getSpecialFiles() { return { - { "about.md", &m_details }, - { "changelog.md", &m_changelog }, - { "support.md", &m_supportInfo }, + {"about.md", &m_details}, + {"changelog.md", &m_changelog}, + {"support.md", &m_supportInfo}, }; } diff --git a/loader/src/main.cpp b/loader/src/main.cpp index 2a57c3a6..e36d4b83 100644 --- a/loader/src/main.cpp +++ b/loader/src/main.cpp @@ -1,4 +1,5 @@ #include "../core/Core.hpp" +#include "loader/LoaderImpl.hpp" #include <Geode/loader/IPC.hpp> #include <Geode/loader/Loader.hpp> @@ -6,7 +7,6 @@ #include <Geode/loader/Mod.hpp> #include <Geode/loader/Setting.hpp> #include <Geode/loader/SettingEvent.hpp> -#include <InternalLoader.hpp> #include <InternalMod.hpp> #include <array> @@ -108,7 +108,7 @@ static auto $_ = Loader::get()->openPlatformConsole(); } else { - Loader::get()->closePlatfromConsole(); + Loader::get()->closePlatformConsole(); } }); @@ -147,18 +147,18 @@ static auto $_ = listenForIPC("list-mods", [](IPCEvent* event) -> nlohmann::json int geodeEntry(void* platformData) { // setup internals - if (!InternalLoader::get()) { - InternalLoader::platformMessageBox( + if (!Loader::get()) { + LoaderImpl::get()->platformMessageBox( "Unable to Load Geode!", "There was an unknown fatal error setting up " "internal tools and Geode can not be loaded. " - "(InternalLoader::get returned nullptr)" + "(Loader::get returned nullptr)" ); return 1; } if (!geode::core::hook::initialize()) { - InternalLoader::platformMessageBox( + LoaderImpl::get()->platformMessageBox( "Unable to load Geode!", "There was an unknown fatal error setting up " "internal tools and Geode can not be loaded. " @@ -169,30 +169,14 @@ int geodeEntry(void* platformData) { geode_implicit_load(InternalMod::get()); - if (!InternalLoader::get()->setup()) { - // if we've made it here, Geode will - // be gettable (otherwise the call to - // setup would've immediately crashed) - - InternalLoader::platformMessageBox( - "Unable to Load Geode!", - "There was an unknown fatal error setting up " - "internal tools and Geode can not be loaded. " - "(InternalLoader::setup) returned false" - ); - return 1; - } - - log::debug("Loaded internal Geode class"); - // set up loader, load mods, etc. - if (!Loader::get()->setup()) { - InternalLoader::platformMessageBox( + if (!LoaderImpl::get()->setup()) { + LoaderImpl::get()->platformMessageBox( "Unable to Load Geode!", "There was an unknown fatal error setting up " "the loader and Geode can not be loaded." ); - delete InternalLoader::get(); + LoaderImpl::get()->reset(); return 1; } diff --git a/loader/src/platform/ios/InternalLoader.cpp b/loader/src/platform/ios/InternalLoader.cpp deleted file mode 100644 index a8f592b7..00000000 --- a/loader/src/platform/ios/InternalLoader.cpp +++ /dev/null @@ -1,40 +0,0 @@ -#include <InternalLoader.hpp> - -#ifdef GEODE_IS_IOS - -#include <Geode/loader/Loader.hpp> -#include <Geode/loader/Log.hpp> -#include <Geode/loader/Dirs.hpp> -#include <iostream> -#include <InternalMod.hpp> -#include <pwd.h> -#include <sys/types.h> -#include <unistd.h> - -void InternalLoader::platformMessageBox(char const* title, std::string const& info) { - std::cout << title << ": " << info << std::endl; -} - -void InternalLoader::openPlatformConsole() { - ghc::filesystem::path(getpwuid(getuid())->pw_dir); - freopen( - ghc::filesystem::path(dirs::getGeodeDir() / "geode_log.txt").string().c_str(), "w", - stdout - ); - InternalLoader::m_platformConsoleOpen = true; -} - -void InternalLoader::closePlatformConsole() {} - -void InternalLoader::postIPCReply( - void* rawPipeHandle, - std::string const& replyID, - nlohmann::json const& data -) {} - -void InternalLoader::setupIPC() { - #warning "Set up pipes or smth for this platform" - log::log(Severity::Warning, InternalMod::get(), "IPC is not supported on this platform"); -} - -#endif diff --git a/loader/src/platform/ios/LoaderImpl.cpp b/loader/src/platform/ios/LoaderImpl.cpp new file mode 100644 index 00000000..f28c7e88 --- /dev/null +++ b/loader/src/platform/ios/LoaderImpl.cpp @@ -0,0 +1,35 @@ +#include <loader/LoaderImpl.hpp> + +#ifdef GEODE_IS_IOS + + #include <Geode/loader/Dirs.hpp> + #include <Geode/loader/Loader.hpp> + #include <Geode/loader/Log.hpp> + #include <InternalMod.hpp> + #include <iostream> + #include <pwd.h> + #include <sys/types.h> + #include <unistd.h> + +void Loader::Impl::platformMessageBox(char const* title, std::string const& info) { + std::cout << title << ": " << info << std::endl; +} + +void Loader::Impl::openPlatformConsole() { + ghc::filesystem::path(getpwuid(getuid())->pw_dir); + freopen(ghc::filesystem::path(dirs::getGeodeDir() / "geode_log.txt").string().c_str(), "w", stdout); + m_platformConsoleOpen = true; +} + +void Loader::Impl::closePlatformConsole() {} + +void Loader::Impl::postIPCReply( + void* rawPipeHandle, std::string const& replyID, nlohmann::json const& data +) {} + +void Loader::Impl::setupIPC() { + #warning "Set up pipes or smth for this platform" + log::warning("IPC is not supported on this platform"); +} + +#endif diff --git a/loader/src/platform/mac/InternalLoader.cpp b/loader/src/platform/mac/LoaderImpl.cpp similarity index 82% rename from loader/src/platform/mac/InternalLoader.cpp rename to loader/src/platform/mac/LoaderImpl.cpp index 8c791e2a..8d98d12e 100644 --- a/loader/src/platform/mac/InternalLoader.cpp +++ b/loader/src/platform/mac/LoaderImpl.cpp @@ -1,14 +1,14 @@ #include <Geode/loader/IPC.hpp> #include <Geode/loader/Log.hpp> -#include <InternalLoader.hpp> #include <InternalMod.hpp> #include <iostream> +#include <loader/LoaderImpl.hpp> #ifdef GEODE_IS_MACOS #include <CoreFoundation/CoreFoundation.h> -void InternalLoader::platformMessageBox(char const* title, std::string const& info) { +void Loader::Impl::platformMessageBox(char const* title, std::string const& info) { CFStringRef cfTitle = CFStringCreateWithCString(NULL, title, kCFStringEncodingUTF8); CFStringRef cfMessage = CFStringCreateWithCString(NULL, info.c_str(), kCFStringEncodingUTF8); @@ -17,7 +17,7 @@ void InternalLoader::platformMessageBox(char const* title, std::string const& in ); } -void InternalLoader::openPlatformConsole() { +void Loader::Impl::openPlatformConsole() { m_platformConsoleOpen = true; for (auto const& log : log::Logs::list()) { @@ -25,7 +25,7 @@ void InternalLoader::openPlatformConsole() { } } -void InternalLoader::closePlatformConsole() { +void Loader::Impl::closePlatformConsole() { m_platformConsoleOpen = false; } @@ -34,11 +34,11 @@ CFDataRef msgPortCallback(CFMessagePortRef port, SInt32 messageID, CFDataRef dat std::string cdata(reinterpret_cast<char const*>(CFDataGetBytePtr(data)), CFDataGetLength(data)); - std::string reply = InternalLoader::processRawIPC(port, cdata); + std::string reply = LoaderImpl::get()->processRawIPC(port, cdata); return CFDataCreate(NULL, (UInt8 const*)reply.data(), reply.size()); } -void InternalLoader::setupIPC() { +void Loader::Impl::setupIPC() { std::thread([]() { CFStringRef portName = CFStringCreateWithCString(NULL, IPC_PORT_NAME, kCFStringEncodingUTF8); diff --git a/loader/src/platform/windows/InternalLoader.cpp b/loader/src/platform/windows/LoaderImpl.cpp similarity index 86% rename from loader/src/platform/windows/InternalLoader.cpp rename to loader/src/platform/windows/LoaderImpl.cpp index ad07754f..8e1b688d 100644 --- a/loader/src/platform/windows/InternalLoader.cpp +++ b/loader/src/platform/windows/LoaderImpl.cpp @@ -1,8 +1,8 @@ #include <Geode/loader/IPC.hpp> #include <Geode/loader/Log.hpp> -#include <InternalLoader.hpp> #include <InternalMod.hpp> #include <iostream> +#include <loader/LoaderImpl.hpp> USE_GEODE_NAMESPACE(); @@ -10,11 +10,11 @@ USE_GEODE_NAMESPACE(); static constexpr auto IPC_BUFFER_SIZE = 512; -void InternalLoader::platformMessageBox(char const* title, std::string const& info) { +void Loader::Impl::platformMessageBox(char const* title, std::string const& info) { MessageBoxA(nullptr, info.c_str(), title, MB_ICONERROR); } -void InternalLoader::openPlatformConsole() { +void Loader::Impl::openPlatformConsole() { if (m_platformConsoleOpen) return; if (AllocConsole() == 0) return; SetConsoleCP(CP_UTF8); @@ -29,7 +29,7 @@ void InternalLoader::openPlatformConsole() { } } -void InternalLoader::closePlatformConsole() { +void Loader::Impl::closePlatformConsole() { if (!m_platformConsoleOpen) return; fclose(stdin); @@ -49,7 +49,7 @@ void ipcPipeThread(HANDLE pipe) { if (ReadFile(pipe, buffer, sizeof(buffer) - 1, &read, nullptr)) { buffer[read] = '\0'; - std::string reply = InternalLoader::processRawIPC((void*)pipe, buffer); + std::string reply = LoaderImpl::get()->processRawIPC((void*)pipe, buffer); DWORD written; WriteFile(pipe, reply.c_str(), reply.size(), &written, nullptr); @@ -63,7 +63,7 @@ void ipcPipeThread(HANDLE pipe) { // log::debug("Disconnected pipe"); } -void InternalLoader::setupIPC() { +void Loader::Impl::setupIPC() { std::thread([]() { while (true) { auto pipe = CreateNamedPipeA( diff --git a/loader/src/ui/internal/GeodeUI.cpp b/loader/src/ui/internal/GeodeUI.cpp index 807a0e45..0dd11431 100644 --- a/loader/src/ui/internal/GeodeUI.cpp +++ b/loader/src/ui/internal/GeodeUI.cpp @@ -1,11 +1,12 @@ -#include <Geode/loader/Index.hpp> -#include <Geode/loader/Dirs.hpp> #include "info/ModInfoPopup.hpp" #include "list/ModListLayer.hpp" #include "settings/ModSettingsPopup.hpp" -#include <Geode/ui/MDPopup.hpp> + +#include <Geode/loader/Dirs.hpp> +#include <Geode/loader/Index.hpp> #include <Geode/ui/GeodeUI.hpp> +#include <Geode/ui/MDPopup.hpp> #include <Geode/utils/web.hpp> void geode::openModsList() { @@ -21,12 +22,11 @@ void geode::openIssueReportPopup(Mod* mod) { "If your issue relates to a <cr>game crash</c>, <cb>please include</c> the " "latest crash log(s) from `" + dirs::getCrashlogsDir().string() + "`", - "OK", (mod->getModInfo().m_issues.value().m_url ? "Open URL" : ""), + "OK", + (mod->getModInfo().m_issues.value().m_url ? "Open URL" : ""), [mod](bool btn2) { if (btn2) { - web::openLinkInBrowser( - mod->getModInfo().m_issues.value().m_url.value() - ); + web::openLinkInBrowser(mod->getModInfo().m_issues.value().m_url.value()); } } )->show(); @@ -38,9 +38,11 @@ void geode::openIssueReportPopup(Mod* mod) { "[#support](https://discord.com/channels/911701438269386882/979352389985390603) " "channnel in the [Geode Discord Server](https://discord.gg/9e43WMKzhp)\n\n" "If your issue relates to a <cr>game crash</c>, <cb>please include</c> the " - "latest crash log(s) from `" + dirs::getCrashlogsDir().string() + "`", + "latest crash log(s) from `" + + dirs::getCrashlogsDir().string() + "`", "OK" - )->show(); + ) + ->show(); } } @@ -49,9 +51,7 @@ void geode::openInfoPopup(Mod* mod) { } void geode::openIndexPopup(Mod* mod) { - if (auto item = Index::get()->getItem( - mod->getID(), mod->getVersion().getMajor() - )) { + if (auto item = Index::get()->getItem(mod->getID(), mod->getVersion().getMajor())) { IndexItemInfoPopup::create(item, nullptr)->show(); } } @@ -73,13 +73,11 @@ CCNode* geode::createDefaultLogo(CCSize const& size) { CCNode* geode::createModLogo(Mod* mod, CCSize const& size) { CCNode* spr = nullptr; - if (mod == Loader::getInternalMod()) { + if (mod == Loader::get()->getInternalMod()) { spr = CCSprite::createWithSpriteFrameName("geode-logo.png"_spr); } else { - spr = CCSprite::create( - fmt::format("{}/logo.png", mod->getID()).c_str() - ); + spr = CCSprite::create(fmt::format("{}/logo.png", mod->getID()).c_str()); } if (!spr) spr = CCSprite::createWithSpriteFrameName("no-logo.png"_spr); if (!spr) spr = CCLabelBMFont::create("N/A", "goldFont.fnt"); @@ -103,11 +101,9 @@ CCNode* geode::createIndexItemLogo(IndexItemHandle item, CCSize const& size) { auto logoGlow = CCSprite::createWithSpriteFrameName("logo-glow.png"_spr); logoGlow->setScaleX(glowSize.width / logoGlow->getContentSize().width); logoGlow->setScaleY(glowSize.height / logoGlow->getContentSize().height); - + // i dont know why + 1 is needed and its too late for me to figure out why - spr->setPosition( - logoGlow->getContentSize().width / 2, logoGlow->getContentSize().height / 2 - ); + spr->setPosition(logoGlow->getContentSize().width / 2, logoGlow->getContentSize().height / 2); // scary mathematics spr->setScaleX(size.width / spr->getContentSize().width / logoGlow->getScaleX()); spr->setScaleY(size.height / spr->getContentSize().height / logoGlow->getScaleY()); diff --git a/loader/src/ui/internal/info/ModInfoPopup.cpp b/loader/src/ui/internal/info/ModInfoPopup.cpp index 50ce37d7..986dc478 100644 --- a/loader/src/ui/internal/info/ModInfoPopup.cpp +++ b/loader/src/ui/internal/info/ModInfoPopup.cpp @@ -2,10 +2,8 @@ #include "../dev/HookListLayer.hpp" #include "../list/ModListView.hpp" -#include "../settings/ModSettingsPopup.hpp" #include "../settings/AdvancedSettingsPopup.hpp" -#include <InternalLoader.hpp> -#include <Geode/loader/Dirs.hpp> +#include "../settings/ModSettingsPopup.hpp" #include <Geode/binding/ButtonSprite.hpp> #include <Geode/binding/CCTextInputNode.hpp> @@ -13,14 +11,15 @@ #include <Geode/binding/Slider.hpp> #include <Geode/binding/SliderThumb.hpp> #include <Geode/binding/SliderTouchLogic.hpp> +#include <Geode/loader/Dirs.hpp> #include <Geode/loader/Mod.hpp> #include <Geode/ui/BasedButton.hpp> -#include <Geode/ui/IconButtonSprite.hpp> #include <Geode/ui/GeodeUI.hpp> +#include <Geode/ui/IconButtonSprite.hpp> #include <Geode/utils/casts.hpp> #include <Geode/utils/ranges.hpp> #include <Geode/utils/web.hpp> -#include <InternalLoader.hpp> +#include <loader/LoaderImpl.hpp> // TODO: die #undef min @@ -28,7 +27,7 @@ static constexpr int const TAG_CONFIRM_UNINSTALL = 5; static constexpr int const TAG_DELETE_SAVEDATA = 6; -static const CCSize LAYER_SIZE = { 440.f, 290.f }; +static const CCSize LAYER_SIZE = {440.f, 290.f}; bool ModInfoPopup::init(ModInfo const& info, ModListView* list) { m_noElasticity = true; @@ -36,11 +35,11 @@ bool ModInfoPopup::init(ModInfo const& info, ModListView* list) { auto winSize = CCDirector::sharedDirector()->getWinSize(); - if (!this->initWithColor({ 0, 0, 0, 105 })) return false; + if (!this->initWithColor({0, 0, 0, 105})) return false; m_mainLayer = CCLayer::create(); this->addChild(m_mainLayer); - auto bg = CCScale9Sprite::create("GJ_square01.png", { 0.0f, 0.0f, 80.0f, 80.0f }); + auto bg = CCScale9Sprite::create("GJ_square01.png", {0.0f, 0.0f, 80.0f, 80.0f}); bg->setContentSize(LAYER_SIZE); bg->setPosition(winSize.width / 2, winSize.height / 2); bg->setZOrder(-10); @@ -53,58 +52,48 @@ bool ModInfoPopup::init(ModInfo const& info, ModListView* list) { constexpr float logoOffset = 10.f; auto nameLabel = CCLabelBMFont::create(info.m_name.c_str(), "bigFont.fnt"); - nameLabel->setAnchorPoint({ .0f, .5f }); + nameLabel->setAnchorPoint({.0f, .5f}); nameLabel->limitLabelWidth(200.f, .7f, .1f); m_mainLayer->addChild(nameLabel, 2); - auto logoSpr = this->createLogo({ logoSize, logoSize }); + auto logoSpr = this->createLogo({logoSize, logoSize}); m_mainLayer->addChild(logoSpr); auto developerStr = "by " + info.m_developer; auto developerLabel = CCLabelBMFont::create(developerStr.c_str(), "goldFont.fnt"); developerLabel->setScale(.5f); - developerLabel->setAnchorPoint({ .0f, .5f }); + developerLabel->setAnchorPoint({.0f, .5f}); m_mainLayer->addChild(developerLabel); auto logoTitleWidth = - std::max( - nameLabel->getScaledContentSize().width, - developerLabel->getScaledContentSize().width - ) + + std::max(nameLabel->getScaledContentSize().width, developerLabel->getScaledContentSize().width) + logoSize + logoOffset; nameLabel->setPosition( - winSize.width / 2 - logoTitleWidth / 2 + logoSize + logoOffset, - winSize.height / 2 + 125.f + winSize.width / 2 - logoTitleWidth / 2 + logoSize + logoOffset, winSize.height / 2 + 125.f + ); + logoSpr->setPosition( + {winSize.width / 2 - logoTitleWidth / 2 + logoSize / 2, winSize.height / 2 + 115.f} ); - logoSpr->setPosition({ - winSize.width / 2 - logoTitleWidth / 2 + logoSize / 2, - winSize.height / 2 + 115.f - }); developerLabel->setPosition( - winSize.width / 2 - logoTitleWidth / 2 + logoSize + logoOffset, - winSize.height / 2 + 105.f + winSize.width / 2 - logoTitleWidth / 2 + logoSize + logoOffset, winSize.height / 2 + 105.f ); - auto versionLabel = CCLabelBMFont::create( - info.m_version.toString().c_str(), - "bigFont.fnt" - ); - versionLabel->setAnchorPoint({ .0f, .5f }); + auto versionLabel = CCLabelBMFont::create(info.m_version.toString().c_str(), "bigFont.fnt"); + versionLabel->setAnchorPoint({.0f, .5f}); versionLabel->setScale(.4f); versionLabel->setPosition( nameLabel->getPositionX() + nameLabel->getScaledContentSize().width + 5.f, winSize.height / 2 + 125.f ); - versionLabel->setColor({ 0, 255, 0 }); + versionLabel->setColor({0, 255, 0}); m_mainLayer->addChild(versionLabel); CCDirector::sharedDirector()->getTouchDispatcher()->incrementForcePrio(2); this->registerWithTouchDispatcher(); m_detailsArea = MDTextArea::create( - (info.m_details ? info.m_details.value() : "### No description provided."), - { 350.f, 137.5f } + (info.m_details ? info.m_details.value() : "### No description provided."), {350.f, 137.5f} ); m_detailsArea->setPosition( winSize.width / 2 - m_detailsArea->getScaledContentSize().width / 2, @@ -121,29 +110,35 @@ bool ModInfoPopup::init(ModInfo const& info, ModListView* list) { // changelog if (info.m_changelog) { - m_changelogArea = MDTextArea::create(info.m_changelog.value(), { 350.f, 137.5f }); + m_changelogArea = MDTextArea::create(info.m_changelog.value(), {350.f, 137.5f}); m_changelogArea->setPosition( - -5000.f, winSize.height / 2 - - m_changelogArea->getScaledContentSize().height / 2 - 20.f + -5000.f, winSize.height / 2 - m_changelogArea->getScaledContentSize().height / 2 - 20.f ); m_changelogArea->setVisible(false); m_mainLayer->addChild(m_changelogArea); auto changelogBtnOffSpr = ButtonSprite::create( CCSprite::createWithSpriteFrameName("changelog.png"_spr), - 0x20, true, 32.f, "GJ_button_01.png", 1.f + 0x20, + true, + 32.f, + "GJ_button_01.png", + 1.f ); changelogBtnOffSpr->setScale(.65f); auto changelogBtnOnSpr = ButtonSprite::create( CCSprite::createWithSpriteFrameName("changelog.png"_spr), - 0x20, true, 32.f, "GJ_button_02.png", 1.f + 0x20, + true, + 32.f, + "GJ_button_02.png", + 1.f ); changelogBtnOnSpr->setScale(.65f); auto changelogBtn = CCMenuItemToggler::create( - changelogBtnOffSpr, changelogBtnOnSpr, - this, menu_selector(ModInfoPopup::onChangelog) + changelogBtnOffSpr, changelogBtnOnSpr, this, menu_selector(ModInfoPopup::onChangelog) ); changelogBtn->setPosition(-LAYER_SIZE.width / 2 + 21.5f, .0f); m_buttonMenu->addChild(changelogBtn); @@ -153,51 +148,38 @@ bool ModInfoPopup::init(ModInfo const& info, ModListView* list) { auto infoSpr = CCSprite::createWithSpriteFrameName("GJ_infoIcon_001.png"); infoSpr->setScale(.85f); - m_infoBtn = CCMenuItemSpriteExtra::create( - infoSpr, this, menu_selector(ModInfoPopup::onInfo) - ); - m_infoBtn->setPosition( - LAYER_SIZE.width / 2 - 25.f, - LAYER_SIZE.height / 2 - 25.f - ); + m_infoBtn = CCMenuItemSpriteExtra::create(infoSpr, this, menu_selector(ModInfoPopup::onInfo)); + m_infoBtn->setPosition(LAYER_SIZE.width / 2 - 25.f, LAYER_SIZE.height / 2 - 25.f); m_buttonMenu->addChild(m_infoBtn); // repo button if (info.m_repository) { auto repoBtn = CCMenuItemSpriteExtra::create( - CCSprite::createWithSpriteFrameName("github.png"_spr), this, + CCSprite::createWithSpriteFrameName("github.png"_spr), + this, menu_selector(ModInfoPopup::onRepository) ); - repoBtn->setPosition( - LAYER_SIZE.width / 2 - 25.f, - -LAYER_SIZE.height / 2 + 25.f - ); + repoBtn->setPosition(LAYER_SIZE.width / 2 - 25.f, -LAYER_SIZE.height / 2 + 25.f); m_buttonMenu->addChild(repoBtn); } // support button if (info.m_supportInfo) { auto supportBtn = CCMenuItemSpriteExtra::create( - CCSprite::createWithSpriteFrameName("gift.png"_spr), this, + CCSprite::createWithSpriteFrameName("gift.png"_spr), + this, menu_selector(ModInfoPopup::onSupport) ); - supportBtn->setPosition( - LAYER_SIZE.width / 2 - 60.f, - -LAYER_SIZE.height / 2 + 25.f - ); + supportBtn->setPosition(LAYER_SIZE.width / 2 - 60.f, -LAYER_SIZE.height / 2 + 25.f); m_buttonMenu->addChild(supportBtn); } auto closeSpr = CCSprite::createWithSpriteFrameName("GJ_closeBtn_001.png"); closeSpr->setScale(.8f); - auto closeBtn = CCMenuItemSpriteExtra::create( - closeSpr, this, menu_selector(ModInfoPopup::onClose) - ); - closeBtn->setPosition( - -LAYER_SIZE.width / 2 + 3.f, - LAYER_SIZE.height / 2 - 3.f - ); + auto closeBtn = + CCMenuItemSpriteExtra::create(closeSpr, this, menu_selector(ModInfoPopup::onClose)); + closeBtn->setPosition(-LAYER_SIZE.width / 2 + 3.f, LAYER_SIZE.height / 2 - 3.f); m_buttonMenu->addChild(closeBtn); this->setKeypadEnabled(true); @@ -208,10 +190,9 @@ bool ModInfoPopup::init(ModInfo const& info, ModListView* list) { void ModInfoPopup::onSupport(CCObject*) { MDPopup::create( - "Support " + this->getModInfo().m_name, - this->getModInfo().m_supportInfo.value(), - "OK" - )->show(); + "Support " + this->getModInfo().m_name, this->getModInfo().m_supportInfo.value(), "OK" + ) + ->show(); } void ModInfoPopup::onRepository(CCObject*) { @@ -233,8 +214,11 @@ void ModInfoPopup::onInfo(CCObject*) { info.m_developer, info.m_path.string() ), - "OK", nullptr, 400.f - )->show(); + "OK", + nullptr, + 400.f + ) + ->show(); } void ModInfoPopup::onChangelog(CCObject* sender) { @@ -245,18 +229,16 @@ void ModInfoPopup::onChangelog(CCObject* sender) { // as it turns out, cocos2d is stupid and still passes touch // events to invisible nodes m_detailsArea->setPositionX( - toggle->isToggled() ? - winSize.width / 2 - m_detailsArea->getScaledContentSize().width / 2 : - -5000.f + toggle->isToggled() ? winSize.width / 2 - m_detailsArea->getScaledContentSize().width / 2 : + -5000.f ); m_changelogArea->setVisible(!toggle->isToggled()); // as it turns out, cocos2d is stupid and still passes touch // events to invisible nodes m_changelogArea->setPositionX( - !toggle->isToggled() ? - winSize.width / 2 - m_changelogArea->getScaledContentSize().width / 2 : - -5000.f + !toggle->isToggled() ? winSize.width / 2 - m_changelogArea->getScaledContentSize().width / 2 : + -5000.f ); } @@ -277,24 +259,17 @@ void ModInfoPopup::onClose(CCObject* pSender) { bool LocalModInfoPopup::init(Mod* mod, ModListView* list) { m_mod = mod; - if (!ModInfoPopup::init(mod->getModInfo(), list)) - return false; + if (!ModInfoPopup::init(mod->getModInfo(), list)) return false; auto winSize = CCDirector::sharedDirector()->getWinSize(); // mod settings - auto settingsSpr = CCSprite::createWithSpriteFrameName( - "GJ_optionsBtn_001.png" - ); + auto settingsSpr = CCSprite::createWithSpriteFrameName("GJ_optionsBtn_001.png"); settingsSpr->setScale(.65f); - auto settingsBtn = CCMenuItemSpriteExtra::create( - settingsSpr, this, menu_selector(LocalModInfoPopup::onSettings) - ); - settingsBtn->setPosition( - -LAYER_SIZE.width / 2 + 25.f, - -LAYER_SIZE.height / 2 + 25.f - ); + auto settingsBtn = + CCMenuItemSpriteExtra::create(settingsSpr, this, menu_selector(LocalModInfoPopup::onSettings)); + settingsBtn->setPosition(-LAYER_SIZE.width / 2 + 25.f, -LAYER_SIZE.height / 2 + 25.f); m_buttonMenu->addChild(settingsBtn); // Check if a config directory for the mod exists @@ -307,31 +282,23 @@ bool LocalModInfoPopup::init(Mod* mod, ModListView* list) { auto configBtn = CCMenuItemSpriteExtra::create( configSpr, this, menu_selector(LocalModInfoPopup::onOpenConfigDir) ); - configBtn->setPosition( - -LAYER_SIZE.width / 2 + 65.f, - -LAYER_SIZE.height / 2 + 25.f - ); + configBtn->setPosition(-LAYER_SIZE.width / 2 + 65.f, -LAYER_SIZE.height / 2 + 25.f); m_buttonMenu->addChild(configBtn); } if (!mod->hasSettings()) { - settingsSpr->setColor({ 150, 150, 150 }); + settingsSpr->setColor({150, 150, 150}); settingsBtn->setTarget(this, menu_selector(LocalModInfoPopup::onNoSettings)); } - auto enableBtnSpr = ButtonSprite::create( - "Enable", "bigFont.fnt", "GJ_button_01.png", .6f - ); + auto enableBtnSpr = ButtonSprite::create("Enable", "bigFont.fnt", "GJ_button_01.png", .6f); enableBtnSpr->setScale(.6f); - auto disableBtnSpr = ButtonSprite::create( - "Disable", "bigFont.fnt", "GJ_button_06.png", .6f - ); + auto disableBtnSpr = ButtonSprite::create("Disable", "bigFont.fnt", "GJ_button_06.png", .6f); disableBtnSpr->setScale(.6f); auto enableBtn = CCMenuItemToggler::create( - disableBtnSpr, enableBtnSpr, - this, menu_selector(LocalModInfoPopup::onEnableMod) + disableBtnSpr, enableBtnSpr, this, menu_selector(LocalModInfoPopup::onEnableMod) ); enableBtn->setPosition(-155.f, 75.f); enableBtn->toggle(!mod->isEnabled()); @@ -339,8 +306,8 @@ bool LocalModInfoPopup::init(Mod* mod, ModListView* list) { if (!mod->supportsDisabling()) { enableBtn->setTarget(this, menu_selector(LocalModInfoPopup::onDisablingNotSupported)); - enableBtnSpr->setColor({ 150, 150, 150 }); - disableBtnSpr->setColor({ 150, 150, 150 }); + enableBtnSpr->setColor({150, 150, 150}); + disableBtnSpr->setColor({150, 150, 150}); } if (mod != Loader::get()->getInternalMod()) { @@ -351,15 +318,11 @@ bool LocalModInfoPopup::init(Mod* mod, ModListView* list) { auto advSettBtn = CCMenuItemSpriteExtra::create( advSettSpr, this, menu_selector(LocalModInfoPopup::onAdvancedSettings) ); - advSettBtn->setPosition( - m_infoBtn->getPositionX() - 30.f, - m_infoBtn->getPositionY() - ); + advSettBtn->setPosition(m_infoBtn->getPositionX() - 30.f, m_infoBtn->getPositionY()); m_buttonMenu->addChild(advSettBtn); - auto uninstallBtnSpr = ButtonSprite::create( - "Uninstall", "bigFont.fnt", "GJ_button_05.png", .6f - ); + auto uninstallBtnSpr = + ButtonSprite::create("Uninstall", "bigFont.fnt", "GJ_button_05.png", .6f); uninstallBtnSpr->setScale(.6f); auto uninstallBtn = CCMenuItemSpriteExtra::create( @@ -375,43 +338,35 @@ bool LocalModInfoPopup::init(Mod* mod, ModListView* list) { m_installBtnSpr = IconButtonSprite::create( "GE_button_01.png"_spr, CCSprite::createWithSpriteFrameName("install.png"_spr), - "Update", "bigFont.fnt" + "Update", + "bigFont.fnt" ); m_installBtnSpr->setScale(.6f); - m_installBtn = CCMenuItemSpriteExtra::create( - m_installBtnSpr, this, nullptr - ); + m_installBtn = CCMenuItemSpriteExtra::create(m_installBtnSpr, this, nullptr); m_installBtn->setPosition(-8.0f, 75.f); m_buttonMenu->addChild(m_installBtn); m_installStatus = DownloadStatusNode::create(); - m_installStatus->setPosition( - winSize.width / 2 + 105.f, - winSize.height / 2 + 75.f - ); + m_installStatus->setPosition(winSize.width / 2 + 105.f, winSize.height / 2 + 75.f); m_installStatus->setVisible(false); m_mainLayer->addChild(m_installStatus); m_updateVersionLabel = CCLabelBMFont::create( - ("Available: " + indexItem->info.m_version.toString()).c_str(), - "bigFont.fnt" + ("Available: " + indexItem->info.m_version.toString()).c_str(), "bigFont.fnt" ); m_updateVersionLabel->setScale(.35f); - m_updateVersionLabel->setAnchorPoint({ .0f, .5f }); - m_updateVersionLabel->setColor({ 94, 219, 255 }); - m_updateVersionLabel->setPosition( - winSize.width / 2 + 35.f, winSize.height / 2 + 75.f - ); + m_updateVersionLabel->setAnchorPoint({.0f, .5f}); + m_updateVersionLabel->setColor({94, 219, 255}); + m_updateVersionLabel->setPosition(winSize.width / 2 + 35.f, winSize.height / 2 + 75.f); m_mainLayer->addChild(m_updateVersionLabel); } } - + // issue report button if (mod->getModInfo().m_issues) { - auto issuesBtnSpr = ButtonSprite::create( - "Report an Issue", "goldFont.fnt", "GJ_button_04.png", .8f - ); + auto issuesBtnSpr = + ButtonSprite::create("Report an Issue", "goldFont.fnt", "GJ_button_04.png", .8f); issuesBtnSpr->setScale(.75f); auto issuesBtn = CCMenuItemSpriteExtra::create( @@ -438,19 +393,18 @@ void LocalModInfoPopup::onIssues(CCObject*) { void LocalModInfoPopup::onUninstall(CCObject*) { auto layer = FLAlertLayer::create( - this, "Confirm Uninstall", - fmt::format( - "Are you sure you want to uninstall <cr>{}</c>?", - m_mod->getName() - ), - "Cancel", "OK" + this, + "Confirm Uninstall", + fmt::format("Are you sure you want to uninstall <cr>{}</c>?", m_mod->getName()), + "Cancel", + "OK" ); layer->setTag(TAG_CONFIRM_UNINSTALL); layer->show(); } void LocalModInfoPopup::onEnableMod(CCObject* sender) { - if (!InternalLoader::get()->shownInfoAlert("mod-disable-vs-unload")) { + if (!LoaderImpl::get()->shownInfoAlert("mod-disable-vs-unload")) { FLAlertLayer::create( "Notice", "You may still see some effects of the mod left, and you may " @@ -464,19 +418,13 @@ void LocalModInfoPopup::onEnableMod(CCObject* sender) { if (as<CCMenuItemToggler*>(sender)->isToggled()) { auto res = m_mod->loadBinary(); if (!res) { - FLAlertLayer::create( - nullptr, "Error Loading Mod", - res.unwrapErr(), "OK", nullptr - )->show(); + FLAlertLayer::create(nullptr, "Error Loading Mod", res.unwrapErr(), "OK", nullptr)->show(); } } else { auto res = m_mod->disable(); if (!res) { - FLAlertLayer::create( - nullptr, "Error Disabling Mod", - res.unwrapErr(), "OK", nullptr - )->show(); + FLAlertLayer::create(nullptr, "Error Disabling Mod", res.unwrapErr(), "OK", nullptr)->show(); } } if (m_list) m_list->updateAllStates(nullptr); @@ -488,11 +436,7 @@ void LocalModInfoPopup::onOpenConfigDir(CCObject*) { } void LocalModInfoPopup::onDisablingNotSupported(CCObject* pSender) { - FLAlertLayer::create( - "Unsupported", - "<cr>Disabling</c> is not supported for this mod.", - "OK" - )->show(); + FLAlertLayer::create("Unsupported", "<cr>Disabling</c> is not supported for this mod.", "OK")->show(); as<CCMenuItemToggler*>(pSender)->toggle(m_mod->isEnabled()); } @@ -501,8 +445,7 @@ void LocalModInfoPopup::onSettings(CCObject*) { } void LocalModInfoPopup::onNoSettings(CCObject*) { - FLAlertLayer::create("No Settings Found", "This mod has no customizable settings.", "OK") - ->show(); + FLAlertLayer::create("No Settings Found", "This mod has no customizable settings.", "OK")->show(); } void LocalModInfoPopup::onAdvancedSettings(CCObject*) { @@ -520,14 +463,10 @@ void LocalModInfoPopup::FLAlert_Clicked(FLAlertLayer* layer, bool btn2) { case TAG_DELETE_SAVEDATA: { if (btn2) { if (ghc::filesystem::remove_all(m_mod->getSaveDir())) { - FLAlertLayer::create( - "Deleted", "The mod's save data was deleted.", "OK" - )->show(); + FLAlertLayer::create("Deleted", "The mod's save data was deleted.", "OK")->show(); } else { - FLAlertLayer::create( - "Error", "Unable to delete mod's save directory!", "OK" - )->show(); + FLAlertLayer::create("Error", "Unable to delete mod's save directory!", "OK")->show(); } } if (m_list) m_list->refreshList(); @@ -539,18 +478,19 @@ void LocalModInfoPopup::FLAlert_Clicked(FLAlertLayer* layer, bool btn2) { void LocalModInfoPopup::uninstall() { auto res = m_mod->uninstall(); if (!res) { - return FLAlertLayer::create( - "Uninstall failed :(", res.unwrapErr(), "OK" - )->show(); + return FLAlertLayer::create("Uninstall failed :(", res.unwrapErr(), "OK")->show(); } auto layer = FLAlertLayer::create( - this, "Uninstall complete", + this, + "Uninstall complete", "Mod was succesfully uninstalled! :) " "(You may need to <cy>restart the game</c> " "for the mod to take full effect). " "<co>Would you also like to delete the mod's " "save data?</c>", - "Cancel", "Delete", 350.f + "Cancel", + "Delete", + 350.f ); layer->setTag(TAG_DELETE_SAVEDATA); layer->show(); @@ -573,29 +513,25 @@ bool IndexItemInfoPopup::init(IndexItemHandle item, ModListView* list) { auto winSize = CCDirector::sharedDirector()->getWinSize(); - if (!ModInfoPopup::init(item->info, list)) - return false; + if (!ModInfoPopup::init(item->info, list)) return false; m_installBtnSpr = IconButtonSprite::create( - "GE_button_01.png"_spr, CCSprite::createWithSpriteFrameName("install.png"_spr), - "Install", "bigFont.fnt" + "GE_button_01.png"_spr, + CCSprite::createWithSpriteFrameName("install.png"_spr), + "Install", + "bigFont.fnt" ); m_installBtnSpr->setScale(.6f); - m_installBtn = CCMenuItemSpriteExtra::create( - m_installBtnSpr, this, nullptr - ); + m_installBtn = CCMenuItemSpriteExtra::create(m_installBtnSpr, this, nullptr); m_installBtn->setPosition(-143.0f, 75.f); m_buttonMenu->addChild(m_installBtn); m_installStatus = DownloadStatusNode::create(); - m_installStatus->setPosition( - winSize.width / 2 - 25.f, - winSize.height / 2 + 75.f - ); + m_installStatus->setPosition(winSize.width / 2 - 25.f, winSize.height / 2 + 75.f); m_installStatus->setVisible(false); m_mainLayer->addChild(m_installStatus); - + return true; } @@ -607,9 +543,7 @@ ModInfo IndexItemInfoPopup::getModInfo() const { return m_item->info; } -IndexItemInfoPopup* IndexItemInfoPopup::create( - IndexItemHandle item, ModListView* list -) { +IndexItemInfoPopup* IndexItemInfoPopup::create(IndexItemHandle item, ModListView* list) { auto ret = new IndexItemInfoPopup; if (ret && ret->init(item, list)) { ret->autorelease(); diff --git a/loader/src/ui/internal/list/ModListCell.cpp b/loader/src/ui/internal/list/ModListCell.cpp index d5e58030..81e5166b 100644 --- a/loader/src/ui/internal/list/ModListCell.cpp +++ b/loader/src/ui/internal/list/ModListCell.cpp @@ -1,13 +1,15 @@ #include "ModListCell.hpp" -#include "ModListView.hpp" + #include "../info/ModInfoPopup.hpp" -#include <Geode/binding/StatsCell.hpp> -#include <Geode/binding/FLAlertLayer.hpp> +#include "ModListView.hpp" + #include <Geode/binding/ButtonSprite.hpp> #include <Geode/binding/CCMenuItemSpriteExtra.hpp> #include <Geode/binding/CCMenuItemToggler.hpp> +#include <Geode/binding/FLAlertLayer.hpp> +#include <Geode/binding/StatsCell.hpp> #include <Geode/ui/GeodeUI.hpp> -#include <InternalLoader.hpp> +#include <loader/LoaderImpl.hpp> template <class T> static bool tryOrAlert(Result<T> const& res, char const* title) { @@ -17,8 +19,8 @@ static bool tryOrAlert(Result<T> const& res, char const* title) { return res.isOk(); } -ModListCell::ModListCell(char const* name, CCSize const& size) - : TableViewCell(name, size.width, size.height) {} +ModListCell::ModListCell(char const* name, CCSize const& size) : + TableViewCell(name, size.width, size.height) {} void ModListCell::draw() { reinterpret_cast<StatsCell*>(this)->StatsCell::draw(); @@ -34,16 +36,14 @@ void ModListCell::setupInfo(ModInfo const& info, bool spaceForCategories) { auto logoSize = m_height / 1.5f; - auto logoSpr = this->createLogo({ logoSize, logoSize }); - logoSpr->setPosition({ logoSize / 2 + 12.f, m_height / 2 }); + auto logoSpr = this->createLogo({logoSize, logoSize}); + logoSpr->setPosition({logoSize / 2 + 12.f, m_height / 2}); m_mainLayer->addChild(logoSpr); - bool hasDesc = - m_display == ModListDisplay::Expanded && - info.m_description.has_value(); + bool hasDesc = m_display == ModListDisplay::Expanded && info.m_description.has_value(); auto titleLabel = CCLabelBMFont::create(info.m_name.c_str(), "bigFont.fnt"); - titleLabel->setAnchorPoint({ .0f, .5f }); + titleLabel->setAnchorPoint({.0f, .5f}); titleLabel->setPositionX(m_height / 2 + logoSize / 2 + 13.f); if (hasDesc && spaceForCategories) { titleLabel->setPositionY(m_height / 2 + 20.f); @@ -58,18 +58,18 @@ void ModListCell::setupInfo(ModInfo const& info, bool spaceForCategories) { m_mainLayer->addChild(titleLabel); auto versionLabel = CCLabelBMFont::create(info.m_version.toString().c_str(), "bigFont.fnt"); - versionLabel->setAnchorPoint({ .0f, .5f }); + versionLabel->setAnchorPoint({.0f, .5f}); versionLabel->setScale(.3f); versionLabel->setPosition( titleLabel->getPositionX() + titleLabel->getScaledContentSize().width + 5.f, titleLabel->getPositionY() - 1.f ); - versionLabel->setColor({ 0, 255, 0 }); + versionLabel->setColor({0, 255, 0}); m_mainLayer->addChild(versionLabel); auto creatorStr = "by " + info.m_developer; auto creatorLabel = CCLabelBMFont::create(creatorStr.c_str(), "goldFont.fnt"); - creatorLabel->setAnchorPoint({ .0f, .5f }); + creatorLabel->setAnchorPoint({.0f, .5f}); creatorLabel->setScale(.43f); creatorLabel->setPositionX(m_height / 2 + logoSize / 2 + 13.f); if (hasDesc && spaceForCategories) { @@ -84,11 +84,11 @@ void ModListCell::setupInfo(ModInfo const& info, bool spaceForCategories) { m_mainLayer->addChild(creatorLabel); if (hasDesc) { - auto descBG = CCScale9Sprite::create("square02b_001.png", { 0.0f, 0.0f, 80.0f, 80.0f }); - descBG->setColor({ 0, 0, 0 }); + auto descBG = CCScale9Sprite::create("square02b_001.png", {0.0f, 0.0f, 80.0f, 80.0f}); + descBG->setColor({0, 0, 0}); descBG->setOpacity(90); - descBG->setContentSize({ m_width * 2, 60.f }); - descBG->setAnchorPoint({ .0f, .5f }); + descBG->setContentSize({m_width * 2, 60.f}); + descBG->setAnchorPoint({.0f, .5f}); descBG->setPositionX(m_height / 2 + logoSize / 2 + 13.f); if (spaceForCategories) { descBG->setPositionY(m_height / 2 - 7.5f); @@ -100,7 +100,7 @@ void ModListCell::setupInfo(ModInfo const& info, bool spaceForCategories) { m_mainLayer->addChild(descBG); auto descText = CCLabelBMFont::create(info.m_description.value().c_str(), "chatFont.fnt"); - descText->setAnchorPoint({ .0f, .5f }); + descText->setAnchorPoint({.0f, .5f}); descText->setPosition(m_height / 2 + logoSize / 2 + 18.f, descBG->getPositionY()); descText->limitLabelWidth(m_width / 2 - 10.f, .5f, .1f); m_mainLayer->addChild(descText); @@ -123,13 +123,9 @@ bool ModListCell::init(ModListView* list, ModListDisplay display) { // ModCell -ModCell::ModCell(const char* name, CCSize const& size) - : ModListCell(name, size) {} +ModCell::ModCell(char const* name, CCSize const& size) : ModListCell(name, size) {} -ModCell* ModCell::create( - ModListView* list, ModListDisplay display, - const char* key, CCSize const& size -) { +ModCell* ModCell::create(ModListView* list, ModListDisplay display, char const* key, CCSize const& size) { auto ret = new ModCell(key, size); if (ret && ret->init(list, display)) { return ret; @@ -139,7 +135,7 @@ ModCell* ModCell::create( } void ModCell::onEnable(CCObject* sender) { - if (!InternalLoader::get()->shownInfoAlert("mod-disable-vs-unload")) { + if (!LoaderImpl::get()->shownInfoAlert("mod-disable-vs-unload")) { FLAlertLayer::create( "Notice", "<cb>Disabling</c> a <cy>mod</c> removes its hooks & patches and " @@ -147,7 +143,8 @@ void ModCell::onEnable(CCObject* sender) { "still see some effects of the mod left however, and you may " "need to <cg>restart</c> the game to have it fully unloaded.", "OK" - )->show(); + ) + ->show(); m_list->updateAllStates(this); return; } @@ -165,10 +162,7 @@ void ModCell::onUnresolvedInfo(CCObject*) { "This mod has the following " "<cr>unresolved dependencies</c>: "; for (auto const& dep : m_mod->getUnresolvedDependencies()) { - info += fmt::format( - "<cg>{}</c> (<cy>{}</c>), ", - dep.m_id, dep.m_version.toString() - ); + info += fmt::format("<cg>{}</c> (<cy>{}</c>), ", dep.m_id, dep.m_version.toString()); } info.pop_back(); info.pop_back(); @@ -197,20 +191,15 @@ void ModCell::loadFromMod(Mod* mod) { this->setupInfo(mod->getModInfo(), false); - auto viewSpr = ButtonSprite::create( - "View", "bigFont.fnt", "GJ_button_01.png", .8f - ); + auto viewSpr = ButtonSprite::create("View", "bigFont.fnt", "GJ_button_01.png", .8f); viewSpr->setScale(.65f); - auto viewBtn = CCMenuItemSpriteExtra::create( - viewSpr, this, menu_selector(ModCell::onInfo) - ); + auto viewBtn = CCMenuItemSpriteExtra::create(viewSpr, this, menu_selector(ModCell::onInfo)); m_menu->addChild(viewBtn); if (m_mod->wasSuccesfullyLoaded() && m_mod->supportsDisabling()) { - m_enableToggle = CCMenuItemToggler::createWithStandardSprites( - this, menu_selector(ModCell::onEnable), .7f - ); + m_enableToggle = + CCMenuItemToggler::createWithStandardSprites(this, menu_selector(ModCell::onEnable), .7f); m_enableToggle->setPosition(-45.f, 0.f); m_menu->addChild(m_enableToggle); } @@ -218,23 +207,22 @@ void ModCell::loadFromMod(Mod* mod) { auto exMark = CCSprite::createWithSpriteFrameName("exMark_001.png"); exMark->setScale(.5f); - m_unresolvedExMark = CCMenuItemSpriteExtra::create( - exMark, this, menu_selector(ModCell::onUnresolvedInfo) - ); + m_unresolvedExMark = + CCMenuItemSpriteExtra::create(exMark, this, menu_selector(ModCell::onUnresolvedInfo)); m_unresolvedExMark->setPosition(-80.f, 0.f); m_unresolvedExMark->setVisible(false); m_menu->addChild(m_unresolvedExMark); // if (m_mod->wasSuccesfullyLoaded()) { - // if (Index::get()->isUpdateAvailableForItem(m_obj->m_mod->getID())) { - // viewSpr->updateBGImage("GE_button_01.png"_spr); + // if (Index::get()->isUpdateAvailableForItem(m_obj->m_mod->getID())) { + // viewSpr->updateBGImage("GE_button_01.png"_spr); - // 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); - // } + // 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(); @@ -246,16 +234,14 @@ CCNode* ModCell::createLogo(CCSize const& size) { // IndexItemCell -IndexItemCell::IndexItemCell(char const* name, CCSize const& size) - : ModListCell(name, size) {} +IndexItemCell::IndexItemCell(char const* name, CCSize const& size) : ModListCell(name, size) {} void IndexItemCell::onInfo(CCObject*) { IndexItemInfoPopup::create(m_item, m_list)->show(); } IndexItemCell* IndexItemCell::create( - ModListView* list, ModListDisplay display, - const char* key, CCSize const& size + ModListView* list, ModListDisplay display, char const* key, CCSize const& size ) { auto ret = new IndexItemCell(key, size); if (ret && ret->init(list, display)) { @@ -269,15 +255,11 @@ void IndexItemCell::loadFromItem(IndexItemHandle item) { m_item = item; this->setupInfo(item->info, true); - - auto viewSpr = ButtonSprite::create( - "View", "bigFont.fnt", "GJ_button_01.png", .8f - ); + + 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) - ); + auto viewBtn = CCMenuItemSpriteExtra::create(viewSpr, this, menu_selector(IndexItemCell::onInfo)); m_menu->addChild(viewBtn); // if (hasCategories) { @@ -298,7 +280,7 @@ void IndexItemCell::loadFromItem(IndexItemHandle item) { // x += node->getScaledContentSize().width + 5.f; // } // } - + this->updateState(); } @@ -310,15 +292,11 @@ CCNode* IndexItemCell::createLogo(CCSize const& size) { // InvalidGeodeFileCell -InvalidGeodeFileCell::InvalidGeodeFileCell(const char* name, CCSize const& size) - : ModListCell(name, size) {} +InvalidGeodeFileCell::InvalidGeodeFileCell(char const* name, CCSize const& size) : + ModListCell(name, size) {} void InvalidGeodeFileCell::onInfo(CCObject*) { - FLAlertLayer::create( - this, "Error Info", - m_info.m_reason, - "OK", "Remove file", 360.f - )->show(); + FLAlertLayer::create(this, "Error Info", m_info.m_reason, "OK", "Remove file", 360.f)->show(); } void InvalidGeodeFileCell::FLAlert_Clicked(FLAlertLayer*, bool btn2) { @@ -327,13 +305,16 @@ void InvalidGeodeFileCell::FLAlert_Clicked(FLAlertLayer*, bool btn2) { if (ghc::filesystem::remove(m_info.m_path)) { FLAlertLayer::create( "File removed", "Removed <cy>" + m_info.m_path.string() + "</c>", "OK" - )->show(); + ) + ->show(); } else { FLAlertLayer::create( "Unable to remove file", - "Unable to remove <cy>" + m_info.m_path.string() + "</c>", "OK" - )->show(); + "Unable to remove <cy>" + m_info.m_path.string() + "</c>", + "OK" + ) + ->show(); } } catch (std::exception& e) { @@ -342,7 +323,8 @@ void InvalidGeodeFileCell::FLAlert_Clicked(FLAlertLayer*, bool btn2) { "Unable to remove <cy>" + m_info.m_path.string() + "</c>: <cr>" + std::string(e.what()) + "</c>", "OK" - )->show(); + ) + ->show(); } (void)Loader::get()->refreshModsList(); m_list->refreshList(); @@ -350,8 +332,7 @@ void InvalidGeodeFileCell::FLAlert_Clicked(FLAlertLayer*, bool btn2) { } InvalidGeodeFileCell* InvalidGeodeFileCell::create( - ModListView* list, ModListDisplay display, - char const* key, CCSize const& size + ModListView* list, ModListDisplay display, char const* key, CCSize const& size ) { auto ret = new InvalidGeodeFileCell(key, size); if (ret && ret->init(list, display)) { @@ -371,29 +352,23 @@ void InvalidGeodeFileCell::loadFromInfo(InvalidGeodeFile const& info) { m_mainLayer->addChild(menu); auto titleLabel = CCLabelBMFont::create("Failed to Load", "bigFont.fnt"); - titleLabel->setAnchorPoint({ .0f, .5f }); + titleLabel->setAnchorPoint({.0f, .5f}); titleLabel->setScale(.5f); titleLabel->setPosition(m_height / 2, m_height / 2 + 7.f); m_mainLayer->addChild(titleLabel); - auto pathLabel = CCLabelBMFont::create( - m_info.m_path.string().c_str(), - "chatFont.fnt" - ); - pathLabel->setAnchorPoint({ .0f, .5f }); + auto pathLabel = CCLabelBMFont::create(m_info.m_path.string().c_str(), "chatFont.fnt"); + pathLabel->setAnchorPoint({.0f, .5f}); pathLabel->setScale(.43f); pathLabel->setPosition(m_height / 2, m_height / 2 - 7.f); - pathLabel->setColor({ 255, 255, 0 }); + pathLabel->setColor({255, 255, 0}); m_mainLayer->addChild(pathLabel); - auto whySpr = ButtonSprite::create( - "Info", 0, 0, "bigFont.fnt", "GJ_button_01.png", 0, .8f - ); + auto whySpr = ButtonSprite::create("Info", 0, 0, "bigFont.fnt", "GJ_button_01.png", 0, .8f); whySpr->setScale(.65f); - auto viewBtn = CCMenuItemSpriteExtra::create( - whySpr, this, menu_selector(InvalidGeodeFileCell::onInfo) - ); + auto viewBtn = + CCMenuItemSpriteExtra::create(whySpr, this, menu_selector(InvalidGeodeFileCell::onInfo)); menu->addChild(viewBtn); } diff --git a/loader/src/ui/internal/list/ModListView.cpp b/loader/src/ui/internal/list/ModListView.cpp index 87a115c5..725217b6 100644 --- a/loader/src/ui/internal/list/ModListView.cpp +++ b/loader/src/ui/internal/list/ModListView.cpp @@ -1,20 +1,19 @@ #include "ModListView.hpp" #include "../info/CategoryNode.hpp" -#include "ModListLayer.hpp" #include "ModListCell.hpp" +#include "ModListLayer.hpp" #include <Geode/binding/ButtonSprite.hpp> -#include <Geode/binding/CCMenuItemSpriteExtra.hpp> -#include <Geode/binding/TableView.hpp> -#include <Geode/binding/CCMenuItemToggler.hpp> #include <Geode/binding/CCContentLayer.hpp> +#include <Geode/binding/CCMenuItemSpriteExtra.hpp> +#include <Geode/binding/CCMenuItemToggler.hpp> +#include <Geode/binding/TableView.hpp> +#include <Geode/loader/Index.hpp> #include <Geode/loader/Mod.hpp> #include <Geode/utils/casts.hpp> #include <Geode/utils/cocos.hpp> #include <Geode/utils/string.hpp> -#include <Geode/loader/Index.hpp> -#include <InternalLoader.hpp> void ModListView::updateAllStates(ModListCell* toggled) { for (auto cell : CCArrayExt<ModListCell>(m_tableView->m_cellArray)) { @@ -34,10 +33,9 @@ void ModListView::setupList() { // fix content layer content size so the // list is properly aligned to the top auto coverage = calculateChildCoverage(m_tableView->m_contentLayer); - m_tableView->m_contentLayer->setContentSize({ - -coverage.origin.x + coverage.size.width, - -coverage.origin.y + coverage.size.height - }); + m_tableView->m_contentLayer->setContentSize( + {-coverage.origin.x + coverage.size.width, -coverage.origin.y + coverage.size.height} + ); if (m_entries->count() == 1) { m_tableView->moveToTopWithOffset(m_itemSeparation * 2); @@ -51,7 +49,7 @@ void ModListView::setupList() { } TableViewCell* ModListView::getListCell(char const* key) { - return ModCell::create(this, m_display, key, { m_width, m_itemSeparation }); + return ModCell::create(this, m_display, key, {m_width, m_itemSeparation}); } void ModListView::loadCell(TableViewCell* cell, unsigned int index) { @@ -112,7 +110,7 @@ CCArray* ModListView::modsForType(ModListType type) { mods->addObject(new InvalidGeodeFileObject(mod)); } // internal geode representation always at the top - auto imod = Loader::getInternalMod(); + auto imod = Loader::get()->getInternalMod(); mods->addObject(new ModObject(imod)); // then other mods From a137fd96374f638b94320f7ac23d7e0d321a306a Mon Sep 17 00:00:00 2001 From: altalk23 <45172705+altalk23@users.noreply.github.com> Date: Sun, 11 Dec 2022 14:37:57 +0300 Subject: [PATCH 3/4] fix filesystem for windows --- loader/src/loader/Dirs.cpp | 38 +++++++++++++++++--------------------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/loader/src/loader/Dirs.cpp b/loader/src/loader/Dirs.cpp index 275802fc..eb463679 100644 --- a/loader/src/loader/Dirs.cpp +++ b/loader/src/loader/Dirs.cpp @@ -2,6 +2,7 @@ #include <Geode/loader/Dirs.hpp> #include <cocos2d.h> #include <crashlog.hpp> +#include <filesystem> USE_GEODE_NAMESPACE(); @@ -10,31 +11,26 @@ ghc::filesystem::path dirs::getGameDir() { } ghc::filesystem::path dirs::getSaveDir() { - #ifdef GEODE_IS_MACOS - // not using ~/Library/Caches - return ghc::filesystem::path("/Users/Shared/Geode"); - #elif defined(GEODE_IS_WINDOWS) - // this is std::filesystem intentionally because ghc version doesnt want to work with softlinked directories - return ghc::filesystem::path( - std::filesystem::weakly_canonical( - CCFileUtils::sharedFileUtils()->getWritablePath().c_str() - ).string() - ); - #else - return ghc::filesystem::path( - CCFileUtils::sharedFileUtils()->getWritablePath().c_str() - ); - #endif +#ifdef GEODE_IS_MACOS + // not using ~/Library/Caches + return ghc::filesystem::path("/Users/Shared/Geode"); +#elif defined(GEODE_IS_WINDOWS) + // this is std::filesystem intentionally because ghc version doesnt want to work with softlinked directories + return ghc::filesystem::path( + std::filesystem::weakly_canonical(CCFileUtils::sharedFileUtils()->getWritablePath().c_str()) + .string() + ); +#else + return ghc::filesystem::path(CCFileUtils::sharedFileUtils()->getWritablePath().c_str()); +#endif } ghc::filesystem::path dirs::getGeodeDir() { - #ifdef GEODE_IS_MACOS - char cwd[PATH_MAX]; - getcwd(cwd, sizeof(cwd)); - return ghc::filesystem::path(cwd) / "geode"; - #else +#ifdef GEODE_IS_MACOS + return ghc::filesystem::current_path() / "geode"; +#else return dirs::getGameDir() / "geode"; - #endif +#endif } ghc::filesystem::path dirs::getGeodeSaveDir() { From d5e0582934fd460b7ba2e815fac1faf35992400f Mon Sep 17 00:00:00 2001 From: altalk23 <45172705+altalk23@users.noreply.github.com> Date: Mon, 12 Dec 2022 14:42:20 +0300 Subject: [PATCH 4/4] Fix compilation caused from merging --- loader/include/Geode/loader/Loader.hpp | 4 +- loader/src/hooks/LoadingLayer.cpp | 2 +- loader/src/loader/Loader.cpp | 4 +- loader/src/loader/LoaderImpl.cpp | 184 ++++++++++--------- loader/src/loader/LoaderImpl.hpp | 9 +- loader/src/ui/internal/info/ModInfoPopup.cpp | 1 - loader/src/ui/internal/list/ModListCell.cpp | 3 +- loader/src/ui/internal/list/ModListLayer.cpp | 2 +- 8 files changed, 107 insertions(+), 102 deletions(-) diff --git a/loader/include/Geode/loader/Loader.hpp b/loader/include/Geode/loader/Loader.hpp index c64e72f9..82eab6cd 100644 --- a/loader/include/Geode/loader/Loader.hpp +++ b/loader/include/Geode/loader/Loader.hpp @@ -50,8 +50,8 @@ namespace geode { bool isModVersionSupported(VersionInfo const& version); Result<Mod*> loadModFromFile(ghc::filesystem::path const& file); - Result<> loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive = true); - Result<> refreshModsList(); + void loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive = true); + void refreshModsList(); bool isModInstalled(std::string const& id) const; Mod* getInstalledMod(std::string const& id) const; bool isModLoaded(std::string const& id) const; diff --git a/loader/src/hooks/LoadingLayer.cpp b/loader/src/hooks/LoadingLayer.cpp index 20eb3adb..62c87b2d 100644 --- a/loader/src/hooks/LoadingLayer.cpp +++ b/loader/src/hooks/LoadingLayer.cpp @@ -58,7 +58,7 @@ struct CustomLoadingLayer : Modify<CustomLoadingLayer, LoadingLayer> { this->loadAssets(); }, [&](UpdateFailed const& error) { - InternalLoader::platformMessageBox( + LoaderImpl::get()->platformMessageBox( "Error updating resources", "Unable to update Geode resources: " + error + ".\n" diff --git a/loader/src/loader/Loader.cpp b/loader/src/loader/Loader.cpp index b533eb5c..5d3e783c 100644 --- a/loader/src/loader/Loader.cpp +++ b/loader/src/loader/Loader.cpp @@ -57,11 +57,11 @@ Result<Mod*> Loader::loadModFromFile(ghc::filesystem::path const& file) { return m_impl->loadModFromFile(file); } -Result<> Loader::loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive) { +void Loader::loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive) { return m_impl->loadModsFromDirectory(dir, recursive); } -Result<> Loader::refreshModsList() { +void Loader::refreshModsList() { return m_impl->refreshModsList(); } diff --git a/loader/src/loader/LoaderImpl.cpp b/loader/src/loader/LoaderImpl.cpp index f6bd4212..e33cbb43 100644 --- a/loader/src/loader/LoaderImpl.cpp +++ b/loader/src/loader/LoaderImpl.cpp @@ -32,18 +32,6 @@ Loader::Impl::Impl() {} Loader::Impl::~Impl() {} -void Loader::Impl::reset() { - this->closePlatformConsole(); - - for (auto& [_, mod] : m_mods) { - delete mod; - } - m_mods.clear(); - log::Logs::clear(); - ghc::filesystem::remove_all(dirs::getModRuntimeDir()); - ghc::filesystem::remove_all(dirs::getTempDir()); -} - // Initialization void Loader::Impl::createDirectories() { @@ -68,20 +56,6 @@ Result<> Loader::Impl::setup() { return Ok(); } - log::debug("Set up internal mod representation"); - log::debug("Loading hooks... "); - - if (!this->loadHooks()) { - log::error("There were errors loading some hooks, see console for details"); - return Err("There were errors loading some hooks, see console for details"); - } - - log::debug("Loaded hooks"); - - log::debug("Setting up IPC..."); - - this->setupIPC(); - log::Logs::setup(); if (crashlog::setupPlatformHandler()) { @@ -98,7 +72,7 @@ Result<> Loader::Impl::setup() { if (!sett) { log::warn("Unable to load loader settings: {}", sett.unwrapErr()); } - GEODE_UNWRAP(this->refreshModsList()); + this->refreshModsList(); this->queueInGDThread([]() { Loader::get()->addSearchPaths(); @@ -145,11 +119,11 @@ VersionInfo Loader::Impl::getVersion() { } VersionInfo Loader::Impl::minModVersion() { - return VersionInfo{0, 3, 1}; + return VersionInfo { 0, 3, 1 }; } VersionInfo Loader::Impl::maxModVersion() { - return VersionInfo{ + return VersionInfo { this->getVersion().getMajor(), this->getVersion().getMinor(), // todo: dynamic version info (vM.M.*) @@ -158,7 +132,9 @@ VersionInfo Loader::Impl::maxModVersion() { } bool Loader::Impl::isModVersionSupported(VersionInfo const& version) { - return version >= this->minModVersion() && version <= this->maxModVersion(); + return + version >= this->minModVersion() && + version <= this->maxModVersion(); } // Data saving @@ -173,7 +149,7 @@ Result<> Loader::Impl::saveData() { } // save loader data GEODE_UNWRAP(InternalMod::get()->saveData()); - + return Ok(); } @@ -194,16 +170,16 @@ Result<> Loader::Impl::loadData() { // Mod loading Result<Mod*> Loader::Impl::loadModFromInfo(ModInfo const& info) { - if (m_mods.count(info.m_id)) { - return Err(fmt::format("Mod with ID '{}' already loaded", info.m_id)); + 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); - m_mods.insert({info.m_id, mod}); - mod->m_enabled = InternalMod::get()->getSavedValue<bool>("should-load-" + info.m_id, true); - // this loads the mod if its dependencies are resolved - mod->updateDependencyStates(); + m_mods.insert({ info.id, mod }); + mod->m_enabled = InternalMod::get()->getSavedValue<bool>( + "should-load-" + info.id, true + ); // add mod resources this->queueInGDThread([this, mod]() { @@ -213,15 +189,18 @@ Result<Mod*> Loader::Impl::loadModFromInfo(ModInfo const& info) { this->updateModResources(mod); }); + // this loads the mod if its dependencies are resolved + GEODE_UNWRAP(mod->updateDependencies()); + 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{ - .m_path = file, - .m_reason = res.unwrapErr(), + m_invalidMods.push_back(InvalidGeodeFile { + .path = file, + .reason = res.unwrapErr(), }); return Err(res.unwrapErr()); } @@ -270,7 +249,7 @@ void Loader::Impl::scheduleOnModLoad(Mod* mod, ScheduledFunction func) { } void Loader::Impl::updateModResources(Mod* mod) { - if (!mod->m_info.m_spritesheets.size()) { + if (!mod->m_info.spritesheets.size()) { return; } @@ -279,7 +258,7 @@ void Loader::Impl::updateModResources(Mod* mod) { log::debug("Adding resources for {}", mod->getID()); // add spritesheets - for (auto const& sheet : mod->m_info.m_spritesheets) { + for (auto const& sheet : mod->m_info.spritesheets) { log::debug("Adding sheet {}", sheet); auto png = sheet + ".png"; auto plist = sheet + ".plist"; @@ -289,8 +268,7 @@ void Loader::Impl::updateModResources(Mod* mod) { plist == std::string(ccfu->fullPathForFilename(plist.c_str(), false))) { log::warn( "The resource dir of \"{}\" is missing \"{}\" png and/or plist files", - mod->m_info.m_id, - sheet + mod->m_info.id, sheet ); } else { @@ -302,12 +280,15 @@ void Loader::Impl::updateModResources(Mod* mod) { // Dependencies and refreshing -Result<> Loader::Impl::loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive) { +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) { - GEODE_UNWRAP(this->loadModsFromDirectory(entry.path(), true)); + this->loadModsFromDirectory(entry.path(), true); continue; } @@ -322,24 +303,27 @@ Result<> Loader::Impl::loadModsFromDirectory(ghc::filesystem::path const& dir, b } // skip this entry if it's already loaded if (map::contains<std::string, Mod*>(m_mods, [entry](Mod* p) -> bool { - return p->m_info.m_path == entry.path(); - })) { + return p->m_info.path == entry.path(); + })) { continue; } // if mods should be loaded immediately, do that if (m_earlyLoadFinished) { - GEODE_UNWRAP(this->loadModFromFile(entry)); + 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 + // 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{ - .m_path = entry.path(), - .m_reason = res.unwrapErr(), + m_invalidMods.push_back(InvalidGeodeFile { + .path = entry.path(), + .reason = res.unwrapErr(), }); continue; } @@ -354,21 +338,23 @@ Result<> Loader::Impl::loadModsFromDirectory(ghc::filesystem::path const& dir, b m_modsToLoad.push_back(info); } } - return Ok(); } -Result<> Loader::Impl::refreshModsList() { +void Loader::Impl::refreshModsList() { log::debug("Loading mods..."); // find mods for (auto& dir : m_modSearchDirectories) { - GEODE_UNWRAP(this->loadModsFromDirectory(dir)); + this->loadModsFromDirectory(dir); } - + // load early-load mods first for (auto& mod : m_modsToLoad) { - if (mod.m_needsEarlyLoad) { - GEODE_UNWRAP(this->loadModFromInfo(mod)); + if (mod.needsEarlyLoad) { + auto load = this->loadModFromInfo(mod); + if (!load) { + log::error("Unable to load {}: {}", mod.id, load.unwrapErr()); + } } } @@ -377,18 +363,19 @@ Result<> Loader::Impl::refreshModsList() { // load the rest of the mods for (auto& mod : m_modsToLoad) { - if (!mod.m_needsEarlyLoad) { - GEODE_UNWRAP(this->loadModFromInfo(mod)); + if (!mod.needsEarlyLoad) { + auto load = this->loadModFromInfo(mod); + if (!load) { + log::error("Unable to load {}: {}", mod.id, load.unwrapErr()); + } } } m_modsToLoad.clear(); - - return Ok(); } void Loader::Impl::updateAllDependencies() { for (auto const& [_, mod] : m_mods) { - mod->updateDependencyStates(); + (void)mod->updateDependencies(); } } @@ -400,6 +387,20 @@ bool Loader::Impl::didLastLaunchCrash() const { return crashlog::didLastLaunchCrash(); } + + + +void Loader::Impl::reset() { + this->closePlatformConsole(); + + for (auto& [_, mod] : m_mods) { + delete mod; + } + m_mods.clear(); + log::Logs::clear(); + ghc::filesystem::remove_all(dirs::getModRuntimeDir()); + ghc::filesystem::remove_all(dirs::getTempDir()); +} bool Loader::Impl::isReadyToHook() const { return m_readyToHook; } @@ -485,20 +486,23 @@ void Loader::Impl::downloadLoaderResources() { auto unzip = file::Unzip::intoDir(tempResourcesZip, resourcesDir, true); if (!unzip) { return ResourceDownloadEvent( - UpdateError("Unable to unzip new resources: " + unzip.unwrapErr()) - ) - .post(); + UpdateFailed("Unable to unzip new resources: " + unzip.unwrapErr()) + ).post(); } ResourceDownloadEvent(UpdateFinished()).post(); }) .expect([](std::string const& info) { - ResourceDownloadEvent(UpdateError("Unable to download resources: " + info)).post(); + ResourceDownloadEvent( + UpdateFailed("Unable to download resources: " + info) + ).post(); }) .progress([](auto&, double now, double total) { ResourceDownloadEvent( - UpdateProgress(static_cast<uint8_t>(now / total * 100.0), "Downloading resources") - ) - .post(); + UpdateProgress( + static_cast<uint8_t>(now / total * 100.0), + "Downloading resources" + ) + ).post(); }); } @@ -512,7 +516,10 @@ bool Loader::Impl::verifyLoaderResources() { auto resourcesDir = dirs::getGeodeResourcesDir() / InternalMod::get()->getID(); // if the resources dir doesn't exist, then it's probably incorrect - if (!(ghc::filesystem::exists(resourcesDir) && ghc::filesystem::is_directory(resourcesDir))) { + if (!( + ghc::filesystem::exists(resourcesDir) && + ghc::filesystem::is_directory(resourcesDir) + )) { this->downloadLoaderResources(); return false; } @@ -530,7 +537,9 @@ bool Loader::Impl::verifyLoaderResources() { // verify hash auto hash = calculateSHA256(file.path()); if (hash != LOADER_RESOURCE_HASHES.at(name)) { - log::debug("compare {} {} {}", file.path().string(), hash, LOADER_RESOURCE_HASHES.at(name)); + log::debug( + "compare {} {} {}", file.path().string(), hash, LOADER_RESOURCE_HASHES.at(name) + ); this->downloadLoaderResources(); return false; } @@ -546,12 +555,9 @@ bool Loader::Impl::verifyLoaderResources() { return true; } -std::string Loader::Impl::processRawIPC(void* rawHandle, std::string const& buffer) { - std::string reply; - +nlohmann::json Loader::Impl::processRawIPC(void* rawHandle, std::string const& buffer) { + nlohmann::json reply; try { - std::optional<std::string> replyID = std::nullopt; - // parse received message auto json = nlohmann::json::parse(buffer); if (!json.contains("mod") || !json["mod"].is_string()) { @@ -562,29 +568,29 @@ std::string Loader::Impl::processRawIPC(void* rawHandle, std::string const& buff log::warn("Received IPC message without 'message' field"); return reply; } - if (json.contains("reply") && json["reply"].is_string()) { - replyID = json["reply"]; - } nlohmann::json data; if (json.contains("data")) { data = json["data"]; } // log::debug("Posting IPC event"); // ! warning: if the event system is ever made asynchronous this will break! - IPCEvent(rawHandle, json["mod"], json["message"], data, &reply).post(); - } - catch (...) { + IPCEvent(rawHandle, json["mod"], json["message"], data, reply).post(); + } catch(...) { log::warn("Received IPC message that isn't valid JSON"); } - return reply; } -ResourceDownloadEvent::ResourceDownloadEvent(UpdateStatus const& status) : status(status) {} +ResourceDownloadEvent::ResourceDownloadEvent( + UpdateStatus const& status +) : status(status) {} -ListenerResult ResourceDownloadFilter::handle(std::function<Callback> fn, ResourceDownloadEvent* event) { +ListenerResult ResourceDownloadFilter::handle( + std::function<Callback> fn, + ResourceDownloadEvent* event +) { fn(event); return ListenerResult::Propagate; } -ResourceDownloadFilter::ResourceDownloadFilter() {} +ResourceDownloadFilter::ResourceDownloadFilter() {} \ No newline at end of file diff --git a/loader/src/loader/LoaderImpl.hpp b/loader/src/loader/LoaderImpl.hpp index 6643f9ab..c369368e 100644 --- a/loader/src/loader/LoaderImpl.hpp +++ b/loader/src/loader/LoaderImpl.hpp @@ -93,8 +93,8 @@ public: bool isModVersionSupported(VersionInfo const& version); Result<Mod*> loadModFromFile(ghc::filesystem::path const& file); - Result<> loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive = true); - Result<> refreshModsList(); + void loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive = true); + void refreshModsList(); bool isModInstalled(std::string const& id) const; Mod* getInstalledMod(std::string const& id) const; bool isModLoaded(std::string const& id) const; @@ -106,13 +106,12 @@ public: void updateResources(); - void queueInGDThread(ScheduledFunction func); void scheduleOnModLoad(Mod* mod, ScheduledFunction func); void waitForModsToBeLoaded(); bool didLastLaunchCrash() const; - std::string processRawIPC(void* rawHandle, std::string const& buffer); + nlohmann::json processRawIPC(void* rawHandle, std::string const& buffer); /** * Check if a one-time event has been shown to the user, @@ -120,6 +119,8 @@ public: * state of the event before setting it to true */ bool shownInfoAlert(std::string const& key); + + void queueInGDThread(ScheduledFunction func); void executeGDThreadQueue(); void logConsoleMessage(std::string const& msg); diff --git a/loader/src/ui/internal/info/ModInfoPopup.cpp b/loader/src/ui/internal/info/ModInfoPopup.cpp index eba0f408..31272273 100644 --- a/loader/src/ui/internal/info/ModInfoPopup.cpp +++ b/loader/src/ui/internal/info/ModInfoPopup.cpp @@ -3,7 +3,6 @@ #include "../dev/HookListLayer.hpp" #include "../list/ModListLayer.hpp" #include "../settings/ModSettingsPopup.hpp" -#include <InternalLoader.hpp> #include <Geode/loader/Dirs.hpp> #include <Geode/binding/ButtonSprite.hpp> diff --git a/loader/src/ui/internal/list/ModListCell.cpp b/loader/src/ui/internal/list/ModListCell.cpp index 3c691978..861b812b 100644 --- a/loader/src/ui/internal/list/ModListCell.cpp +++ b/loader/src/ui/internal/list/ModListCell.cpp @@ -1,7 +1,6 @@ #include "ModListCell.hpp" #include "ModListLayer.hpp" #include "../info/ModInfoPopup.hpp" -#include "ModListView.hpp" #include <Geode/binding/ButtonSprite.hpp> #include <Geode/binding/CCMenuItemSpriteExtra.hpp> @@ -9,7 +8,7 @@ #include <Geode/binding/FLAlertLayer.hpp> #include <Geode/binding/StatsCell.hpp> #include <Geode/ui/GeodeUI.hpp> -#include <InternalLoader.hpp> +#include "../../../loader/LoaderImpl.hpp" // how should i include this src/loader/LoaderImpl.hpp #include "../info/TagNode.hpp" template <class T> diff --git a/loader/src/ui/internal/list/ModListLayer.cpp b/loader/src/ui/internal/list/ModListLayer.cpp index 053fcf22..b8648551 100644 --- a/loader/src/ui/internal/list/ModListLayer.cpp +++ b/loader/src/ui/internal/list/ModListLayer.cpp @@ -166,7 +166,7 @@ CCArray* ModListLayer::createModCells(ModListType type, ModListQuery const& quer // something matches query better // the sorted list is in reverse so adding it last = adding it at the // top - auto imod = Loader::getInternalMod(); + auto imod = Mod::get(); if (auto match = queryMatch(query, imod)) { sorted.insert({ match.value(), imod }); }