diff --git a/loader/CMakeLists.txt b/loader/CMakeLists.txt index e05f6188..c7973156 100644 --- a/loader/CMakeLists.txt +++ b/loader/CMakeLists.txt @@ -60,9 +60,6 @@ file(GLOB SOURCES CONFIGURE_DEPENDS src/hooks/*.cpp src/ids/*.cpp src/internal/*.cpp - src/platform/mac/*.cpp - src/platform/ios/*.cpp - src/platform/android/*.cpp src/loader/*.cpp src/load.cpp src/utils/*.cpp diff --git a/loader/include/Geode/loader/IPC.hpp b/loader/include/Geode/loader/IPC.hpp index eaa2e1f7..2cdd2f61 100644 --- a/loader/include/Geode/loader/IPC.hpp +++ b/loader/include/Geode/loader/IPC.hpp @@ -4,9 +4,9 @@ #include "Loader.hpp" #include -namespace geode { +namespace geode::ipc { #ifdef GEODE_IS_WINDOWS - constexpr char const* IPC_PIPE_NAME = "\\\\.\\pipe\\GeodeIPCPipe"; + constexpr char const* IPC_PIPE_NAME = R"(\\.\pipe\GeodeIPCPipe)"; #endif #ifdef GEODE_IS_MACOS @@ -15,20 +15,20 @@ namespace geode { class IPCFilter; - // IPC (Inter-Process Communication) provides a way for Geode mods to talk - // to other programs on the user's computer. If you have, for example, a - // debugger, or an external modding UI, that application can open up the - // platform-specific pipe and start sending messages to mods. Mods can - // listen for messages using the listenForIPC function, and reply to - // messages the get by using the reply method on the event provided. For - // example, an external application can query what mods are loaded in Geode + // IPC (Inter-Process Communication) provides a way for Geode mods to talk + // to other programs on the user's computer. If you have, for example, a + // debugger, or an external modding UI, that application can open up the + // platform-specific pipe and start sending messages to mods. Mods can + // listen for messages using the listenForIPC function, and reply to + // messages the get by using the reply method on the event provided. For + // example, an external application can query what mods are loaded in Geode // by sending the `list-mods` message to `geode.loader`. class GEODE_DLL IPCEvent : public Event { protected: void* m_rawPipeHandle; bool m_replied = false; - + public: std::string targetModID; std::string messageID; @@ -57,12 +57,12 @@ namespace geode { public: ListenerResult handle(utils::MiniFunction fn, IPCEvent* event); - IPCFilter( + IPCFilter( std::string const& modID, std::string const& messageID ); IPCFilter(IPCFilter const&) = default; }; - std::monostate listenForIPC(std::string const& messageID, matjson::Value(*callback)(IPCEvent*)); + std::monostate listen(std::string const& messageID, matjson::Value(*callback)(IPCEvent*)); } diff --git a/loader/include/Geode/loader/Loader.hpp b/loader/include/Geode/loader/Loader.hpp index 8d1b09b1..dd960482 100644 --- a/loader/include/Geode/loader/Loader.hpp +++ b/loader/include/Geode/loader/Loader.hpp @@ -85,8 +85,6 @@ namespace geode { void queueInMainThread(ScheduledFunction func); - bool didLastLaunchCrash() const; - friend class LoaderImpl; friend Mod* takeNextLoaderMod(); diff --git a/loader/src/hooks/LoadingLayer.cpp b/loader/src/hooks/LoadingLayer.cpp index 76bca4de..ad90ade0 100644 --- a/loader/src/hooks/LoadingLayer.cpp +++ b/loader/src/hooks/LoadingLayer.cpp @@ -1,10 +1,11 @@ - #include #include #include #include #include #include +#include +#include using namespace geode::prelude; @@ -77,23 +78,23 @@ struct CustomLoadingLayer : Modify { this->setSmallText("Verifying Loader Resources"); // verify loader resources Loader::get()->queueInMainThread([&]() { - if (!LoaderImpl::get()->verifyLoaderResources()) { + if (!updater::verifyLoaderResources()) { log::debug("Downloading Loader Resources"); this->setSmallText("Downloading Loader Resources"); - this->addChild(EventListenerNode::create( + this->addChild(EventListenerNode::create( this, &CustomLoadingLayer::updateResourcesProgress )); } else { log::debug("Loading Loader Resources"); this->setSmallText("Loading Loader Resources"); - LoaderImpl::get()->updateSpecialFiles(); + updater::updateSpecialFiles(); this->continueLoadAssets(); } }); } - void updateResourcesProgress(ResourceDownloadEvent* event) { + void updateResourcesProgress(updater::ResourceDownloadEvent* event) { std::visit(makeVisitor { [&](UpdateProgress const& progress) { this->setSmallText(fmt::format( @@ -107,7 +108,7 @@ struct CustomLoadingLayer : Modify { }, [&](UpdateFailed const& error) { log::debug("Failed Loader Resources"); - LoaderImpl::get()->platformMessageBox( + console::messageBox( "Error updating resources", error + ".\n" "You will have to install resources manually by downloading resources.zip " diff --git a/loader/src/hooks/MenuLayer.cpp b/loader/src/hooks/MenuLayer.cpp index 78f594ce..cfd90c59 100644 --- a/loader/src/hooks/MenuLayer.cpp +++ b/loader/src/hooks/MenuLayer.cpp @@ -1,4 +1,3 @@ - #include "../ui/internal/list/ModListLayer.hpp" #include @@ -7,13 +6,13 @@ #include #include #include -#include #include #include #include #include #include #include +#include using namespace geode::prelude; @@ -129,7 +128,7 @@ struct CustomMenuLayer : Modify { // show auto update message static bool shownUpdateInfo = false; - if (LoaderImpl::get()->isNewUpdateDownloaded() && !shownUpdateInfo) { + if (updater::isNewUpdateDownloaded() && !shownUpdateInfo) { shownUpdateInfo = true; auto popup = FLAlertLayer::create( "Update downloaded", @@ -147,7 +146,7 @@ struct CustomMenuLayer : Modify { // show crash info static bool shownLastCrash = false; if ( - Loader::get()->didLastLaunchCrash() && + crashlog::didLastLaunchCrash() && !shownLastCrash && !Mod::get()->template getSettingValue("disable-last-crashed-popup") ) { diff --git a/loader/src/load.cpp b/loader/src/load.cpp index 0ae9462b..6d921e05 100644 --- a/loader/src/load.cpp +++ b/loader/src/load.cpp @@ -1,4 +1,7 @@ #include +#include +#include +#include #include #include @@ -10,7 +13,7 @@ #include #include - + using namespace geode::prelude; #include "load.hpp" @@ -18,22 +21,22 @@ using namespace geode::prelude; $execute { listenForSettingChanges("show-platform-console", +[](bool value) { if (value) { - LoaderImpl::get()->openPlatformConsole(); + console::open(); } else { - LoaderImpl::get()->closePlatformConsole(); + console::close(); } }); - listenForIPC("ipc-test", [](IPCEvent* event) -> matjson::Value { + ipc::listen("ipc-test", [](ipc::IPCEvent* event) -> matjson::Value { return "Hello from Geode!"; }); - listenForIPC("loader-info", [](IPCEvent* event) -> matjson::Value { + ipc::listen("loader-info", [](ipc::IPCEvent* event) -> matjson::Value { return Mod::get()->getMetadata(); }); - listenForIPC("list-mods", [](IPCEvent* event) -> matjson::Value { + ipc::listen("list-mods", [](ipc::IPCEvent* event) -> matjson::Value { std::vector res; auto args = *event->messageData; @@ -76,7 +79,7 @@ void tryShowForwardCompat() { return; // TODO: change text later - LoaderImpl::get()->platformMessageBox( + console::messageBox( "Forward Compatibility Warning", "Geode is running in a newer version of GD than Geode targets.\n" "UI is going to be disabled, platform console is forced on and crashes can be more common.\n" @@ -116,7 +119,7 @@ int geodeEntry(void* platformData) { auto internalSetupRes = LoaderImpl::get()->setupInternalMod(); log::popNest(); if (!internalSetupRes) { - LoaderImpl::get()->platformMessageBox( + console::messageBox( "Unable to Load Geode!", "There was a fatal error setting up " "the internal mod and Geode can not be loaded: " + internalSetupRes.unwrapErr() @@ -131,7 +134,7 @@ int geodeEntry(void* platformData) { if (LoaderImpl::get()->isForwardCompatMode() || Mod::get()->getSettingValue("show-platform-console")) { log::debug("Opening console"); - LoaderImpl::get()->openPlatformConsole(); + console::open(); } // set up loader, load mods, etc. @@ -140,7 +143,7 @@ int geodeEntry(void* platformData) { auto setupRes = LoaderImpl::get()->setup(); log::popNest(); if (!setupRes) { - LoaderImpl::get()->platformMessageBox( + console::messageBox( "Unable to Load Geode!", "There was an unknown fatal error setting up " "the loader and Geode can not be loaded. " @@ -152,12 +155,15 @@ int geodeEntry(void* platformData) { crashlog::setupPlatformHandlerPost(); - log::info("Set up loader"); + log::debug("Setting up IPC"); + log::pushNest(); + ipc::setup(); + log::popNest(); // download and install new loader update in the background if (Mod::get()->getSettingValue("auto-check-updates")) { log::info("Starting loader update check"); - LoaderImpl::get()->checkForLoaderUpdates(); + updater::checkForLoaderUpdates(); } else { log::info("Skipped loader update check"); diff --git a/loader/src/loader/IPC.cpp b/loader/src/loader/IPC.cpp index 40defb17..79a3220c 100644 --- a/loader/src/loader/IPC.cpp +++ b/loader/src/loader/IPC.cpp @@ -1,16 +1,17 @@ #include +#include "IPC.hpp" #include using namespace geode::prelude; -std::monostate geode::listenForIPC(std::string const& messageID, matjson::Value(*callback)(IPCEvent*)) { +std::monostate ipc::listen(std::string const& messageID, matjson::Value(*callback)(IPCEvent*)) { (void) new EventListener( callback, IPCFilter(getMod()->getID(), messageID) ); return std::monostate(); } -IPCEvent::IPCEvent( +ipc::IPCEvent::IPCEvent( void* rawPipeHandle, std::string const& targetModID, std::string const& messageID, @@ -22,9 +23,9 @@ IPCEvent::IPCEvent( replyData(replyData), messageData(std::make_unique(messageData)) {} -IPCEvent::~IPCEvent() {} +ipc::IPCEvent::~IPCEvent() {} -ListenerResult IPCFilter::handle(utils::MiniFunction fn, IPCEvent* event) { +ListenerResult ipc::IPCFilter::handle(utils::MiniFunction fn, IPCEvent* event) { if (event->targetModID == m_modID && event->messageID == m_messageID) { event->replyData = fn(event); return ListenerResult::Stop; @@ -32,5 +33,34 @@ ListenerResult IPCFilter::handle(utils::MiniFunction fn, IPCEvent* eve return ListenerResult::Propagate; } -IPCFilter::IPCFilter(std::string const& modID, std::string const& messageID) : +ipc::IPCFilter::IPCFilter(std::string const& modID, std::string const& messageID) : m_modID(modID), m_messageID(messageID) {} + +matjson::Value ipc::processRaw(void* rawHandle, std::string const& buffer) { + matjson::Value reply; + + matjson::Value json; + try { + json = matjson::parse(buffer); + } catch (...) { + log::warn("Received IPC message that isn't valid JSON"); + return reply; + } + + 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; + } + matjson::Value 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"].as_string(), json["message"].as_string(), data, reply).post(); + return reply; +} diff --git a/loader/src/loader/IPC.hpp b/loader/src/loader/IPC.hpp new file mode 100644 index 00000000..35fbc81f --- /dev/null +++ b/loader/src/loader/IPC.hpp @@ -0,0 +1,9 @@ +#pragma once + +#include +#include + +namespace geode::ipc { + void setup(); + matjson::Value processRaw(void* rawHandle, std::string const& buffer); +} diff --git a/loader/src/loader/Loader.cpp b/loader/src/loader/Loader.cpp index 3f95e839..086d30c9 100644 --- a/loader/src/loader/Loader.cpp +++ b/loader/src/loader/Loader.cpp @@ -73,10 +73,6 @@ void Loader::queueInMainThread(ScheduledFunction func) { return m_impl->queueInMainThread(std::move(func)); } -bool Loader::didLastLaunchCrash() const { - return m_impl->didLastLaunchCrash(); -} - Mod* Loader::takeNextMod() { return m_impl->takeNextMod(); } diff --git a/loader/src/loader/LoaderImpl.cpp b/loader/src/loader/LoaderImpl.cpp index cbba3531..107e150b 100644 --- a/loader/src/loader/LoaderImpl.cpp +++ b/loader/src/loader/LoaderImpl.cpp @@ -4,6 +4,7 @@ #include "ModImpl.hpp" #include "ModMetadataImpl.hpp" #include "LogImpl.hpp" +#include "console.hpp" #include #include @@ -82,11 +83,6 @@ Result<> Loader::Impl::setup() { } log::popNest(); - log::debug("Setting up IPC"); - log::pushNest(); - this->setupIPC(); - log::popNest(); - log::debug("Setting up directories"); log::pushNest(); this->createDirectories(); @@ -663,12 +659,8 @@ std::vector Loader::Impl::getProblems() const { return m_problems; } -bool Loader::Impl::didLastLaunchCrash() const { - return crashlog::didLastLaunchCrash(); -} - void Loader::Impl::forceReset() { - this->closePlatformConsole(); + console::close(); for (auto& [_, mod] : m_mods) { delete mod; } @@ -719,356 +711,6 @@ void Loader::Impl::executeGDThreadQueue() { } } -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; -} - -void Loader::Impl::fetchLatestGithubRelease( - utils::MiniFunction then, - utils::MiniFunction expect -) { - if (m_latestGithubRelease) { - return then(m_latestGithubRelease.value()); - } - // TODO: add header to not get rate limited - web::AsyncWebRequest() - .join("loader-auto-update-check") - .userAgent("github_api/1.0") - .fetch("https://api.github.com/repos/geode-sdk/geode/releases/latest") - .json() - .then([this, then](matjson::Value const& json) { - m_latestGithubRelease = json; - then(json); - }) - .expect(expect); -} - -void Loader::Impl::tryDownloadLoaderResources( - std::string const& url, - bool tryLatestOnError -) { - auto tempResourcesZip = dirs::getTempDir() / "new.zip"; - auto resourcesDir = dirs::getGeodeResourcesDir() / Mod::get()->getID(); - - web::AsyncWebRequest() - // use the url as a join handle - .join(url) - .fetch(url) - .into(tempResourcesZip) - .then([tempResourcesZip, resourcesDir, this](auto) { - // unzip resources zip - auto unzip = file::Unzip::intoDir(tempResourcesZip, resourcesDir, true); - if (!unzip) { - ResourceDownloadEvent( - UpdateFailed("Unable to unzip new resources: " + unzip.unwrapErr()) - ).post(); - return; - } - this->updateSpecialFiles(); - - ResourceDownloadEvent(UpdateFinished()).post(); - }) - .expect([this, tryLatestOnError](std::string const& info, int code) { - // if the url was not found, try downloading latest release instead - // (for development versions) - if (code == 404) { - log::warn("Unable to download resources: {}", info); - } - ResourceDownloadEvent( - UpdateFailed("Unable to download resources: " + info) - ).post(); - }) - .progress([](auto&, double now, double total) { - ResourceDownloadEvent( - UpdateProgress( - static_cast(now / total * 100.0), - "Downloading resources" - ) - ).post(); - }); -} - -void Loader::Impl::updateSpecialFiles() { - auto resourcesDir = dirs::getGeodeResourcesDir() / Mod::get()->getID(); - auto res = ModMetadataImpl::getImpl(ModImpl::get()->m_metadata).addSpecialFiles(resourcesDir); - if (res.isErr()) { - log::warn("Unable to add special files: {}", res.unwrapErr()); - } -} - -void Loader::Impl::downloadLoaderResources(bool useLatestRelease) { - web::AsyncWebRequest() - .join("loader-tag-exists-check") - .userAgent("github_api/1.0") - .fetch(fmt::format( - "https://api.github.com/repos/geode-sdk/geode/git/ref/tags/{}", - this->getVersion().toString() - )) - .json() - .then([this](matjson::Value const& json) { - this->tryDownloadLoaderResources(fmt::format( - "https://github.com/geode-sdk/geode/releases/download/{}/resources.zip", - this->getVersion().toString() - ), true); - }) - .expect([=, this](std::string const& info, int code) { - if (code == 404) { - if (useLatestRelease) { - log::debug("Loader version {} does not exist on Github, downloading latest resources", this->getVersion().toString()); - fetchLatestGithubRelease( - [this](matjson::Value const& raw) { - auto json = raw; - JsonChecker checker(json); - auto root = checker.root("[]").obj(); - - // find release asset - for (auto asset : root.needs("assets").iterate()) { - auto obj = asset.obj(); - if (obj.needs("name").template get() == "resources.zip") { - this->tryDownloadLoaderResources( - obj.needs("browser_download_url").template get(), - false - ); - return; - } - } - - ResourceDownloadEvent( - UpdateFailed("Unable to find resources in latest GitHub release") - ).post(); - }, - [this](std::string const& info) { - ResourceDownloadEvent( - UpdateFailed("Unable to download resources: " + info) - ).post(); - } - ); - return; - } - else { - log::debug("Loader version {} does not exist on Github, not downloading the resources", this->getVersion().toString()); - } - ResourceDownloadEvent( - UpdateFinished() - ).post(); - } - else { - ResourceDownloadEvent( - UpdateFailed("Unable to check if tag exists: " + info) - ).post(); - } - }); -} - -bool Loader::Impl::verifyLoaderResources() { - static std::optional CACHED = std::nullopt; - if (CACHED.has_value()) { - return CACHED.value(); - } - - // geode/resources/geode.loader - auto resourcesDir = dirs::getGeodeResourcesDir() / Mod::get()->getID(); - - // if the resources dir doesn't exist, then it's probably incorrect - if (!( - ghc::filesystem::exists(resourcesDir) && - ghc::filesystem::is_directory(resourcesDir) - )) { - log::debug("Resources directory does not exist"); - this->downloadLoaderResources(true); - return false; - } - - // TODO: actually have a proper way to disable checking resources - // for development builds - if (ghc::filesystem::exists(resourcesDir / "dont-update.txt")) { - // this is kind of a hack, but it's the easiest way to prevent - // auto update while developing - log::debug("Not updating resources since dont-update.txt exists"); - return true; - } - - // 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()); - auto expected = LOADER_RESOURCE_HASHES.at(name); - if (hash != expected) { - log::debug("Resource hash mismatch: {} ({}, {})", name, hash.substr(0, 7), expected.substr(0, 7)); - this->downloadLoaderResources(); - return false; - } - coverage += 1; - } - - // make sure every file was found - if (coverage != LOADER_RESOURCE_HASHES.size()) { - log::debug("Resource coverage mismatch"); - this->downloadLoaderResources(); - return false; - } - - return true; -} - -void Loader::Impl::downloadLoaderUpdate(std::string const& url) { - auto updateZip = dirs::getTempDir() / "loader-update.zip"; - auto targetDir = dirs::getGeodeDir() / "update"; - - web::AsyncWebRequest() - .join("loader-update-download") - .fetch(url) - .into(updateZip) - .then([this, updateZip, targetDir](auto) { - // unzip resources zip - auto unzip = file::Unzip::intoDir(updateZip, targetDir, true); - if (!unzip) { - LoaderUpdateEvent( - UpdateFailed("Unable to unzip update: " + unzip.unwrapErr()) - ).post(); - return; - } - m_isNewUpdateDownloaded = true; - LoaderUpdateEvent(UpdateFinished()).post(); - }) - .expect([](std::string const& info) { - LoaderUpdateEvent( - UpdateFailed("Unable to download update: " + info) - ).post(); - }) - .progress([](auto&, double now, double total) { - LoaderUpdateEvent( - UpdateProgress( - static_cast(now / total * 100.0), - "Downloading update" - ) - ).post(); - }); -} - -void Loader::Impl::checkForLoaderUpdates() { - // Check for updates in the background - fetchLatestGithubRelease( - [this](matjson::Value const& raw) { - auto json = raw; - JsonChecker checker(json); - auto root = checker.root("[]").obj(); - - VersionInfo ver { 0, 0, 0 }; - root.needs("tag_name").into(ver); - - // make sure release is newer - if (ver <= this->getVersion()) { - return; - } - - // don't auto-update major versions - if (ver.getMajor() > this->getVersion().getMajor()) { - return; - } - - // find release asset - for (auto asset : root.needs("assets").iterate()) { - auto obj = asset.obj(); - if (string::endsWith( - obj.needs("name").template get(), - GEODE_PLATFORM_SHORT_IDENTIFIER ".zip" - )) { - this->downloadLoaderUpdate( - obj.needs("browser_download_url").template get() - ); - return; - } - } - - LoaderUpdateEvent( - UpdateFailed("Unable to find release asset for " GEODE_PLATFORM_NAME) - ).post(); - }, - [](std::string const& info) { - LoaderUpdateEvent( - UpdateFailed("Unable to check for updates: " + info) - ).post(); - } - ); -} - -bool Loader::Impl::isNewUpdateDownloaded() const { - return m_isNewUpdateDownloaded; -} - -matjson::Value Loader::Impl::processRawIPC(void* rawHandle, std::string const& buffer) { - matjson::Value reply; - - matjson::Value json; - try { - json = matjson::parse(buffer); - } catch (...) { - log::warn("Received IPC message that isn't valid JSON"); - return reply; - } - - 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; - } - matjson::Value 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"].as_string(), json["message"].as_string(), data, reply).post(); - return reply; -} - -ResourceDownloadEvent::ResourceDownloadEvent( - UpdateStatus const& status -) : status(status) {} - -ListenerResult ResourceDownloadFilter::handle( - utils::MiniFunction fn, - ResourceDownloadEvent* event -) { - fn(event); - return ListenerResult::Propagate; -} - -ResourceDownloadFilter::ResourceDownloadFilter() {} - -LoaderUpdateEvent::LoaderUpdateEvent( - UpdateStatus const& status -) : status(status) {} - -ListenerResult LoaderUpdateFilter::handle( - utils::MiniFunction fn, - LoaderUpdateEvent* event -) { - fn(event); - return ListenerResult::Propagate; -} - -LoaderUpdateFilter::LoaderUpdateFilter() {} - void Loader::Impl::provideNextMod(Mod* mod) { m_nextModLock.lock(); if (mod) { diff --git a/loader/src/loader/LoaderImpl.hpp b/loader/src/loader/LoaderImpl.hpp index 7d77cd3a..0fc19a4d 100644 --- a/loader/src/loader/LoaderImpl.hpp +++ b/loader/src/loader/LoaderImpl.hpp @@ -25,32 +25,6 @@ // TODO: Find a file convention for impl headers namespace geode { - struct ResourceDownloadEvent : public Event { - const UpdateStatus status; - ResourceDownloadEvent(UpdateStatus const& status); - }; - - class ResourceDownloadFilter : public EventFilter { - public: - using Callback = void(ResourceDownloadEvent*); - - ListenerResult handle(utils::MiniFunction fn, ResourceDownloadEvent* event); - ResourceDownloadFilter(); - }; - - struct LoaderUpdateEvent : public Event { - const UpdateStatus status; - LoaderUpdateEvent(UpdateStatus const& status); - }; - - class LoaderUpdateFilter : public EventFilter { - public: - using Callback = void(LoaderUpdateEvent*); - - ListenerResult handle(utils::MiniFunction fn, LoaderUpdateEvent* event); - LoaderUpdateFilter(); - }; - class Loader::Impl { public: mutable std::mutex m_mutex; @@ -66,11 +40,6 @@ namespace geode { std::vector m_texturePaths; bool m_isSetup = false; - // cache for the json of the latest github release to avoid hitting - // the github api too much - std::optional m_latestGithubRelease; - bool m_isNewUpdateDownloaded = false; - LoadingState m_loadingState; std::vector> m_mainThreadQueue; @@ -78,9 +47,6 @@ namespace geode { std::vector> m_uninitializedHooks; bool m_readyToHook = false; - bool m_platformConsoleOpen = false; - void* m_platformData = nullptr; - std::mutex m_nextModMutex; std::unique_lock m_nextModLock = std::unique_lock(m_nextModMutex, std::defer_lock); std::condition_variable m_nextModCV; @@ -109,17 +75,7 @@ namespace geode { Result getHandler(void* address); Result<> removeHandler(void* address); - void updateSpecialFiles(); - void tryDownloadLoaderResources(std::string const& url, bool tryLatestOnError = true); - void downloadLoaderResources(bool useLatestRelease = false); - void downloadLoaderUpdate(std::string const& url); - void fetchLatestGithubRelease( - utils::MiniFunction then, - utils::MiniFunction expect - ); - bool loadHooks(); - void setupIPC(); Impl(); ~Impl(); @@ -157,25 +113,9 @@ namespace geode { void updateResources(bool forceReload); - bool didLastLaunchCrash() const; - - matjson::Value processRawIPC(void* rawHandle, std::string const& buffer); - void queueInMainThread(ScheduledFunction func); void executeGDThreadQueue(); - void logConsoleMessage(std::string const& msg); - void logConsoleMessageWithSeverity(std::string const& msg, Severity severity); - - bool platformConsoleOpen() const; - void openPlatformConsole(); - void closePlatformConsole(); - void platformMessageBox(char const* title, std::string const& info, Severity severity = Severity::Error); - - bool verifyLoaderResources(); - void checkForLoaderUpdates(); - bool isNewUpdateDownloaded() const; - bool isReadyToHook() const; void addUninitializedHook(Hook* hook, Mod* mod); diff --git a/loader/src/loader/Log.cpp b/loader/src/loader/Log.cpp index c4ebddfc..7364a4b8 100644 --- a/loader/src/loader/Log.cpp +++ b/loader/src/loader/Log.cpp @@ -1,4 +1,4 @@ -#include "LoaderImpl.hpp" +#include "console.hpp" #include "LogImpl.hpp" #include @@ -198,7 +198,7 @@ void Logger::push(Severity sev, Mod* mod, std::string&& content) { auto& log = m_logs.emplace_back(sev, mod, std::move(content)); auto const logStr = log.toString(true, m_nestLevel); - LoaderImpl::get()->logConsoleMessageWithSeverity(logStr, log.getSeverity()); + console::log(logStr, log.getSeverity()); m_logStream << logStr << std::endl; } } @@ -233,4 +233,4 @@ void log::pushNest() { void log::popNest() { Logger::get()->popNest(); -} \ No newline at end of file +} diff --git a/loader/src/loader/ModImpl.cpp b/loader/src/loader/ModImpl.cpp index da733a1e..9d5db133 100644 --- a/loader/src/loader/ModImpl.cpp +++ b/loader/src/loader/ModImpl.cpp @@ -2,6 +2,7 @@ #include "LoaderImpl.hpp" #include "ModMetadataImpl.hpp" #include "about.hpp" +#include "console.hpp" #include #include @@ -686,7 +687,7 @@ Mod* Loader::Impl::getInternalMod() { } auto infoRes = getModImplInfo(); if (!infoRes) { - LoaderImpl::get()->platformMessageBox( + console::messageBox( "Fatal Internal Error", "Unable to create internal mod info: \"" + infoRes.unwrapErr() + "\"\n" diff --git a/loader/src/loader/console.hpp b/loader/src/loader/console.hpp new file mode 100644 index 00000000..835251da --- /dev/null +++ b/loader/src/loader/console.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace geode::console { + void open(); + void close(); + void log(std::string const& msg, Severity severity); + void messageBox(char const* title, std::string const& info, Severity severity = Severity::Error); +} diff --git a/loader/src/loader/updater.cpp b/loader/src/loader/updater.cpp new file mode 100644 index 00000000..85480225 --- /dev/null +++ b/loader/src/loader/updater.cpp @@ -0,0 +1,325 @@ +#include "updater.hpp" +#include +#include +#include +#include +#include +#include "LoaderImpl.hpp" +#include "ModMetadataImpl.hpp" + +using namespace geode::prelude; + +updater::ResourceDownloadEvent::ResourceDownloadEvent( + UpdateStatus status +) : status(std::move(status)) {} + +ListenerResult updater::ResourceDownloadFilter::handle( + const utils::MiniFunction& fn, + ResourceDownloadEvent* event +) { + fn(event); + return ListenerResult::Propagate; +} + +updater::ResourceDownloadFilter::ResourceDownloadFilter() = default; + +updater::LoaderUpdateEvent::LoaderUpdateEvent( + UpdateStatus status +) : status(std::move(status)) {} + +ListenerResult updater::LoaderUpdateFilter::handle( + const utils::MiniFunction& fn, + LoaderUpdateEvent* event +) { + fn(event); + return ListenerResult::Propagate; +} + +updater::LoaderUpdateFilter::LoaderUpdateFilter() = default; + +// cache for the json of the latest github release to avoid hitting +// the github api too much +std::optional s_latestGithubRelease; +bool s_isNewUpdateDownloaded = false; + +void updater::fetchLatestGithubRelease( + const utils::MiniFunction& then, + utils::MiniFunction expect +) { + if (s_latestGithubRelease) { + return then(s_latestGithubRelease.value()); + } + // TODO: add header to not get rate limited + web::AsyncWebRequest() + .join("loader-auto-update-check") + .userAgent("github_api/1.0") + .fetch("https://api.github.com/repos/geode-sdk/geode/releases/latest") + .json() + .then([then](matjson::Value const& json) { + s_latestGithubRelease = json; + then(json); + }) + .expect(std::move(expect)); +} + +void updater::tryDownloadLoaderResources( + std::string const& url, + bool tryLatestOnError +) { + auto tempResourcesZip = dirs::getTempDir() / "new.zip"; + auto resourcesDir = dirs::getGeodeResourcesDir() / Mod::get()->getID(); + + web::AsyncWebRequest() + // use the url as a join handle + .join(url) + .fetch(url) + .into(tempResourcesZip) + .then([tempResourcesZip, resourcesDir](auto) { + // unzip resources zip + auto unzip = file::Unzip::intoDir(tempResourcesZip, resourcesDir, true); + if (!unzip) { + ResourceDownloadEvent( + UpdateFailed("Unable to unzip new resources: " + unzip.unwrapErr()) + ).post(); + return; + } + updater::updateSpecialFiles(); + + ResourceDownloadEvent(UpdateFinished()).post(); + }) + .expect([tryLatestOnError](std::string const& info, int code) { + // if the url was not found, try downloading latest release instead + // (for development versions) + if (code == 404) { + log::warn("Unable to download resources: {}", info); + } + ResourceDownloadEvent( + UpdateFailed("Unable to download resources: " + info) + ).post(); + }) + .progress([](auto&, double now, double total) { + ResourceDownloadEvent( + UpdateProgress( + static_cast(now / total * 100.0), + "Downloading resources" + ) + ).post(); + }); +} + +void updater::updateSpecialFiles() { + auto resourcesDir = dirs::getGeodeResourcesDir() / Mod::get()->getID(); + auto res = ModMetadataImpl::getImpl(ModImpl::get()->m_metadata).addSpecialFiles(resourcesDir); + if (res.isErr()) { + log::warn("Unable to add special files: {}", res.unwrapErr()); + } +} + +void updater::downloadLoaderResources(bool useLatestRelease) { + web::AsyncWebRequest() + .join("loader-tag-exists-check") + .userAgent("github_api/1.0") + .fetch(fmt::format( + "https://api.github.com/repos/geode-sdk/geode/git/ref/tags/{}", + Loader::get()->getVersion().toString() + )) + .json() + .then([](matjson::Value const& json) { + updater::tryDownloadLoaderResources(fmt::format( + "https://github.com/geode-sdk/geode/releases/download/{}/resources.zip", + Loader::get()->getVersion().toString() + ), true); + }) + .expect([=](std::string const& info, int code) { + if (code == 404) { + if (useLatestRelease) { + log::debug("Loader version {} does not exist on Github, downloading latest resources", Loader::get()->getVersion().toString()); + fetchLatestGithubRelease( + [](matjson::Value const& raw) { + auto json = raw; + JsonChecker checker(json); + auto root = checker.root("[]").obj(); + + // find release asset + for (auto asset : root.needs("assets").iterate()) { + auto obj = asset.obj(); + if (obj.needs("name").template get() == "resources.zip") { + updater::tryDownloadLoaderResources( + obj.needs("browser_download_url").template get(), + false + ); + return; + } + } + + ResourceDownloadEvent( + UpdateFailed("Unable to find resources in latest GitHub release") + ).post(); + }, + [](std::string const& info) { + ResourceDownloadEvent( + UpdateFailed("Unable to download resources: " + info) + ).post(); + } + ); + return; + } + else { + log::debug("Loader version {} does not exist on GitHub, not downloading the resources", Loader::get()->getVersion().toString()); + } + ResourceDownloadEvent( + UpdateFinished() + ).post(); + } + else { + ResourceDownloadEvent( + UpdateFailed("Unable to check if tag exists: " + info) + ).post(); + } + }); +} + +bool updater::verifyLoaderResources() { + static std::optional CACHED = std::nullopt; + if (CACHED.has_value()) { + return CACHED.value(); + } + + // geode/resources/geode.loader + auto resourcesDir = dirs::getGeodeResourcesDir() / Mod::get()->getID(); + + // if the resources dir doesn't exist, then it's probably incorrect + if (!( + ghc::filesystem::exists(resourcesDir) && + ghc::filesystem::is_directory(resourcesDir) + )) { + log::debug("Resources directory does not exist"); + updater::downloadLoaderResources(true); + return false; + } + + // TODO: actually have a proper way to disable checking resources + // for development builds + if (ghc::filesystem::exists(resourcesDir / "dont-update.txt")) { + // this is kind of a hack, but it's the easiest way to prevent + // auto update while developing + log::debug("Not updating resources since dont-update.txt exists"); + return true; + } + + // 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()); + const auto& expected = LOADER_RESOURCE_HASHES.at(name); + if (hash != expected) { + log::debug("Resource hash mismatch: {} ({}, {})", name, hash.substr(0, 7), expected.substr(0, 7)); + updater::downloadLoaderResources(); + return false; + } + coverage += 1; + } + + // make sure every file was found + if (coverage != LOADER_RESOURCE_HASHES.size()) { + log::debug("Resource coverage mismatch"); + updater::downloadLoaderResources(); + return false; + } + + return true; +} + +void updater::downloadLoaderUpdate(std::string const& url) { + auto updateZip = dirs::getTempDir() / "loader-update.zip"; + auto targetDir = dirs::getGeodeDir() / "update"; + + web::AsyncWebRequest() + .join("loader-update-download") + .fetch(url) + .into(updateZip) + .then([updateZip, targetDir](auto) { + // unzip resources zip + auto unzip = file::Unzip::intoDir(updateZip, targetDir, true); + if (!unzip) { + LoaderUpdateEvent( + UpdateFailed("Unable to unzip update: " + unzip.unwrapErr()) + ).post(); + return; + } + s_isNewUpdateDownloaded = true; + LoaderUpdateEvent(UpdateFinished()).post(); + }) + .expect([](std::string const& info) { + LoaderUpdateEvent( + UpdateFailed("Unable to download update: " + info) + ).post(); + }) + .progress([](auto&, double now, double total) { + LoaderUpdateEvent( + UpdateProgress( + static_cast(now / total * 100.0), + "Downloading update" + ) + ).post(); + }); +} + +void updater::checkForLoaderUpdates() { + // Check for updates in the background + fetchLatestGithubRelease( + [](matjson::Value const& raw) { + auto json = raw; + JsonChecker checker(json); + auto root = checker.root("[]").obj(); + + VersionInfo ver { 0, 0, 0 }; + root.needs("tag_name").into(ver); + + // make sure release is newer + if (ver <= Loader::get()->getVersion()) { + return; + } + + // don't auto-update major versions + if (ver.getMajor() > Loader::get()->getVersion().getMajor()) { + return; + } + + // find release asset + for (auto asset : root.needs("assets").iterate()) { + auto obj = asset.obj(); + if (string::endsWith( + obj.needs("name").template get(), + GEODE_PLATFORM_SHORT_IDENTIFIER ".zip" + )) { + updater::downloadLoaderUpdate( + obj.needs("browser_download_url").template get() + ); + return; + } + } + + LoaderUpdateEvent( + UpdateFailed("Unable to find release asset for " GEODE_PLATFORM_NAME) + ).post(); + }, + [](std::string const& info) { + LoaderUpdateEvent( + UpdateFailed("Unable to check for updates: " + info) + ).post(); + } + ); +} + +bool updater::isNewUpdateDownloaded() { + return s_isNewUpdateDownloaded; +} diff --git a/loader/src/loader/updater.hpp b/loader/src/loader/updater.hpp new file mode 100644 index 00000000..2023b604 --- /dev/null +++ b/loader/src/loader/updater.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include + +namespace geode::updater { + struct ResourceDownloadEvent : public Event { + const UpdateStatus status; + explicit ResourceDownloadEvent(UpdateStatus status); + }; + + class ResourceDownloadFilter : public EventFilter { + public: + using Callback = void(ResourceDownloadEvent*); + + static ListenerResult handle(const utils::MiniFunction& fn, ResourceDownloadEvent* event); + ResourceDownloadFilter(); + }; + + struct LoaderUpdateEvent : public Event { + const UpdateStatus status; + explicit LoaderUpdateEvent(UpdateStatus status); + }; + + class LoaderUpdateFilter : public EventFilter { + public: + using Callback = void(LoaderUpdateEvent*); + + static ListenerResult handle(const utils::MiniFunction& fn, LoaderUpdateEvent* event); + LoaderUpdateFilter(); + }; + + void updateSpecialFiles(); + void tryDownloadLoaderResources(std::string const& url, bool tryLatestOnError = true); + void downloadLoaderResources(bool useLatestRelease = false); + void downloadLoaderUpdate(std::string const& url); + void fetchLatestGithubRelease( + const utils::MiniFunction& then, + utils::MiniFunction expect + ); + + bool verifyLoaderResources(); + void checkForLoaderUpdates(); + bool isNewUpdateDownloaded(); +} diff --git a/loader/src/platform/android/FileWatcher.cpp b/loader/src/platform/android/FileWatcher.cpp index 550c938c..fd210965 100644 --- a/loader/src/platform/android/FileWatcher.cpp +++ b/loader/src/platform/android/FileWatcher.cpp @@ -1,7 +1,5 @@ #include -#ifdef GEODE_IS_ANDROID - FileWatcher::FileWatcher( ghc::filesystem::path const& file, FileWatchCallback callback, ErrorCallback error ) { @@ -23,5 +21,3 @@ void FileWatcher::watch() { bool FileWatcher::watching() const { return false; } - -#endif diff --git a/loader/src/platform/android/IPC.cpp b/loader/src/platform/android/IPC.cpp new file mode 100644 index 00000000..82b7ef92 --- /dev/null +++ b/loader/src/platform/android/IPC.cpp @@ -0,0 +1,7 @@ +#include + +using namespace geode::prelude; + +void ipc::setup() { + log::debug("IPC is not supported on this platform!"); +} diff --git a/loader/src/platform/android/LoaderImpl.cpp b/loader/src/platform/android/LoaderImpl.cpp index 91e8bd56..c4b08e84 100644 --- a/loader/src/platform/android/LoaderImpl.cpp +++ b/loader/src/platform/android/LoaderImpl.cpp @@ -1,29 +1,9 @@ #include -#include -#include -#include #include -#include +#include using namespace geode::prelude; -#ifdef GEODE_IS_ANDROID - -#include -#include - -namespace { - android_LogPriority getLogSeverityForSeverity(Severity severity) { - switch (severity) { - case Severity::Debug: return ANDROID_LOG_DEBUG; - case Severity::Info: return ANDROID_LOG_INFO; - case Severity::Warning: return ANDROID_LOG_WARN; - case Severity::Error: return ANDROID_LOG_ERROR; - default: return ANDROID_LOG_DEFAULT; - } - } -} - std::string Loader::Impl::getGameVersion() { if (m_gdVersion.empty()) { /* @@ -49,33 +29,6 @@ std::string Loader::Impl::getGameVersion() { return m_gdVersion; } -void Loader::Impl::platformMessageBox(char const* title, std::string const& info, Severity severity) { - cocos2d::CCMessageBox(info.c_str(), title); -} - -void Loader::Impl::logConsoleMessageWithSeverity(std::string const& msg, Severity severity) { - __android_log_print( - getLogSeverityForSeverity(severity), - "Geode", - "%s", - msg.c_str() - ); -} - -void Loader::Impl::openPlatformConsole() { - return; -} - -void Loader::Impl::closePlatformConsole() { - return; -} - -void Loader::Impl::setupIPC() { - log::warn("IPC is not supported on this platform!"); -} - bool Loader::Impl::userTriedToLoadDLLs() const { return false; } - -#endif diff --git a/loader/src/platform/android/ModImpl.cpp b/loader/src/platform/android/ModImpl.cpp index 042e63a5..edcd3415 100644 --- a/loader/src/platform/android/ModImpl.cpp +++ b/loader/src/platform/android/ModImpl.cpp @@ -1,7 +1,5 @@ #include -#ifdef GEODE_IS_ANDROID - #include #include @@ -34,5 +32,3 @@ Result<> Mod::Impl::unloadPlatformBinary() { return Err("Unable to free library"); } } - -#endif diff --git a/loader/src/platform/android/console.cpp b/loader/src/platform/android/console.cpp new file mode 100644 index 00000000..5ac2dbac --- /dev/null +++ b/loader/src/platform/android/console.cpp @@ -0,0 +1,39 @@ +#include +#include +#include +#include + +using namespace geode::prelude; + +namespace { + android_LogPriority getLogSeverityForSeverity(Severity severity) { + switch (severity) { + case Severity::Debug: return ANDROID_LOG_DEBUG; + case Severity::Info: return ANDROID_LOG_INFO; + case Severity::Warning: return ANDROID_LOG_WARN; + case Severity::Error: return ANDROID_LOG_ERROR; + default: return ANDROID_LOG_DEFAULT; + } + } +} + +void console::open() { + return; +} + +void console::close() { + return; +} + +void console::log(std::string const& msg, Severity severity) { + __android_log_print( + getLogSeverityForSeverity(severity), + "Geode", + "%s", + msg.c_str() + ); +} + +void console::messageBox(char const* title, std::string const& info, Severity severity) { + cocos2d::CCMessageBox(info.c_str(), title); +} diff --git a/loader/src/platform/android/crashlog.cpp b/loader/src/platform/android/crashlog.cpp index d9d15455..492466a2 100644 --- a/loader/src/platform/android/crashlog.cpp +++ b/loader/src/platform/android/crashlog.cpp @@ -1,7 +1,5 @@ #include -#ifdef GEODE_IS_ANDROID - using namespace geode::prelude; #include @@ -302,5 +300,3 @@ void crashlog::setupPlatformHandlerPost() { bool crashlog::didLastLaunchCrash() { return s_lastLaunchCrashed; } - -#endif diff --git a/loader/src/platform/android/gdstdlib.cpp b/loader/src/platform/android/gdstdlib.cpp index faa43d88..740c74d5 100644 --- a/loader/src/platform/android/gdstdlib.cpp +++ b/loader/src/platform/android/gdstdlib.cpp @@ -1,8 +1,6 @@ #include #include "../../c++stl/string-impl.hpp" -#ifdef GEODE_IS_ANDROID - #if defined(GEODE_IS_ANDROID32) static auto constexpr NEW_SYM = "_Znwj"; @@ -119,5 +117,3 @@ namespace geode::stl { // TODO: implement this, remember its copy-on-write... } } - -#endif \ No newline at end of file diff --git a/loader/src/platform/android/main.cpp b/loader/src/platform/android/main.cpp index b8015ef4..91242326 100644 --- a/loader/src/platform/android/main.cpp +++ b/loader/src/platform/android/main.cpp @@ -1,7 +1,5 @@ #include -#if defined(GEODE_IS_ANDROID) - #include "../load.hpp" #include @@ -30,5 +28,3 @@ extern "C" [[gnu::visibility("default")]] jint JNI_OnLoad(JavaVM* vm, void* rese extern "C" [[gnu::visibility("default")]] void emptyFunction(void*) { // empty } - -#endif \ No newline at end of file diff --git a/loader/src/platform/android/util.cpp b/loader/src/platform/android/util.cpp index c8bd35ae..250c447d 100644 --- a/loader/src/platform/android/util.cpp +++ b/loader/src/platform/android/util.cpp @@ -1,7 +1,5 @@ #include -#ifdef GEODE_IS_ANDROID - using namespace geode::prelude; #include @@ -279,5 +277,3 @@ void geode::utils::game::restart() { nullptr ), CCDirector::get()->getRunningScene(), false); } - -#endif diff --git a/loader/src/platform/ios/FileWatcher.mm b/loader/src/platform/ios/FileWatcher.mm index 88e5546c..96098328 100644 --- a/loader/src/platform/ios/FileWatcher.mm +++ b/loader/src/platform/ios/FileWatcher.mm @@ -1,7 +1,5 @@ #include -#ifdef GEODE_IS_IOS - #import #include #include @@ -52,5 +50,3 @@ void FileWatcher::watch() { bool FileWatcher::watching() const { return m_platformHandle != NULL; } - -#endif diff --git a/loader/src/platform/ios/LoaderImpl.cpp b/loader/src/platform/ios/LoaderImpl.cpp index f752c466..79614590 100644 --- a/loader/src/platform/ios/LoaderImpl.cpp +++ b/loader/src/platform/ios/LoaderImpl.cpp @@ -1,15 +1,13 @@ #include -#ifdef GEODE_IS_IOS - - #include - #include - #include - #include - #include - #include - #include - #include +#include +#include +#include +#include +#include +#include +#include +#include void Loader::Impl::platformMessageBox(char const* title, std::string const& info) { std::cout << title << ": " << info << std::endl; @@ -41,5 +39,3 @@ void Loader::Impl::setupIPC() { bool Loader::Impl::userTriedToLoadDLLs() const { return false; } - -#endif diff --git a/loader/src/platform/ios/ModImpl.cpp b/loader/src/platform/ios/ModImpl.cpp index a472e1ff..cec53bda 100644 --- a/loader/src/platform/ios/ModImpl.cpp +++ b/loader/src/platform/ios/ModImpl.cpp @@ -1,10 +1,8 @@ #include -#ifdef GEODE_IS_IOS - - #include - #include - #include +#include +#include +#include using namespace geode::prelude; @@ -43,5 +41,3 @@ Result<> Mod::Impl::unloadPlatformBinary() { return Err("Unable to free library"); } } - -#endif diff --git a/loader/src/platform/ios/crashlog.cpp b/loader/src/platform/ios/crashlog.cpp index b1c2d3d0..9321f510 100644 --- a/loader/src/platform/ios/crashlog.cpp +++ b/loader/src/platform/ios/crashlog.cpp @@ -1,7 +1,5 @@ #include -#ifdef GEODE_IS_IOS - bool crashlog::setupPlatformHandler() { return false; } @@ -13,5 +11,3 @@ bool crashlog::didLastLaunchCrash() { ghc::filesystem::path crashlog::getCrashLogDirectory() { return ""; } - -#endif diff --git a/loader/src/platform/ios/main.cpp b/loader/src/platform/ios/main.cpp index 8e4c273f..b4c22218 100644 --- a/loader/src/platform/ios/main.cpp +++ b/loader/src/platform/ios/main.cpp @@ -1,7 +1,5 @@ #include -#if defined(GEODE_IS_IOS) - #include "../load.hpp" #include #include @@ -43,5 +41,3 @@ extern "C" __attribute__((visibility("default"))) void dynamicTrigger() { // remove when we can figure out how to not remove it auto dynamicTriggerRef = &dynamicTrigger; - -#endif \ No newline at end of file diff --git a/loader/src/platform/ios/util.mm b/loader/src/platform/ios/util.mm index 8a029fae..0ad47fdb 100644 --- a/loader/src/platform/ios/util.mm +++ b/loader/src/platform/ios/util.mm @@ -1,9 +1,7 @@ - #include -#ifdef GEODE_IS_IOS - using namespace geode::prelude; + #include #include #include @@ -36,5 +34,3 @@ ghc::filesystem::path dirs::getGameDir() { ghc::filesystem::path dirs::getSaveDir() { return weaklyCanonical(CCFileUtils::sharedFileUtils()->getWritablePath().c_str()); } - -#endif diff --git a/loader/src/platform/mac/Cocos2d.cpp b/loader/src/platform/mac/Cocos2d.cpp index d0a366d8..630c9c26 100644 --- a/loader/src/platform/mac/Cocos2d.cpp +++ b/loader/src/platform/mac/Cocos2d.cpp @@ -1,7 +1,5 @@ #include -#ifdef GEODE_IS_MACOS - #include using namespace cocos2d; @@ -815,5 +813,3 @@ void CCArray::acceptVisitor(CCDataVisitor &visitor) { visitor.visit(this); } - -#endif diff --git a/loader/src/platform/mac/FileWatcher.mm b/loader/src/platform/mac/FileWatcher.mm index fc7ea533..5133a3e3 100644 --- a/loader/src/platform/mac/FileWatcher.mm +++ b/loader/src/platform/mac/FileWatcher.mm @@ -1,10 +1,8 @@ #include -#ifdef GEODE_IS_MACOS - - #import - #include - #include +#import +#include +#include // static constexpr const auto notifyAttributes = FILE_NOTIFY_CHANGE_LAST_WRITE | // FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE; @@ -52,5 +50,3 @@ void FileWatcher::watch() { bool FileWatcher::watching() const { return m_platformHandle != NULL; } - -#endif diff --git a/loader/src/platform/mac/ModImpl.cpp b/loader/src/platform/mac/ModImpl.cpp index 762ad3ec..a05c32eb 100644 --- a/loader/src/platform/mac/ModImpl.cpp +++ b/loader/src/platform/mac/ModImpl.cpp @@ -1,7 +1,5 @@ #include -#ifdef GEODE_IS_MACOS - #include #include #include @@ -43,5 +41,3 @@ Result<> Mod::Impl::unloadPlatformBinary() { return Err("Unable to free library"); } } - -#endif diff --git a/loader/src/platform/mac/crashlog.mm b/loader/src/platform/mac/crashlog.mm index 882f2961..9e623778 100644 --- a/loader/src/platform/mac/crashlog.mm +++ b/loader/src/platform/mac/crashlog.mm @@ -1,7 +1,5 @@ #include -#ifdef GEODE_IS_MACOS - #include #include #include @@ -391,6 +389,3 @@ bool crashlog::didLastLaunchCrash() { ghc::filesystem::path crashlog::getCrashLogDirectory() { return dirs::getGeodeDir() / "crashlogs"; } - - -#endif diff --git a/loader/src/platform/mac/gdstdlib.cpp b/loader/src/platform/mac/gdstdlib.cpp index fcb3de8f..d151aa75 100644 --- a/loader/src/platform/mac/gdstdlib.cpp +++ b/loader/src/platform/mac/gdstdlib.cpp @@ -1,7 +1,5 @@ #include -#ifdef GEODE_IS_MACOS - namespace gd { namespace { static inline auto emptyInternalString() { @@ -57,5 +55,3 @@ namespace gd { return std::string(*this) == std::string(other); } } - -#endif \ No newline at end of file diff --git a/loader/src/platform/mac/main.mm b/loader/src/platform/mac/main.mm index ec92b1cd..0b5db2f6 100644 --- a/loader/src/platform/mac/main.mm +++ b/loader/src/platform/mac/main.mm @@ -1,7 +1,5 @@ #include -#if defined(GEODE_IS_MACOS) - #import #include "../load.hpp" #include @@ -151,5 +149,3 @@ __attribute__((constructor)) void _entry() { if (!loadGeode()) return; } - -#endif \ No newline at end of file diff --git a/loader/src/platform/mac/util.mm b/loader/src/platform/mac/util.mm index 13ef876f..30c89acb 100644 --- a/loader/src/platform/mac/util.mm +++ b/loader/src/platform/mac/util.mm @@ -1,8 +1,5 @@ - #include -#ifdef GEODE_IS_MACOS - using namespace geode::prelude; #include @@ -304,5 +301,3 @@ Result geode::hook::getObjcMethodImp(std::string const& className, std::s return Ok((void*)method_getImplementation(method)); } - -#endif diff --git a/loader/src/platform/windows/FileWatcher.cpp b/loader/src/platform/windows/FileWatcher.cpp index d46d1c1b..9d356f7b 100644 --- a/loader/src/platform/windows/FileWatcher.cpp +++ b/loader/src/platform/windows/FileWatcher.cpp @@ -2,8 +2,6 @@ #include #include -#ifdef GEODE_IS_WINDOWS - static constexpr auto const notifyAttributes = FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE; @@ -100,5 +98,3 @@ bool FileWatcher::watching() const { HANDLE handle = (HANDLE)m_platformHandle; return handle != INVALID_HANDLE_VALUE && handle != nullptr; } - -#endif diff --git a/loader/src/platform/windows/IPC.cpp b/loader/src/platform/windows/IPC.cpp new file mode 100644 index 00000000..e2960c69 --- /dev/null +++ b/loader/src/platform/windows/IPC.cpp @@ -0,0 +1,75 @@ +#include +#include + +#include +#include +#include + +using namespace geode::prelude; + +static constexpr auto IPC_BUFFER_SIZE = 512; + +void ipcPipeThread(HANDLE pipe) { + char buffer[IPC_BUFFER_SIZE * sizeof(TCHAR)]; + DWORD read; + + std::optional replyID; + + // log::debug("Waiting for I/O"); + if (ReadFile(pipe, buffer, sizeof(buffer) - 1, &read, nullptr)) { + buffer[read] = '\0'; + + std::string reply = ipc::processRaw((void*)pipe, buffer).dump(); + + DWORD written; + WriteFile(pipe, reply.c_str(), reply.size(), &written, nullptr); + } + // log::debug("Connection done"); + + FlushFileBuffers(pipe); + DisconnectNamedPipe(pipe); + CloseHandle(pipe); + + // log::debug("Disconnected pipe"); +} + +void ipc::setup() { + std::thread ipcThread([]() { + while (true) { + auto pipe = CreateNamedPipeA( + ipc::IPC_PIPE_NAME, + PIPE_ACCESS_DUPLEX, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, + PIPE_UNLIMITED_INSTANCES, + IPC_BUFFER_SIZE, + IPC_BUFFER_SIZE, + NMPWAIT_USE_DEFAULT_WAIT, + nullptr + ); + if (pipe == INVALID_HANDLE_VALUE) { + // todo: Rn this quits IPC, but we might wanna change that later + // to just continue trying. however, I'm assuming that if + // CreateNamedPipeA fails, then it will probably fail again if + // you try right after, so changing the break; to continue; might + // just result in the console getting filled with error messages + log::warn("Unable to create pipe, quitting IPC"); + break; + } + // log::debug("Waiting for pipe connections"); + if (ConnectNamedPipe(pipe, nullptr)) { + // log::debug("Got connection, creating thread"); + std::thread pipeThread(&ipcPipeThread, pipe); + // SetThreadDescription(pipeThread.native_handle(), L"Geode IPC Pipe"); + pipeThread.detach(); + } + else { + // log::debug("No connection, cleaning pipe"); + CloseHandle(pipe); + } + } + }); + // SetThreadDescription(ipcThread.native_handle(), L"Geode Main IPC"); + ipcThread.detach(); + + log::debug("IPC set up"); +} diff --git a/loader/src/platform/windows/LoaderImpl.cpp b/loader/src/platform/windows/LoaderImpl.cpp index 7865759d..2b432eb2 100644 --- a/loader/src/platform/windows/LoaderImpl.cpp +++ b/loader/src/platform/windows/LoaderImpl.cpp @@ -1,19 +1,13 @@ #include #include #include -#include #include #include -#include using namespace geode::prelude; -#ifdef GEODE_IS_WINDOWS - #include -static constexpr auto IPC_BUFFER_SIZE = 512; - #include "gdTimestampMap.hpp" std::string Loader::Impl::getGameVersion() { if (m_gdVersion.empty()) { @@ -25,161 +19,6 @@ std::string Loader::Impl::getGameVersion() { return m_gdVersion; } -void Loader::Impl::platformMessageBox(char const* title, std::string const& info, Severity severity) { - unsigned int icon; - switch (severity) { - case Severity::Debug: - case Severity::Info: - case Severity::Notice: - icon = MB_ICONINFORMATION; - break; - case Severity::Warning: - icon = MB_ICONWARNING; - break; - default: - icon = MB_ICONERROR; - break; - } - MessageBoxA(nullptr, info.c_str(), title, icon); -} - -bool hasAnsiColorSupport = false; - -void Loader::Impl::logConsoleMessageWithSeverity(std::string const& msg, Severity severity) { - if (!m_platformConsoleOpen) - return; - - if (!hasAnsiColorSupport) { - std::cout << msg << "\n" << std::flush; - return; - } - - int color = 0; - switch (severity) { - case Severity::Debug: - color = 243; - break; - case Severity::Info: - color = 33; - break; - case Severity::Warning: - color = 229; - break; - case Severity::Error: - color = 9; - break; - default: - color = 7; - break; - } - auto const colorStr = fmt::format("\x1b[38;5;{}m", color); - auto const newMsg = fmt::format("{}{}\x1b[0m{}", colorStr, msg.substr(0, 12), - msg.substr(12)); - - std::cout << newMsg << "\n" << std::flush; -} - -void Loader::Impl::openPlatformConsole() { - if (m_platformConsoleOpen) return; - if (AllocConsole() == 0) return; - SetConsoleCP(CP_UTF8); - // redirect console output - freopen_s(reinterpret_cast(stdout), "CONOUT$", "w", stdout); - freopen_s(reinterpret_cast(stdin), "CONIN$", "r", stdin); - - // Set output mode to handle ansi color sequences - auto handleStdout = GetStdHandle(STD_OUTPUT_HANDLE); - - DWORD consoleMode = 0; - if (GetConsoleMode(handleStdout, &consoleMode)) { - consoleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; - if (SetConsoleMode(handleStdout, consoleMode)) { - hasAnsiColorSupport = true; - } - } - - m_platformConsoleOpen = true; - - for (auto const& log : log::Logger::get()->list()) { - this->logConsoleMessageWithSeverity(log.toString(true), log.getSeverity()); - } -} - -void Loader::Impl::closePlatformConsole() { - if (!m_platformConsoleOpen) return; - - fclose(stdin); - fclose(stdout); - FreeConsole(); - - m_platformConsoleOpen = false; -} - -void ipcPipeThread(HANDLE pipe) { - char buffer[IPC_BUFFER_SIZE * sizeof(TCHAR)]; - DWORD read; - - std::optional replyID = std::nullopt; - - // log::debug("Waiting for I/O"); - if (ReadFile(pipe, buffer, sizeof(buffer) - 1, &read, nullptr)) { - buffer[read] = '\0'; - - std::string reply = LoaderImpl::get()->processRawIPC((void*)pipe, buffer).dump(); - - DWORD written; - WriteFile(pipe, reply.c_str(), reply.size(), &written, nullptr); - } - // log::debug("Connection done"); - - FlushFileBuffers(pipe); - DisconnectNamedPipe(pipe); - CloseHandle(pipe); - - // log::debug("Disconnected pipe"); -} - -void Loader::Impl::setupIPC() { - std::thread ipcThread([]() { - while (true) { - auto pipe = CreateNamedPipeA( - IPC_PIPE_NAME, - PIPE_ACCESS_DUPLEX, - PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, - PIPE_UNLIMITED_INSTANCES, - IPC_BUFFER_SIZE, - IPC_BUFFER_SIZE, - NMPWAIT_USE_DEFAULT_WAIT, - nullptr - ); - if (pipe == INVALID_HANDLE_VALUE) { - // todo: Rn this quits IPC, but we might wanna change that later - // to just continue trying. however, I'm assuming that if - // CreateNamedPipeA fails, then it will probably fail again if - // you try right after, so changing the break; to continue; might - // just result in the console getting filled with error messages - log::warn("Unable to create pipe, quitting IPC"); - break; - } - // log::debug("Waiting for pipe connections"); - if (ConnectNamedPipe(pipe, nullptr)) { - // log::debug("Got connection, creating thread"); - std::thread pipeThread(&ipcPipeThread, pipe); - // SetThreadDescription(pipeThread.native_handle(), L"Geode IPC Pipe"); - pipeThread.detach(); - } - else { - // log::debug("No connection, cleaning pipe"); - CloseHandle(pipe); - } - } - }); - // SetThreadDescription(ipcThread.native_handle(), L"Geode Main IPC"); - ipcThread.detach(); - - log::debug("IPC set up"); -} - bool Loader::Impl::userTriedToLoadDLLs() const { static std::unordered_set KNOWN_MOD_DLLS { "betteredit-v4.0.5.dll", @@ -235,5 +74,3 @@ bool Loader::Impl::userTriedToLoadDLLs() const { return triedToLoadDLLs; } - -#endif diff --git a/loader/src/platform/windows/ModImpl.cpp b/loader/src/platform/windows/ModImpl.cpp index 7f9bd8c1..d7122317 100644 --- a/loader/src/platform/windows/ModImpl.cpp +++ b/loader/src/platform/windows/ModImpl.cpp @@ -1,7 +1,5 @@ #include -#ifdef GEODE_IS_WINDOWS - #include #include @@ -81,5 +79,3 @@ Result<> Mod::Impl::unloadPlatformBinary() { return Err("Unable to free the DLL: " + getLastWinError()); } } - -#endif diff --git a/loader/src/platform/windows/console.cpp b/loader/src/platform/windows/console.cpp new file mode 100644 index 00000000..e87d8edf --- /dev/null +++ b/loader/src/platform/windows/console.cpp @@ -0,0 +1,96 @@ +#include +#include +#include + +using namespace geode::prelude; + +bool s_isOpen = false; +bool s_hasAnsiColorSupport = false; + +void console::open() { + if (s_isOpen) return; + if (AllocConsole() == 0) return; + SetConsoleCP(CP_UTF8); + // redirect console output + freopen_s(reinterpret_cast(stdout), "CONOUT$", "w", stdout); + freopen_s(reinterpret_cast(stdin), "CONIN$", "r", stdin); + + // Set output mode to handle ansi color sequences + auto handleStdout = GetStdHandle(STD_OUTPUT_HANDLE); + + DWORD consoleMode = 0; + if (GetConsoleMode(handleStdout, &consoleMode)) { + consoleMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + if (SetConsoleMode(handleStdout, consoleMode)) { + s_hasAnsiColorSupport = true; + } + } + + s_isOpen = true; + + for (auto const& log : log::Logger::get()->list()) { + console::log(log.toString(true), log.getSeverity()); + } +} + +void console::close() { + if (!s_isOpen) return; + + fclose(stdin); + fclose(stdout); + FreeConsole(); + + s_isOpen = false; +} + +void console::log(std::string const& msg, Severity severity) { + if (!s_isOpen) + return; + + if (!s_hasAnsiColorSupport) { + std::cout << msg << "\n" << std::flush; + return; + } + + int color = 0; + switch (severity) { + case Severity::Debug: + color = 243; + break; + case Severity::Info: + color = 33; + break; + case Severity::Warning: + color = 229; + break; + case Severity::Error: + color = 9; + break; + default: + color = 7; + break; + } + auto const colorStr = fmt::format("\x1b[38;5;{}m", color); + auto const newMsg = fmt::format("{}{}\x1b[0m{}", colorStr, msg.substr(0, 12), + msg.substr(12)); + + std::cout << newMsg << "\n" << std::flush; +} + +void console::messageBox(char const* title, std::string const& info, Severity severity) { + unsigned int icon; + switch (severity) { + case Severity::Debug: + case Severity::Info: + case Severity::Notice: + icon = MB_ICONINFORMATION; + break; + case Severity::Warning: + icon = MB_ICONWARNING; + break; + default: + icon = MB_ICONERROR; + break; + } + MessageBoxA(nullptr, info.c_str(), title, icon); +} diff --git a/loader/src/platform/windows/crashlog.cpp b/loader/src/platform/windows/crashlog.cpp index 2403faf9..1075c104 100644 --- a/loader/src/platform/windows/crashlog.cpp +++ b/loader/src/platform/windows/crashlog.cpp @@ -2,8 +2,6 @@ #include -#ifdef GEODE_IS_WINDOWS - #include #include #include @@ -301,5 +299,3 @@ void crashlog::setupPlatformHandlerPost() {} ghc::filesystem::path crashlog::getCrashLogDirectory() { return dirs::getGeodeDir() / "crashlogs"; } - -#endif diff --git a/loader/src/platform/windows/gdstdlib.cpp b/loader/src/platform/windows/gdstdlib.cpp index 6a637488..ada3675b 100644 --- a/loader/src/platform/windows/gdstdlib.cpp +++ b/loader/src/platform/windows/gdstdlib.cpp @@ -1,7 +1,5 @@ #include "../../c++stl/string-impl.hpp" -#ifdef GEODE_IS_WINDOWS - namespace geode::stl { void StringImpl::setEmpty() { data.m_size = 0; @@ -43,5 +41,3 @@ namespace geode::stl { data.m_capacity = cap; } } - -#endif \ No newline at end of file diff --git a/loader/src/platform/windows/main.cpp b/loader/src/platform/windows/main.cpp index 534d0af4..e369a6eb 100644 --- a/loader/src/platform/windows/main.cpp +++ b/loader/src/platform/windows/main.cpp @@ -1,11 +1,11 @@ #include -#if defined(GEODE_IS_WINDOWS) - #include "../load.hpp" #include #include "loader/LoaderImpl.hpp" +#include "loader/console.hpp" + using namespace geode::prelude; void updateGeode() { @@ -38,7 +38,7 @@ int WINAPI gdMainHook(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmd updateGeode(); if (versionToTimestamp(GEODE_STR(GEODE_GD_VERSION)) > gdTimestamp) { - LoaderImpl::get()->platformMessageBox( + console::messageBox( "Unable to Load Geode!", fmt::format( "This version of Geode is made for Geometry Dash {} " @@ -142,7 +142,7 @@ void earlyError(std::string message) { std::ofstream fout("_geode_early_error.txt"); fout << message; fout.close(); - LoaderImpl::get()->platformMessageBox("Unable to Load Geode!", message); + console::messageBox("Unable to Load Geode!", message); } BOOL WINAPI DllMain(HINSTANCE module, DWORD reason, LPVOID) { @@ -176,5 +176,3 @@ BOOL WINAPI DllMain(HINSTANCE module, DWORD reason, LPVOID) { return TRUE; } - -#endif diff --git a/loader/src/platform/windows/nfdwin.cpp b/loader/src/platform/windows/nfdwin.cpp index ad660a4b..dfd4a75b 100644 --- a/loader/src/platform/windows/nfdwin.cpp +++ b/loader/src/platform/windows/nfdwin.cpp @@ -1,8 +1,6 @@ #include "nfdwin.hpp" #include -#ifdef GEODE_IS_WINDOWS - using Path = ghc::filesystem::path; using Paths = std::vector; @@ -296,5 +294,3 @@ Result<> nfdPick( return Err("Unknown error"); } - -#endif diff --git a/loader/src/platform/windows/nfdwin.hpp b/loader/src/platform/windows/nfdwin.hpp index aa3d01dc..53128f27 100644 --- a/loader/src/platform/windows/nfdwin.hpp +++ b/loader/src/platform/windows/nfdwin.hpp @@ -10,8 +10,6 @@ #include -#ifdef GEODE_IS_WINDOWS - #ifdef __MINGW32__ // Explicitly setting NTDDI version, this is necessary for the MinGW compiler #define NTDDI_VERSION NTDDI_VISTA @@ -58,5 +56,3 @@ Result<> nfdPick( file::FilePickOptions const& options, void* result ); - -#endif diff --git a/loader/src/platform/windows/util.cpp b/loader/src/platform/windows/util.cpp index c047dcbf..f5f28bcb 100644 --- a/loader/src/platform/windows/util.cpp +++ b/loader/src/platform/windows/util.cpp @@ -1,9 +1,7 @@ - #include -#ifdef GEODE_IS_WINDOWS - using namespace geode::prelude; + #include #include "nfdwin.hpp" #include @@ -265,5 +263,3 @@ Result<> geode::hook::addObjcMethod(std::string const& className, std::string co Result geode::hook::getObjcMethodImp(std::string const& className, std::string const& selectorName) { return Err("Wrong platform"); } - -#endif