mirror of
https://github.com/geode-sdk/geode.git
synced 2025-03-25 04:11:42 -04:00
update IPC to use json
This commit is contained in:
parent
384efb32bb
commit
7a047e2a08
12 changed files with 140 additions and 68 deletions
loader
|
@ -8,35 +8,61 @@ namespace geode {
|
|||
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\-_\.]+
|
||||
// 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;
|
||||
std::string m_targetModID;
|
||||
std::string m_senderID;
|
||||
std::optional<std::string> m_replyID;
|
||||
std::string m_messageID;
|
||||
std::string m_messageData;
|
||||
nlohmann::json m_messageData;
|
||||
bool m_replied = false;
|
||||
|
||||
public:
|
||||
IPCEvent(
|
||||
void* rawPipeHandle,
|
||||
std::string targetModID,
|
||||
std::string senderID,
|
||||
std::string messageID,
|
||||
std::string messageData
|
||||
std::string const& targetModID,
|
||||
std::string const& messageID,
|
||||
std::optional<std::string> const& replyID,
|
||||
nlohmann::json const& messageData
|
||||
);
|
||||
virtual ~IPCEvent();
|
||||
|
||||
std::string getSenderID() const;
|
||||
std::optional<std::string> getReplyID() const;
|
||||
std::string getTargetModID() const;
|
||||
std::string getMessageID() const;
|
||||
std::string getMessageData() const;
|
||||
nlohmann::json getMessageData() const;
|
||||
|
||||
/**
|
||||
* Reply to the message. You can only reply once
|
||||
* Whether this message can be replied to, i.e. if it has a reply ID
|
||||
* provided
|
||||
* @returns True if the message can be replied to, false otherwise
|
||||
*/
|
||||
void reply(std::string const& data);
|
||||
bool canReply() const;
|
||||
|
||||
/**
|
||||
* Reply to the message. Will post a message back to the application
|
||||
* the sent this message with the reply ID and provided data.
|
||||
* You can only reply once; after the other application has received
|
||||
* the reply, it can assume the reply ID can be freed and reused for
|
||||
* other messages. Calling reply again on this message will not cause
|
||||
* a new response to be sent.
|
||||
* If reply is not explicitly called, a default response of null will
|
||||
* be posted back.
|
||||
* @param data The data to send back; will be the under the "data" key
|
||||
* in the response JSON. The structure may be anything; however, you
|
||||
* should document what kind of JSON structures applications may expect
|
||||
* from your mod.
|
||||
*/
|
||||
void reply(nlohmann::json const& data);
|
||||
};
|
||||
|
||||
class GEODE_DLL IPCFilter : public EventFilter<IPCEvent> {
|
||||
|
|
|
@ -172,7 +172,19 @@ namespace geode {
|
|||
*/
|
||||
static Result<ModInfo> create(ModJson const& json);
|
||||
|
||||
/**
|
||||
* Convert to JSON. Essentially same as getRawJSON except dynamically
|
||||
* adds runtime fields like path
|
||||
*/
|
||||
ModJson toJSON() const;
|
||||
/**
|
||||
* Get the raw JSON file
|
||||
*/
|
||||
ModJson getRawJSON() const;
|
||||
|
||||
private:
|
||||
ModJson m_rawJSON;
|
||||
|
||||
/**
|
||||
* Version is passed for backwards
|
||||
* compatibility if we update the mod.json
|
||||
|
@ -186,6 +198,9 @@ namespace geode {
|
|||
std::vector<std::pair<std::string, std::optional<std::string>*>> getSpecialFiles();
|
||||
};
|
||||
|
||||
// For converting ModInfo back to JSON
|
||||
void GEODE_DLL to_json(nlohmann::json& json, ModInfo const& info);
|
||||
|
||||
/**
|
||||
* @class DataStore
|
||||
* Internal class for notifying Mod
|
||||
|
|
|
@ -52,8 +52,8 @@ namespace geode {
|
|||
};
|
||||
|
||||
template <class T>
|
||||
|
||||
requires std::is_base_of_v<Setting, T> std::monostate listenForSettingChanges(
|
||||
requires std::is_base_of_v<Setting, T>
|
||||
std::monostate listenForSettingChanges(
|
||||
std::string const& settingID, void (*callback)(T*)
|
||||
) {
|
||||
Loader::get()->scheduleOnModLoad(getMod(), [=]() {
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include <Geode/DefaultInclude.hpp>
|
||||
#include <string_view>
|
||||
#include "json.hpp"
|
||||
|
||||
namespace geode {
|
||||
/**
|
||||
|
@ -61,6 +62,8 @@ namespace geode {
|
|||
std::string toString() const;
|
||||
};
|
||||
|
||||
void GEODE_DLL to_json(nlohmann::json& json, VersionInfo const& info);
|
||||
|
||||
inline std::ostream& operator<<(std::ostream& stream, VersionInfo const& version) {
|
||||
stream << version.toString();
|
||||
return stream;
|
||||
|
|
|
@ -44,10 +44,10 @@ public:
|
|||
|
||||
bool setup();
|
||||
|
||||
void postIPCMessage(
|
||||
void postIPCReply(
|
||||
void* rawPipeHandle,
|
||||
std::string const& senderID,
|
||||
std::string const& data
|
||||
std::string const& replyID,
|
||||
nlohmann::json const& data
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
|
@ -24,7 +24,7 @@ void InternalLoader::openPlatformConsole() {
|
|||
|
||||
void InternalLoader::closePlatformConsole() {}
|
||||
|
||||
void InternalLoader::postIPCMessage(
|
||||
void InternalLoader::postIPCReply(
|
||||
void* rawPipeHandle,
|
||||
std::string const& senderID,
|
||||
std::string const& data
|
||||
|
|
|
@ -28,7 +28,7 @@ void InternalLoader::closePlatformConsole() {
|
|||
m_platformConsoleOpen = false;
|
||||
}
|
||||
|
||||
void InternalLoader::postIPCMessage(
|
||||
void InternalLoader::postIPCReply(
|
||||
void* rawPipeHandle,
|
||||
std::string const& senderID,
|
||||
std::string const& data
|
||||
|
|
|
@ -37,17 +37,21 @@ void InternalLoader::closePlatformConsole() {
|
|||
m_platformConsoleOpen = false;
|
||||
}
|
||||
|
||||
void InternalLoader::postIPCMessage(
|
||||
void InternalLoader::postIPCReply(
|
||||
void* rawPipeHandle,
|
||||
std::string const& senderID,
|
||||
std::string const& data
|
||||
std::string const& replyID,
|
||||
nlohmann::json const& data
|
||||
) {
|
||||
std::string msg = senderID + "/" + data;
|
||||
log::debug("Replying msg: {}", msg);
|
||||
auto msgJson = nlohmann::json::object();
|
||||
msgJson["reply"] = replyID;
|
||||
msgJson["data"] = data;
|
||||
auto msg = msgJson.dump();
|
||||
|
||||
DWORD written;
|
||||
WriteFile(rawPipeHandle, msg.c_str(), msg.size(), &written, nullptr);
|
||||
}
|
||||
|
||||
// todo: multiple connections
|
||||
void InternalLoader::setupIPC() {
|
||||
auto pipe = CreateNamedPipeA(
|
||||
IPC_PIPE_NAME,
|
||||
|
@ -68,29 +72,27 @@ void InternalLoader::setupIPC() {
|
|||
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;
|
||||
}
|
||||
try {
|
||||
auto json = nlohmann::json::parse(buffer);
|
||||
if (!json.contains("mod") || !json["mod"].is_string()) {
|
||||
log::warn("Received IPC message without 'mod' field");
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (modID.size() && senderID.size() && msgID.size()) {
|
||||
IPCEvent(pipe, modID, senderID, msgID, data).post();
|
||||
} else {
|
||||
log::warn("Received invalid IPC message: '{}'", buffer);
|
||||
if (!json.contains("message") || !json["message"].is_string()) {
|
||||
log::warn("Received IPC message without 'message' field");
|
||||
continue;
|
||||
}
|
||||
std::optional<std::string> reply = std::nullopt;
|
||||
if (json.contains("reply") && json["reply"].is_string()) {
|
||||
reply = json["reply"];
|
||||
}
|
||||
nlohmann::json data;
|
||||
if (json.contains("data")) {
|
||||
data = json["data"];
|
||||
}
|
||||
IPCEvent(pipe, json["mod"], json["message"], reply, data).post();
|
||||
} catch(...) {
|
||||
log::warn("Received IPC message that isn't valid JSON");
|
||||
}
|
||||
}
|
||||
log::debug("Connection done");
|
||||
|
|
|
@ -5,18 +5,22 @@ USE_GEODE_NAMESPACE();
|
|||
|
||||
IPCEvent::IPCEvent(
|
||||
void* rawPipeHandle,
|
||||
std::string targetModID,
|
||||
std::string senderID,
|
||||
std::string messageID,
|
||||
std::string messageData
|
||||
std::string const& targetModID,
|
||||
std::string const& messageID,
|
||||
std::optional<std::string> const& replyID,
|
||||
nlohmann::json const& messageData
|
||||
) : m_rawPipeHandle(rawPipeHandle),
|
||||
m_targetModID(targetModID),
|
||||
m_senderID(senderID),
|
||||
m_messageID(messageID),
|
||||
m_replyID(replyID),
|
||||
m_messageData(messageData) {}
|
||||
|
||||
std::string IPCEvent::getSenderID() const {
|
||||
return m_senderID;
|
||||
IPCEvent::~IPCEvent() {
|
||||
this->reply(nullptr);
|
||||
}
|
||||
|
||||
std::optional<std::string> IPCEvent::getReplyID() const {
|
||||
return m_replyID;
|
||||
}
|
||||
|
||||
std::string IPCEvent::getTargetModID() const {
|
||||
|
@ -27,13 +31,20 @@ std::string IPCEvent::getMessageID() const {
|
|||
return m_messageID;
|
||||
}
|
||||
|
||||
std::string IPCEvent::getMessageData() const {
|
||||
nlohmann::json IPCEvent::getMessageData() const {
|
||||
return m_messageData;
|
||||
}
|
||||
|
||||
void IPCEvent::reply(std::string const& data) {
|
||||
if (m_rawPipeHandle) {
|
||||
InternalLoader::get()->postIPCMessage(m_rawPipeHandle, m_senderID, data);
|
||||
bool IPCEvent::canReply() const {
|
||||
return m_replyID.has_value();
|
||||
}
|
||||
|
||||
void IPCEvent::reply(nlohmann::json const& data) {
|
||||
if (!m_replied && m_rawPipeHandle && m_replyID.has_value()) {
|
||||
InternalLoader::get()->postIPCReply(
|
||||
m_rawPipeHandle, m_replyID.value(), data
|
||||
);
|
||||
m_replied = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@ Result<ModInfo> ModInfo::createFromSchemaV010(ModJson const& rawJson) {
|
|||
ModInfo info;
|
||||
|
||||
auto json = rawJson;
|
||||
info.m_rawJSON = rawJson;
|
||||
|
||||
#define PROPAGATE(err) \
|
||||
{ \
|
||||
|
@ -290,3 +291,18 @@ std::vector<std::pair<std::string, std::optional<std::string>*>> ModInfo::getSpe
|
|||
{ "support.md", &m_supportInfo },
|
||||
};
|
||||
}
|
||||
|
||||
ModJson ModInfo::toJSON() const {
|
||||
auto json = m_rawJSON;
|
||||
json["path"] = m_path;
|
||||
json["binary"] = m_binaryName;
|
||||
return json;
|
||||
}
|
||||
|
||||
ModJson ModInfo::getRawJSON() const {
|
||||
return m_rawJSON;
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json& json, ModInfo const& info) {
|
||||
json = info.toJSON();
|
||||
}
|
||||
|
|
|
@ -102,7 +102,7 @@ BOOL WINAPI DllMain(HINSTANCE lib, DWORD reason, LPVOID) {
|
|||
|
||||
#define $_ GEODE_CONCAT(unnamedVar_, __LINE__)
|
||||
|
||||
static auto $_ = listenForSettingChanges(
|
||||
static auto $_ = listenForSettingChanges<BoolSetting>(
|
||||
"show-platform-console",
|
||||
[](BoolSetting* setting) {
|
||||
if (setting->getValue()) {
|
||||
|
@ -119,16 +119,11 @@ static auto $_ = listenForIPC("ipc-test", +[](IPCEvent* event) {
|
|||
});
|
||||
|
||||
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() + "\"";
|
||||
}
|
||||
), ", "
|
||||
) + " ]"
|
||||
);
|
||||
event->reply(ranges::map<std::vector<nlohmann::json>>(
|
||||
Loader::get()->getAllMods(), [](Mod* mod) {
|
||||
return mod->getModInfo().toJSON();
|
||||
}
|
||||
));
|
||||
});
|
||||
|
||||
int geodeEntry(void* platformData) {
|
||||
|
|
|
@ -105,6 +105,10 @@ bool VersionInfo::match(
|
|||
return false;
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json& json, VersionInfo const& info) {
|
||||
json = info.toString();
|
||||
}
|
||||
|
||||
std::string VersionInfo::toString() const {
|
||||
return "v" + std::to_string(this->m_major) + "." + std::to_string(this->m_minor) + "." +
|
||||
std::to_string(this->m_patch);
|
||||
|
|
Loading…
Add table
Reference in a new issue