diff --git a/bindings/GeometryDash.bro b/bindings/GeometryDash.bro index de62d935..a1ef1f3a 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; 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 ef1cbab3..82eab6cd 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 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); - void loadModsFromDirectory( - ghc::filesystem::path const& dir, - bool recursive = true - ); + 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; 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 fcb613b3..c6b84ddf 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(); @@ -104,7 +103,6 @@ namespace geode { friend class ::InternalMod; friend class Loader; - friend class ::InternalLoader; friend struct ModInfo; template <class = void> @@ -169,41 +167,45 @@ 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 @@ -337,9 +339,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 @@ -387,7 +389,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 1e49be06..d264d1e0 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 { @@ -130,7 +130,7 @@ namespace geode { }; constexpr std::string_view GEODE_MOD_EXTENSION = ".geode"; - + class Mod; class Setting; class Loader; @@ -146,7 +146,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 b1a973a3..b48c7c71 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(); @@ -21,23 +21,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"); } @@ -62,7 +60,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/hooks/MenuLayer.cpp b/loader/src/hooks/MenuLayer.cpp index e0b37acb..18d953f5 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(); @@ -45,15 +44,14 @@ $execute { struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> { CCSprite* m_geodeButton; - 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); + + auto winSize = CCDirector::sharedDirector()->getWinSize(); // add geode button @@ -66,7 +64,7 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> { )) .orMake<ButtonSprite>("!!"); - auto bottomMenu = static_cast<CCMenu*>(this->getChildByID("bottom-menu")); + auto bottomMenu = static_cast<CCMenu*>(this->getChildByID("bottom-menu")); auto btn = CCMenuItemSpriteExtra::create( m_fields->m_geodeButton, this, menu_selector(CustomMenuLayer::onGeode) @@ -74,46 +72,44 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> { btn->setID("geode-button"_spr); bottomMenu->addChild(btn); - bottomMenu->updateLayout(); + bottomMenu->updateLayout(); - if (auto node = this->getChildByID("settings-gamepad-icon")) { - node->setPositionX(bottomMenu->getChildByID( - "settings-button" - )->getPositionX() + winSize.width / 2); - } - - // 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(); - } + 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(); + } + } + + // 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(); + } // update mods index if (!INDEX_UPDATE_NOTIF && !Index::get()->hasTriedToUpdate()) { @@ -153,7 +149,7 @@ struct CustomMenuLayer : Modify<CustomMenuLayer, MenuLayer> { } } - void onGeode(CCObject*) { - ModListLayer::scene(); - } + 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 004688fe..00000000 --- a/loader/src/internal/InternalLoader.cpp +++ /dev/null @@ -1,227 +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; -} - -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( - UpdateFailed("Unable to unzip new resources: " + unzip.unwrapErr()) - ).post(); - } - ResourceDownloadEvent(UpdateFinished()).post(); - }) - .expect([](std::string const& info) { - 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(); - }); -} - -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; -} - -nlohmann::json InternalLoader::processRawIPC(void* rawHandle, std::string const& buffer) { - nlohmann::json reply; - try { - // 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; - } - 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 05cf8f01..00000000 --- a/loader/src/internal/InternalLoader.hpp +++ /dev/null @@ -1,78 +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::vector<std::pair<Hook*, Mod*>> m_internalHooks; - bool m_readyToHook; - - void downloadLoaderResources(); - - bool loadHooks(); - void setupIPC(); - - InternalLoader(); - ~InternalLoader(); - - friend class Loader; - -public: - static InternalLoader* get(); - - bool setup(); - - static nlohmann::json processRawIPC(void* rawHandle, std::string const& buffer); - - 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 a37eecf6..4add508f 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..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,30 +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) - return ghc::filesystem::path( - ghc::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() { 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 96a011e9..dafb4e80 100644 --- a/loader/src/loader/IPC.cpp +++ b/loader/src/loader/IPC.cpp @@ -1,5 +1,4 @@ #include <Geode/loader/IPC.hpp> -#include <InternalLoader.hpp> USE_GEODE_NAMESPACE(); @@ -25,8 +24,5 @@ ListenerResult IPCFilter::handle(std::function<Callback> fn, IPCEvent* event) { 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 63f7254f..5d3e783c 100644 --- a/loader/src/loader/Loader.cpp +++ b/loader/src/loader/Loader.cpp @@ -1,395 +1,126 @@ +#include "LoaderImpl.hpp" -#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> +Loader::Loader() : m_impl(new Impl) {} -USE_GEODE_NAMESPACE(); +Loader::~Loader() {} Loader* Loader::get() { - return InternalLoader::get(); + static auto g_geode = new Loader; + return g_geode; } -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()); - } - 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.id)) { - return Err(fmt::format("Mod with ID '{}' already loaded", info.id)); - } - - // create Mod instance - auto mod = new Mod(info); - 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]() { - auto searchPath = dirs::getModRuntimeDir() / mod->getID() / "resources"; - - CCFileUtils::get()->addSearchPath(searchPath.string().c_str()); - this->updateModResources(mod); - }); - - // this loads the mod if its dependencies are resolved - GEODE_UNWRAP(mod->updateDependencies()); - - return Ok(mod); -} - -Result<Mod*> Loader::loadModFromFile(ghc::filesystem::path const& file) { - auto res = ModInfo::createFromGeodeFile(file); - if (!res) { - m_invalidMods.push_back(InvalidGeodeFile { - .path = file, - .reason = res.unwrapErr(), - }); - return Err(res.unwrapErr()); - } - return this->loadModFromInfo(res.unwrap()); -} - -bool Loader::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); + return m_impl->createDirectories(); } void Loader::updateModResources(Mod* mod) { - if (!mod->m_info.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.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.id, sheet - ); - } - else { - CCTextureCache::get()->addImage(png.c_str(), false); - CCSpriteFrameCache::get()->addSpriteFramesWithFile(plist.c_str()); - } - } + return m_impl->updateModResources(mod); } -// Dependencies and refreshing +void Loader::addSearchPaths() { + return m_impl->addSearchPaths(); +} -void 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) { - this->loadModsFromDirectory(entry.path(), true); - continue; - } +void Loader::dispatchScheduledFunctions(Mod* mod) { + return m_impl->dispatchScheduledFunctions(mod); +} - // skip this entry if it's not a file - if (!ghc::filesystem::is_regular_file(entry)) { - continue; - } +Result<Mod*> Loader::loadModFromInfo(ModInfo const& info) { + return m_impl->loadModFromInfo(info); +} - // 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.path == entry.path(); - })) { - continue; - } +Result<> Loader::saveData() { + return m_impl->saveData(); +} - // if mods should be loaded immediately, do that - if (m_earlyLoadFinished) { - auto load = this->loadModFromFile(entry); - if (!load) { - log::error("Unable to load {}: {}", entry, load.unwrapErr()); - } - } - // otherwise collect mods to load first to make sure the correct - // versions of the mods are loaded and that early-loaded mods are - // loaded early - else { - auto res = ModInfo::createFromGeodeFile(entry.path()); - if (!res) { - m_invalidMods.push_back(InvalidGeodeFile { - .path = entry.path(), - .reason = res.unwrapErr(), - }); - continue; - } - auto info = res.unwrap(); +Result<> Loader::loadData() { + return m_impl->loadData(); +} - // skip this entry if it's already set to be loaded - if (ranges::contains(m_modsToLoad, info)) { - continue; - } +VersionInfo Loader::getVersion() { + return m_impl->getVersion(); +} - // add to list of mods to load - m_modsToLoad.push_back(info); - } - } +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); +} + +void Loader::loadModsFromDirectory(ghc::filesystem::path const& dir, bool recursive) { + return m_impl->loadModsFromDirectory(dir, recursive); } void Loader::refreshModsList() { - log::debug("Loading mods..."); + return m_impl->refreshModsList(); +} - // find mods - for (auto& dir : m_modSearchDirectories) { - this->loadModsFromDirectory(dir); - } - - // load early-load mods first - for (auto& mod : m_modsToLoad) { - if (mod.needsEarlyLoad) { - auto load = this->loadModFromInfo(mod); - if (!load) { - log::error("Unable to load {}: {}", mod.id, load.unwrapErr()); - } - } - } +bool Loader::isModInstalled(std::string const& id) const { + return m_impl->isModInstalled(id); +} - // UI can be loaded now - m_earlyLoadFinished = true; +Mod* Loader::getInstalledMod(std::string const& id) const { + return m_impl->getInstalledMod(id); +} - // load the rest of the mods - for (auto& mod : m_modsToLoad) { - if (!mod.needsEarlyLoad) { - auto load = this->loadModFromInfo(mod); - if (!load) { - log::error("Unable to load {}: {}", mod.id, load.unwrapErr()); - } - } - } - m_modsToLoad.clear(); +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() { - for (auto const& [_, mod] : m_mods) { - (void)mod->updateDependencies(); - } + 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() { - while (!m_earlyLoadFinished) {} -} - -// Misc - -void Loader::queueInGDThread(ScheduledFunction func) { - InternalLoader::get()->queueInGDThread(func); -} - -bool Loader::didLastLaunchCrash() const { - return crashlog::didLastLaunchCrash(); + return m_impl->waitForModsToBeLoaded(); } void Loader::openPlatformConsole() { - InternalLoader::get()->openPlatformConsole(); + return m_impl->openPlatformConsole(); } -void Loader::closePlatfromConsole() { - InternalLoader::get()->closePlatformConsole(); +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..e33cbb43 --- /dev/null +++ b/loader/src/loader/LoaderImpl.cpp @@ -0,0 +1,596 @@ + +#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() {} + +// 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::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()); + } + 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.id)) { + return Err(fmt::format("Mod with ID '{}' already loaded", info.id)); + } + + // create Mod instance + auto mod = new Mod(info); + 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]() { + auto searchPath = dirs::getModRuntimeDir() / mod->getID() / "resources"; + + CCFileUtils::get()->addSearchPath(searchPath.string().c_str()); + 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 { + .path = file, + .reason = res.unwrapErr(), + }); + return Err(res.unwrapErr()); + } + return this->loadModFromInfo(res.unwrap()); +} + +bool Loader::Impl::isModInstalled(std::string const& id) const { + return m_mods.count(id) && !m_mods.at(id)->isUninstalled(); +} + +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.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.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.id, sheet + ); + } + else { + CCTextureCache::get()->addImage(png.c_str(), false); + CCSpriteFrameCache::get()->addSpriteFramesWithFile(plist.c_str()); + } + } +} + +// Dependencies and refreshing + +void Loader::Impl::loadModsFromDirectory( + ghc::filesystem::path const& dir, + bool recursive +) { + log::debug("Searching {}", dir); + for (auto const& entry : ghc::filesystem::directory_iterator(dir)) { + // recursively search directories + if (ghc::filesystem::is_directory(entry) && recursive) { + this->loadModsFromDirectory(entry.path(), true); + continue; + } + + // 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.path == entry.path(); + })) { + continue; + } + + // if mods should be loaded immediately, do that + if (m_earlyLoadFinished) { + auto load = this->loadModFromFile(entry); + if (!load) { + log::error("Unable to load {}: {}", entry, load.unwrapErr()); + } + } + // otherwise collect mods to load first to make sure the correct + // versions of the mods are loaded and that early-loaded mods are + // loaded early + else { + auto res = ModInfo::createFromGeodeFile(entry.path()); + if (!res) { + m_invalidMods.push_back(InvalidGeodeFile { + .path = entry.path(), + .reason = res.unwrapErr(), + }); + continue; + } + auto info = res.unwrap(); + + // skip this entry if it's already set to be loaded + if (ranges::contains(m_modsToLoad, info)) { + continue; + } + + // add to list of mods to load + m_modsToLoad.push_back(info); + } + } +} + +void Loader::Impl::refreshModsList() { + log::debug("Loading mods..."); + + // find mods + for (auto& dir : m_modSearchDirectories) { + this->loadModsFromDirectory(dir); + } + + // load early-load mods first + for (auto& mod : m_modsToLoad) { + if (mod.needsEarlyLoad) { + auto load = this->loadModFromInfo(mod); + if (!load) { + log::error("Unable to load {}: {}", mod.id, load.unwrapErr()); + } + } + } + + // UI can be loaded now + m_earlyLoadFinished = true; + + // load the rest of the mods + for (auto& mod : m_modsToLoad) { + if (!mod.needsEarlyLoad) { + auto load = this->loadModFromInfo(mod); + if (!load) { + log::error("Unable to load {}: {}", mod.id, load.unwrapErr()); + } + } + } + m_modsToLoad.clear(); +} + +void Loader::Impl::updateAllDependencies() { + for (auto const& [_, mod] : m_mods) { + (void)mod->updateDependencies(); + } +} + +void Loader::Impl::waitForModsToBeLoaded() { + while (!m_earlyLoadFinished) {} +} + +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; +} + +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( + UpdateFailed("Unable to unzip new resources: " + unzip.unwrapErr()) + ).post(); + } + ResourceDownloadEvent(UpdateFinished()).post(); + }) + .expect([](std::string const& info) { + 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(); + }); +} + +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; +} + +nlohmann::json Loader::Impl::processRawIPC(void* rawHandle, std::string const& buffer) { + nlohmann::json reply; + try { + // 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; + } + 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() {} \ No newline at end of file diff --git a/loader/src/loader/LoaderImpl.hpp b/loader/src/loader/LoaderImpl.hpp new file mode 100644 index 00000000..c369368e --- /dev/null +++ b/loader/src/loader/LoaderImpl.hpp @@ -0,0 +1,143 @@ +#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); + 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; + Mod* getLoadedMod(std::string const& id) const; + std::vector<Mod*> getAllMods(); + Mod* getInternalMod(); + void updateAllDependencies(); + std::vector<InvalidGeodeFile> getFailedMods() const; + + void updateResources(); + + void scheduleOnModLoad(Mod* mod, ScheduledFunction func); + void waitForModsToBeLoaded(); + + bool didLastLaunchCrash() const; + + nlohmann::json 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(); + 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 330c7d23..56c129c9 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> @@ -417,7 +418,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; @@ -425,7 +426,7 @@ Result<Hook*> Mod::addHook(Hook* hook) { } } else { - InternalLoader::get()->addInternalHook(hook, this); + LoaderImpl::get()->addInternalHook(hook, this); } return Ok(hook); @@ -486,7 +487,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.id; if (!file::createDirectoryAll(tempPath)) { diff --git a/loader/src/loader/ModInfo.cpp b/loader/src/loader/ModInfo.cpp index 0fec58c1..3dad5578 100644 --- a/loader/src/loader/ModInfo.cpp +++ b/loader/src/loader/ModInfo.cpp @@ -133,19 +133,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." ); @@ -195,8 +195,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 { @@ -213,10 +212,7 @@ Result<ModInfo> ModInfo::createFromGeodeZip(file::Unzip& unzip) { auto info = res.unwrap(); info.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); } @@ -225,9 +221,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())); } } diff --git a/loader/src/main.cpp b/loader/src/main.cpp index 210dd917..9694f989 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 85% rename from loader/src/platform/windows/InternalLoader.cpp rename to loader/src/platform/windows/LoaderImpl.cpp index dbb691f1..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); @@ -43,11 +43,13 @@ void ipcPipeThread(HANDLE pipe) { char buffer[IPC_BUFFER_SIZE * sizeof(TCHAR)]; DWORD read; + std::optional<std::string> replyID = std::nullopt; + // log::debug("Waiting for I/O"); if (ReadFile(pipe, buffer, sizeof(buffer) - 1, &read, nullptr)) { buffer[read] = '\0'; - auto reply = InternalLoader::processRawIPC((void*)pipe, buffer).dump(); + std::string reply = LoaderImpl::get()->processRawIPC((void*)pipe, buffer); DWORD written; WriteFile(pipe, reply.c_str(), reply.size(), &written, nullptr); @@ -61,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 6aefeb64..bf24db94 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() { @@ -38,9 +39,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(); } } @@ -71,13 +74,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"); @@ -101,7 +102,7 @@ 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 + 1, diff --git a/loader/src/ui/internal/info/ModInfoPopup.cpp b/loader/src/ui/internal/info/ModInfoPopup.cpp index 047d5609..8f02f941 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> @@ -12,15 +11,17 @@ #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/GeodeUI.hpp> #include <Geode/ui/IconButtonSprite.hpp> #include <Geode/ui/GeodeUI.hpp> #include <Geode/ui/MDPopup.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 +29,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, ModListLayer* list) { m_noElasticity = true; @@ -36,11 +37,11 @@ bool ModInfoPopup::init(ModInfo const& info, ModListLayer* 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); @@ -57,33 +58,27 @@ bool ModInfoPopup::init(ModInfo const& info, ModListLayer* list) { 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.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( @@ -96,7 +91,7 @@ bool ModInfoPopup::init(ModInfo const& info, ModListLayer* list) { 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); @@ -123,27 +118,33 @@ bool ModInfoPopup::init(ModInfo const& info, ModListLayer* list) { if (info.changelog) { m_changelogArea = MDTextArea::create(info.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 +154,38 @@ bool ModInfoPopup::init(ModInfo const& info, ModListLayer* 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.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.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); @@ -233,8 +221,11 @@ void ModInfoPopup::onInfo(CCObject*) { info.developer, info.path.string() ), - "OK", nullptr, 400.f - )->show(); + "OK", + nullptr, + 400.f + ) + ->show(); } void ModInfoPopup::onChangelog(CCObject* sender) { @@ -245,18 +236,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 ); } @@ -287,24 +276,17 @@ void ModInfoPopup::setInstallStatus(std::optional<UpdateProgress> const& progres bool LocalModInfoPopup::init(Mod* mod, ModListLayer* 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 @@ -317,31 +299,23 @@ bool LocalModInfoPopup::init(Mod* mod, ModListLayer* 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()); @@ -349,8 +323,8 @@ bool LocalModInfoPopup::init(Mod* mod, ModListLayer* 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()) { @@ -372,21 +346,17 @@ bool LocalModInfoPopup::init(Mod* mod, ModListLayer* 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); @@ -395,15 +365,13 @@ bool LocalModInfoPopup::init(Mod* mod, ModListLayer* list) { "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().issues) { auto issuesBtnSpr = ButtonSprite::create( @@ -435,12 +403,11 @@ 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(); @@ -462,19 +429,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_layer) m_layer->updateAllStates(nullptr); @@ -486,11 +447,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()); } @@ -514,14 +471,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_layer) m_layer->reloadList(); @@ -533,18 +486,19 @@ void LocalModInfoPopup::FLAlert_Clicked(FLAlertLayer* layer, bool btn2) { void LocalModInfoPopup::doUninstall() { 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(); @@ -574,12 +528,13 @@ bool IndexItemInfoPopup::init(IndexItemHandle item, ModListLayer* 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); @@ -590,13 +545,10 @@ bool IndexItemInfoPopup::init(IndexItemHandle item, ModListLayer* list) { 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; } diff --git a/loader/src/ui/internal/list/ModListCell.cpp b/loader/src/ui/internal/list/ModListCell.cpp index 838a022a..85814836 100644 --- a/loader/src/ui/internal/list/ModListCell.cpp +++ b/loader/src/ui/internal/list/ModListCell.cpp @@ -1,13 +1,14 @@ #include "ModListCell.hpp" #include "ModListLayer.hpp" #include "../info/ModInfoPopup.hpp" -#include <Geode/binding/StatsCell.hpp> -#include <Geode/binding/FLAlertLayer.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" // how should i include this src/loader/LoaderImpl.hpp #include "../info/TagNode.hpp" template <class T> @@ -93,11 +94,11 @@ void ModListCell::setupInfo(ModInfo const& info, bool spaceForTags) { m_menu->addChild(creatorBtn); 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 (spaceForTags) { descBG->setPositionY(m_height / 2 - 7.5f); @@ -211,20 +212,15 @@ bool ModCell::init( 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); } @@ -232,23 +228,22 @@ bool ModCell::init( 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(); @@ -300,9 +295,7 @@ bool IndexItemCell::init( ); 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 (item->tags.size()) { @@ -370,7 +363,8 @@ void InvalidGeodeFileCell::FLAlert_Clicked(FLAlertLayer*, bool btn2) { "Unable to remove <cy>" + m_info.path.string() + "</c>: <cr>" + std::string(e.what()) + "</c>", "OK" - )->show(); + ) + ->show(); } Loader::get()->refreshModsList(); m_layer->reloadList(); @@ -392,7 +386,7 @@ bool InvalidGeodeFileCell::init( this->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); this->addChild(titleLabel); @@ -407,14 +401,11 @@ bool InvalidGeodeFileCell::init( pathLabel->setColor({ 255, 255, 0 }); this->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); return true; 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 }); }