diff --git a/loader/include/Geode/loader/Event.hpp b/loader/include/Geode/loader/Event.hpp index 7c7bf65f..1ccc3348 100644 --- a/loader/include/Geode/loader/Event.hpp +++ b/loader/include/Geode/loader/Event.hpp @@ -65,12 +65,20 @@ namespace geode { return ListenerResult::Propagate; } - EventListener(T filter = T()) {} - EventListener(std::function<Callback> fn, T filter = T()) : m_callback(fn), m_filter(filter) {} - EventListener(Callback* fnptr, T filter = T()) : m_callback(fnptr), m_filter(filter) {} + EventListener(T filter = T()) { + this->enable(); + } + EventListener(std::function<Callback> fn, T filter = T()) : m_callback(fn), m_filter(filter) { + this->enable(); + } + EventListener(Callback* fnptr, T filter = T()) : m_callback(fnptr), m_filter(filter) { + this->enable(); + } template <class C> - EventListener(C* cls, MemberFn<C> fn, T filter = T()) : EventListener(std::bind(fn, cls, std::placeholders::_1), filter) {} + EventListener(C* cls, MemberFn<C> fn, T filter = T()) : EventListener(std::bind(fn, cls, std::placeholders::_1), filter) { + this->enable(); + } void bind(std::function<Callback> fn) { m_callback = fn; diff --git a/loader/include/Geode/loader/IPC.hpp b/loader/include/Geode/loader/IPC.hpp new file mode 100644 index 00000000..5a8ab4ae --- /dev/null +++ b/loader/include/Geode/loader/IPC.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include "Event.hpp" +#include "Loader.hpp" + +namespace geode { + #ifdef GEODE_IS_WINDOWS + constexpr const char* IPC_PIPE_NAME = "\\\\.\\pipe\\GeodeIPCPipe"; + #endif + + // message ID may be anything that doesn't contain /, but to be safe, you + // should only define message IDs that match the following: [a-z\-_\.]+ + + class GEODE_DLL IPCEvent : public Event { + protected: + void* m_rawPipeHandle; + std::string m_targetModID; + std::string m_senderID; + std::string m_messageID; + std::string m_messageData; + + public: + IPCEvent( + void* rawPipeHandle, + std::string targetModID, + std::string senderID, + std::string messageID, + std::string messageData + ); + + std::string getSenderID() const; + std::string getTargetModID() const; + std::string getMessageID() const; + std::string getMessageData() const; + + /** + * Reply to the message. You can only reply once + */ + void reply(std::string const& data); + }; + + class GEODE_DLL IPCFilter : public EventFilter<IPCEvent> { + public: + using Callback = void(IPCEvent*); + + protected: + std::string m_modID; + std::string m_messageID; + + public: + ListenerResult handle(std::function<Callback> fn, IPCEvent* event); + IPCFilter( + std::string const& modID, + std::string const& messageID + ); + }; + + template<class = void> + std::monostate listenForIPC(std::string const& messageID, void(*callback)(IPCEvent*)) { + Loader::get()->scheduleOnModLoad(getMod(), [=]() { + new EventListener( + callback, IPCFilter(getMod()->getID(), messageID) + ); + }); + return std::monostate(); + } +} diff --git a/loader/include/Geode/loader/SettingEvent.hpp b/loader/include/Geode/loader/SettingEvent.hpp index c5d2dbc9..2d3f0553 100644 --- a/loader/include/Geode/loader/SettingEvent.hpp +++ b/loader/include/Geode/loader/SettingEvent.hpp @@ -57,7 +57,7 @@ namespace geode { std::string const& settingID, void (*callback)(std::shared_ptr<T>) ) { Loader::get()->scheduleOnModLoad(getMod(), [=]() { - static auto _ = EventListener( + new EventListener( callback, SettingChangedFilter<T>(getMod()->getID(), settingID) ); }); @@ -66,7 +66,7 @@ namespace geode { static std::monostate listenForAllSettingChanges(void (*callback)(std::shared_ptr<Setting>)) { Loader::get()->scheduleOnModLoad(getMod(), [=]() { - static auto _ = EventListener( + new EventListener( callback, SettingChangedFilter(getMod()->getID()) ); }); diff --git a/loader/include/Geode/utils/ranges.hpp b/loader/include/Geode/utils/ranges.hpp index 1cd6e431..561a6a3f 100644 --- a/loader/include/Geode/utils/ranges.hpp +++ b/loader/include/Geode/utils/ranges.hpp @@ -201,11 +201,11 @@ namespace geode::utils::ranges { } template < - ValidConstContainer From, ValidContainer Into, + ValidContainer Into, ValidConstContainer From, ValidIntoConverter<typename From::value_type, typename Into::value_type> Mapper> Into map(From const& from, Mapper mapper) { auto res = Into(); - std::transform(from.begin(), from.end(), res.end(), mapper); + std::transform(from.begin(), from.end(), std::back_inserter(res), mapper); return res; } diff --git a/loader/src/ids/LevelInfoLayer.cpp b/loader/src/ids/LevelInfoLayer.cpp index 37d8c598..28e216b0 100644 --- a/loader/src/ids/LevelInfoLayer.cpp +++ b/loader/src/ids/LevelInfoLayer.cpp @@ -1,4 +1,5 @@ #include "AddIDs.hpp" +#include <Geode/binding/LevelInfoLayer.hpp> #include <Geode/modify/LevelInfoLayer.hpp> $register_ids(LevelInfoLayer) { diff --git a/loader/src/internal/InternalLoader.cpp b/loader/src/internal/InternalLoader.cpp index c9de6c10..abd34c46 100644 --- a/loader/src/internal/InternalLoader.cpp +++ b/loader/src/internal/InternalLoader.cpp @@ -39,6 +39,9 @@ bool InternalLoader::setup() { log::log(Severity::Debug, InternalMod::get(), "Loaded hooks"); + log::log(Severity::Debug, InternalMod::get(), "Setting up IPC..."); + this->setupIPC(); + return true; } @@ -177,79 +180,3 @@ bool InternalLoader::verifyLoaderResources(IndexUpdateCallback callback) { return true; } - -#if defined(GEODE_IS_WINDOWS) -void InternalLoader::platformMessageBox(char const* title, std::string const& info) { - MessageBoxA(nullptr, info.c_str(), title, MB_ICONERROR); -} - -void InternalLoader::openPlatformConsole() { - if (m_platformConsoleOpen) return; - if (AllocConsole() == 0) return; - SetConsoleCP(CP_UTF8); - // redirect console output - freopen_s(reinterpret_cast<FILE**>(stdout), "CONOUT$", "w", stdout); - freopen_s(reinterpret_cast<FILE**>(stdin), "CONIN$", "r", stdin); - - m_platformConsoleOpen = true; - - for (auto const& log : Loader::get()->getLogs()) { - std::cout << log->toString(true) << "\n"; - } -} - -void InternalLoader::closePlatformConsole() { - if (!m_platformConsoleOpen) return; - - fclose(stdin); - fclose(stdout); - FreeConsole(); - - m_platformConsoleOpen = false; -} - -#elif defined(GEODE_IS_MACOS) - #include <CoreFoundation/CoreFoundation.h> - -void InternalLoader::platformMessageBox(char const* title, std::string const& info) { - CFStringRef cfTitle = CFStringCreateWithCString(NULL, title, kCFStringEncodingUTF8); - CFStringRef cfMessage = CFStringCreateWithCString(NULL, info.c_str(), kCFStringEncodingUTF8); - - CFUserNotificationDisplayNotice( - 0, kCFUserNotificationNoteAlertLevel, NULL, NULL, NULL, cfTitle, cfMessage, NULL - ); -} - -void InternalLoader::openPlatformConsole() { - m_platformConsoleOpen = true; - - for (auto const& log : Loader::get()->getLogs()) { - std::cout << log->toString(true) << "\n"; - } -} - -void InternalLoader::closePlatformConsole() { - m_platformConsoleOpen = false; -} - -#elif defined(GEODE_IS_IOS) - - #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(utils::file::geodeRoot() / "geode_log.txt").string().c_str(), "w", - stdout - ); - InternalLoader::m_platformConsoleOpen = true; -} - -void InternalLoader::closePlatformConsole() {} -#endif diff --git a/loader/src/internal/InternalLoader.hpp b/loader/src/internal/InternalLoader.hpp index ff62f133..58965174 100644 --- a/loader/src/internal/InternalLoader.hpp +++ b/loader/src/internal/InternalLoader.hpp @@ -31,6 +31,9 @@ protected: void downloadLoaderResources(IndexUpdateCallback callback); + bool loadHooks(); + void setupIPC(); + InternalLoader(); ~InternalLoader(); @@ -41,7 +44,11 @@ public: bool setup(); - bool loadHooks(); + void postIPCMessage( + void* rawPipeHandle, + std::string const& senderID, + std::string const& data + ); /** * Check if a one-time event has been shown to the user, diff --git a/loader/src/internal/ios/InternalLoader.cpp b/loader/src/internal/ios/InternalLoader.cpp new file mode 100644 index 00000000..6c026b15 --- /dev/null +++ b/loader/src/internal/ios/InternalLoader.cpp @@ -0,0 +1,38 @@ +#include "../InternalLoader.hpp" +#include <Geode/loader/Log.hpp> +#include <iostream> +#include "../InternalMod.hpp" + +#ifdef GEODE_IS_IOS + +#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(utils::file::geodeRoot() / "geode_log.txt").string().c_str(), "w", + stdout + ); + InternalLoader::m_platformConsoleOpen = true; +} + +void InternalLoader::closePlatformConsole() {} + +void InternalLoader::postIPCMessage( + void* rawPipeHandle, + std::string const& senderID, + std::string 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/internal/mac/InternalLoader.cpp b/loader/src/internal/mac/InternalLoader.cpp new file mode 100644 index 00000000..5abc785c --- /dev/null +++ b/loader/src/internal/mac/InternalLoader.cpp @@ -0,0 +1,42 @@ +#include "../InternalLoader.hpp" +#include <Geode/loader/Log.hpp> +#include <iostream> +#include "../InternalMod.hpp" + +#ifdef GEODE_IS_MACOS + +#include <CoreFoundation/CoreFoundation.h> + +void InternalLoader::platformMessageBox(char const* title, std::string const& info) { + CFStringRef cfTitle = CFStringCreateWithCString(NULL, title, kCFStringEncodingUTF8); + CFStringRef cfMessage = CFStringCreateWithCString(NULL, info.c_str(), kCFStringEncodingUTF8); + + CFUserNotificationDisplayNotice( + 0, kCFUserNotificationNoteAlertLevel, NULL, NULL, NULL, cfTitle, cfMessage, NULL + ); +} + +void InternalLoader::openPlatformConsole() { + m_platformConsoleOpen = true; + + for (auto const& log : Loader::get()->getLogs()) { + std::cout << log->toString(true) << "\n"; + } +} + +void InternalLoader::closePlatformConsole() { + m_platformConsoleOpen = false; +} + +void InternalLoader::postIPCMessage( + void* rawPipeHandle, + std::string const& senderID, + std::string 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/internal/windows/InternalLoader.cpp b/loader/src/internal/windows/InternalLoader.cpp new file mode 100644 index 00000000..ab709061 --- /dev/null +++ b/loader/src/internal/windows/InternalLoader.cpp @@ -0,0 +1,110 @@ +#include "../InternalLoader.hpp" +#include <Geode/loader/Log.hpp> +#include <Geode/loader/IPC.hpp> +#include <iostream> +#include "../InternalMod.hpp" + +USE_GEODE_NAMESPACE(); + +#ifdef GEODE_IS_WINDOWS + +void InternalLoader::platformMessageBox(char const* title, std::string const& info) { + MessageBoxA(nullptr, info.c_str(), title, MB_ICONERROR); +} + +void InternalLoader::openPlatformConsole() { + if (m_platformConsoleOpen) return; + if (AllocConsole() == 0) return; + SetConsoleCP(CP_UTF8); + // redirect console output + freopen_s(reinterpret_cast<FILE**>(stdout), "CONOUT$", "w", stdout); + freopen_s(reinterpret_cast<FILE**>(stdin), "CONIN$", "r", stdin); + + m_platformConsoleOpen = true; + + for (auto const& log : Loader::get()->getLogs()) { + std::cout << log->toString(true) << "\n"; + } +} + +void InternalLoader::closePlatformConsole() { + if (!m_platformConsoleOpen) return; + + fclose(stdin); + fclose(stdout); + FreeConsole(); + + m_platformConsoleOpen = false; +} + +void InternalLoader::postIPCMessage( + void* rawPipeHandle, + std::string const& senderID, + std::string const& data +) { + std::string msg = senderID + "/" + data; + log::debug("Replying msg: {}", msg); + DWORD written; + WriteFile(rawPipeHandle, msg.c_str(), msg.size(), &written, nullptr); +} + +void InternalLoader::setupIPC() { + auto pipe = CreateNamedPipeA( + IPC_PIPE_NAME, + PIPE_ACCESS_DUPLEX, + PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, + 1, + 1024 * 16, + 1024 * 16, + NMPWAIT_USE_DEFAULT_WAIT, + nullptr + ); + std::thread([pipe]() { + while (pipe != INVALID_HANDLE_VALUE) { + log::debug("Waiting for connection"); + if (ConnectNamedPipe(pipe, nullptr)) { + log::debug("Got connection"); + char buffer[1024]; + DWORD read; + while (ReadFile(pipe, buffer, sizeof(buffer) - 1, &read, nullptr)) { + buffer[read] = '\0'; + + // format of the message should be modID/senderID/msgID/data + std::string modID; + std::string senderID; + std::string msgID; + std::string data; + size_t collectPart = 0; + for (size_t i = 0; i < read; i++) { + if (buffer[i] == '/' && collectPart < 3) { + collectPart++; + } else { + switch (collectPart) { + case 0: modID += buffer[i]; break; + case 1: senderID += buffer[i]; break; + case 2: msgID += buffer[i]; break; + default: data += buffer[i]; break; + } + } + } + if (modID.size() && senderID.size() && msgID.size()) { + IPCEvent(pipe, modID, senderID, msgID, data).post(); + } else { + log::warn("Received invalid IPC message: '{}'", buffer); + } + } + log::debug("Connection done"); + } + DisconnectNamedPipe(pipe); + log::debug("Disconnected pipe"); + } + log::debug("IPC ended"); + }).detach(); + if (pipe != INVALID_HANDLE_VALUE) { + log::log(Severity::Debug, InternalMod::get(), "IPC set up"); + } else { + log::log(Severity::Error, InternalMod::get(), "Unable to set up IPC"); + } +} + +#endif diff --git a/loader/src/load/IPC.cpp b/loader/src/load/IPC.cpp new file mode 100644 index 00000000..84d3081b --- /dev/null +++ b/loader/src/load/IPC.cpp @@ -0,0 +1,55 @@ +#include <Geode/loader/IPC.hpp> +#include <InternalLoader.hpp> + +USE_GEODE_NAMESPACE(); + +IPCEvent::IPCEvent( + void* rawPipeHandle, + std::string targetModID, + std::string senderID, + std::string messageID, + std::string messageData +) : m_rawPipeHandle(rawPipeHandle), + m_targetModID(targetModID), + m_senderID(senderID), + m_messageID(messageID), + m_messageData(messageData) {} + +std::string IPCEvent::getSenderID() const { + return m_senderID; +} + +std::string IPCEvent::getTargetModID() const { + return m_targetModID; +} + +std::string IPCEvent::getMessageID() const { + return m_messageID; +} + +std::string IPCEvent::getMessageData() const { + return m_messageData; +} + +void IPCEvent::reply(std::string const& data) { + if (m_rawPipeHandle) { + InternalLoader::get()->postIPCMessage(m_rawPipeHandle, m_senderID, data); + } +} + +ListenerResult IPCFilter::handle(std::function<Callback> fn, IPCEvent* event) { + if ( + event->getTargetModID() == m_modID && + event->getMessageID() == m_messageID + ) { + fn(event); + return ListenerResult::Stop; + } + return ListenerResult::Propagate; +} + +IPCFilter::IPCFilter( + std::string const& modID, + std::string const& messageID +) : m_modID(modID), + m_messageID(messageID) {} diff --git a/loader/src/main.cpp b/loader/src/main.cpp index c77d9faa..e7211d1b 100644 --- a/loader/src/main.cpp +++ b/loader/src/main.cpp @@ -4,6 +4,7 @@ #include <Geode/loader/Log.hpp> #include <Geode/loader/Mod.hpp> #include <Geode/loader/SettingEvent.hpp> +#include <Geode/loader/IPC.hpp> #include <InternalLoader.hpp> #include <InternalMod.hpp> #include <array> @@ -96,7 +97,9 @@ BOOL WINAPI DllMain(HINSTANCE lib, DWORD reason, LPVOID) { } #endif -static auto _ = listenForSettingChanges( +#define $_ GEODE_CONCAT(unnamedVar_, __LINE__) + +static auto $_ = listenForSettingChanges( "show-platform-console", +[](std::shared_ptr<BoolSetting> setting) { if (setting->getValue()) { @@ -108,6 +111,23 @@ static auto _ = listenForSettingChanges( } ); +static auto $_ = listenForIPC("ipc-test", +[](IPCEvent* event) { + event->reply("Hello from Geode!"); +}); + +static auto $_ = listenForIPC("list-mods", +[](IPCEvent* event) { + event->reply( + "[ " + ranges::join( + ranges::map<std::vector<std::string>>( + Loader::get()->getAllMods(), + [](Mod* mod) { + return "\"" + mod->getID() + "\""; + } + ), ", " + ) + " ]" + ); +}); + int geodeEntry(void* platformData) { // setup internals