update IPC to use json

This commit is contained in:
HJfod 2022-11-23 00:35:08 +02:00
parent 384efb32bb
commit 7a047e2a08
12 changed files with 140 additions and 68 deletions

View file

@ -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> {

View file

@ -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

View file

@ -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(), [=]() {

View file

@ -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;

View file

@ -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
);
/**

View file

@ -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

View file

@ -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

View file

@ -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");

View file

@ -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;
}
}

View file

@ -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();
}

View file

@ -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) {

View file

@ -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);