summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Gavrilov2012-03-14 19:57:29 +0400
committerAlexander Gavrilov2012-03-14 19:57:29 +0400
commit560e977f0589ac1c0feb6ea825d20d351e325826 (patch)
treee32bf2135261fa1e9129fae4c2c7ae068b6ef4cc
parentc42e2ff053bc3acbded353112cd6412c8211f279 (diff)
downloaddfhack-560e977f0589ac1c0feb6ea825d20d351e325826.tar.gz
dfhack-560e977f0589ac1c0feb6ea825d20d351e325826.tar.bz2
dfhack-560e977f0589ac1c0feb6ea825d20d351e325826.tar.xz
Implement trivial RPC interface for dfhack via TCP & protobufs.
Use it to make an executable capable of calling commands remotely.
-rw-r--r--CMakeLists.txt6
-rw-r--r--library/CMakeLists.txt21
-rw-r--r--library/ColorText.cpp23
-rw-r--r--library/Core.cpp8
-rw-r--r--library/MiscUtils.cpp1
-rw-r--r--library/RemoteClient.cpp359
-rw-r--r--library/RemoteServer.cpp359
-rw-r--r--library/dfhack-run.cpp90
-rw-r--r--library/include/ColorText.h30
-rw-r--r--library/include/Core.h4
-rw-r--r--library/include/PluginManager.h11
-rw-r--r--library/include/RemoteClient.h169
-rw-r--r--library/include/RemoteServer.h189
-rw-r--r--library/proto/CoreProtocol.proto14
-rw-r--r--plugins/Plugins.cmake1
15 files changed, 1260 insertions, 25 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c34202f2..4d8977bf 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -104,6 +104,12 @@ ENDIF()
# use shared libraries for protobuf
ADD_DEFINITIONS(-DPROTOBUF_USE_DLLS)
+if(UNIX)
+ add_definitions(-D_LINUX)
+elseif(WIN32)
+ add_definitions(-DWIN32)
+endif()
+
#### expose depends ####
# find and make available libz
diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt
index 0521184c..cef56d4d 100644
--- a/library/CMakeLists.txt
+++ b/library/CMakeLists.txt
@@ -10,6 +10,7 @@ IF(UNIX)
ENDIF()
include_directories (include)
+include_directories (proto)
SET(PERL_EXECUTABLE "perl" CACHE FILEPATH "This is the perl executable to run in the codegen step. Tweak it if you need to run a specific one.")
@@ -41,6 +42,8 @@ include/Types.h
include/VersionInfo.h
include/VersionInfoFactory.h
include/Virtual.h
+include/RemoteClient.h
+include/RemoteServer.h
)
SET(MAIN_HEADERS_WINDOWS
@@ -58,6 +61,8 @@ PluginManager.cpp
TileTypes.cpp
VersionInfoFactory.cpp
Virtual.cpp
+RemoteClient.cpp
+RemoteServer.cpp
)
SET(MAIN_SOURCES_WINDOWS
@@ -158,7 +163,7 @@ STRING(REPLACE ".proto" ".pb.cc" PROJECT_PROTO_SRCS ${PROJECT_PROTOS})
STRING(REPLACE ".proto" ".pb.h" PROJECT_PROTO_HDRS ${PROJECT_PROTOS})
LIST(APPEND PROJECT_HEADERS ${PROJECT_PROTO_HDRS})
-LIST(APPEND PROJECT_HEADERS ${PROJECT_PROTO_SRCS})
+LIST(APPEND PROJECT_SOURCES ${PROJECT_PROTO_SRCS})
ADD_CUSTOM_COMMAND(
OUTPUT ${PROJECT_PROTO_SRCS} ${PROJECT_PROTO_HDRS}
@@ -211,6 +216,11 @@ ENDIF()
ADD_LIBRARY(dfhack SHARED ${PROJECT_SOURCES})
ADD_DEPENDENCIES(dfhack generate_headers)
+ADD_EXECUTABLE(dfhack-run dfhack-run.cpp
+ RemoteClient.cpp ColorText.cpp MiscUtils.cpp
+ ${PROJECT_PROTO_SRCS})
+ADD_DEPENDENCIES(dfhack-run dfhack)
+
IF(BUILD_EGGY)
SET_TARGET_PROPERTIES(dfhack PROPERTIES OUTPUT_NAME "egg" )
else()
@@ -221,8 +231,10 @@ endif()
IF(WIN32)
SET_TARGET_PROPERTIES(dfhack PROPERTIES COMPILE_FLAGS "/FI\"Export.h\"" )
+ SET_TARGET_PROPERTIES(dfhack-run PROPERTIES COMPILE_FLAGS "/FI\"Export.h\"" )
ELSE()
SET_TARGET_PROPERTIES(dfhack PROPERTIES COMPILE_FLAGS "-include Export.h" )
+ SET_TARGET_PROPERTIES(dfhack-run PROPERTIES COMPILE_FLAGS "-include Export.h" )
ENDIF()
#effectively disables debug builds...
@@ -231,6 +243,8 @@ SET_TARGET_PROPERTIES(dfhack PROPERTIES DEBUG_POSTFIX "-debug" )
TARGET_LINK_LIBRARIES(dfhack protobuf-lite clsocket ${PROJECT_LIBS})
SET_TARGET_PROPERTIES(dfhack PROPERTIES LINK_INTERFACE_LIBRARIES "")
+TARGET_LINK_LIBRARIES(dfhack-run protobuf-lite clsocket)
+
IF(UNIX)
# On linux, copy our version of the df launch script which sets LD_PRELOAD
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack
@@ -253,6 +267,7 @@ else()
LIBRARY DESTINATION ${DFHACK_EGGY_DESTINATION}
RUNTIME DESTINATION ${DFHACK_EGGY_DESTINATION})
endif()
+
#install the offset file
install(FILES xml/symbols.xml
DESTINATION ${DFHACK_DATA_DESTINATION}) #linux: share/dfhack
@@ -260,6 +275,10 @@ install(FILES xml/symbols.xml
install(FILES ../dfhack.init-example
DESTINATION ${DFHACK_BINARY_DESTINATION})
+install(TARGETS dfhack-run
+ LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION}
+ RUNTIME DESTINATION ${DFHACK_LIBRARY_DESTINATION})
+
# Unused for so long that it's not even relevant now...
if(BUILD_DEVEL)
if(WIN32)
diff --git a/library/ColorText.cpp b/library/ColorText.cpp
index fa5d57e0..c399504b 100644
--- a/library/ColorText.cpp
+++ b/library/ColorText.cpp
@@ -143,6 +143,16 @@ void color_ostream::reset_color(void)
color(COLOR_RESET);
}
+void color_ostream_wrapper::add_text(color_value, const std::string &text)
+{
+ out << text;
+}
+
+void color_ostream_wrapper::flush_proxy()
+{
+ out << std::flush;
+}
+
void buffered_color_ostream::add_text(color_value color, const std::string &text)
{
if (text.empty())
@@ -165,23 +175,20 @@ void buffered_color_ostream::add_text(color_value color, const std::string &text
void color_ostream_proxy::flush_proxy()
{
- if (!buffer.empty())
+ if (buffer.empty())
+ return;
+
+ if (target)
{
target->begin_batch();
for (auto it = buffer.begin(); it != buffer.end(); ++it)
target->add_text(it->first, it->second);
- buffer.clear();
-
target->end_batch();
}
-}
-color_ostream_proxy::color_ostream_proxy(color_ostream &target)
- : target(&target)
-{
- //
+ buffer.clear();
}
color_ostream_proxy::~color_ostream_proxy()
diff --git a/library/Core.cpp b/library/Core.cpp
index 9a005d33..0765f4cd 100644
--- a/library/Core.cpp
+++ b/library/Core.cpp
@@ -48,6 +48,7 @@ using namespace std;
#include "modules/World.h"
#include "modules/Graphic.h"
#include "modules/Windows.h"
+#include "RemoteServer.h"
using namespace DFHack;
#include "df/ui.h"
@@ -570,6 +571,7 @@ Core::Core()
last_world_data_ptr = NULL;
top_viewscreen = NULL;
screen_window = NULL;
+ server = NULL;
};
void Core::fatal (std::string output, bool deactivate)
@@ -678,6 +680,12 @@ bool Core::Init()
screen_window = new Windows::top_level_window();
screen_window->addChild(new Windows::dfhack_dummy(5,10));
started = true;
+
+ cerr << "Starting the TCP listener.\n";
+ server = new ServerMain();
+ if (!server->listen(RemoteClient::GetDefaultPort()))
+ cerr << "TCP listen failed.\n";
+
cerr << "DFHack is running.\n";
return true;
}
diff --git a/library/MiscUtils.cpp b/library/MiscUtils.cpp
index aa141313..dff8af0a 100644
--- a/library/MiscUtils.cpp
+++ b/library/MiscUtils.cpp
@@ -24,7 +24,6 @@ distribution.
#include "Internal.h"
#include "Export.h"
-#include "Core.h"
#include "MiscUtils.h"
#ifndef LINUX_BUILD
diff --git a/library/RemoteClient.cpp b/library/RemoteClient.cpp
new file mode 100644
index 00000000..a8c80466
--- /dev/null
+++ b/library/RemoteClient.cpp
@@ -0,0 +1,359 @@
+/*
+https://github.com/peterix/dfhack
+Copyright (c) 2011 Petr Mrázek <peterix@gmail.com>
+
+A thread-safe logging console with a line editor for windows.
+
+Based on linenoise win32 port,
+copyright 2010, Jon Griffiths <jon_p_griffiths at yahoo dot com>.
+All rights reserved.
+Based on linenoise, copyright 2010, Salvatore Sanfilippo <antirez at gmail dot com>.
+The original linenoise can be found at: http://github.com/antirez/linenoise
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of Redis nor the names of its contributors may be used
+ to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+#include <stdarg.h>
+#include <errno.h>
+#include <stdio.h>
+#include <iostream>
+#include <fstream>
+#include <istream>
+#include <string>
+
+#include "RemoteClient.h"
+#include "MiscUtils.h"
+
+#include <cstdio>
+#include <cstdlib>
+#include <sstream>
+
+#include <memory>
+
+using namespace DFHack;
+
+#include "tinythread.h"
+using namespace tthread;
+
+using dfproto::CoreTextNotification;
+
+using google::protobuf::MessageLite;
+
+const char RPCHandshakeHeader::REQUEST_MAGIC[9] = "DFHack?\n";
+const char RPCHandshakeHeader::RESPONSE_MAGIC[9] = "DFHack!\n";
+
+void color_ostream_proxy::decode(CoreTextNotification *data)
+{
+ flush_proxy();
+
+ int cnt = data->fragments_size();
+ if (cnt > 0) {
+ target->begin_batch();
+
+ for (int i = 0; i < cnt; i++)
+ {
+ auto &frag = data->fragments(i);
+
+ color_value color = frag.has_color() ? color_value(frag.color()) : COLOR_RESET;
+ target->add_text(color, frag.text());
+ }
+
+ target->end_batch();
+ }
+}
+
+RemoteClient::RemoteClient()
+{
+ active = false;
+}
+
+RemoteClient::~RemoteClient()
+{
+ disconnect();
+}
+
+bool DFHack::readFullBuffer(CSimpleSocket &socket, void *buf, int size)
+{
+ if (!socket.IsSocketValid())
+ return false;
+
+ char *ptr = (char*)buf;
+ while (size > 0) {
+ int cnt = socket.Receive(size);
+ if (cnt <= 0)
+ return false;
+ memcpy(ptr, socket.GetData(), cnt);
+ ptr += cnt;
+ size -= cnt;
+ }
+
+ return true;
+}
+
+int RemoteClient::GetDefaultPort()
+{
+ const char *port = getenv("DFHACK_PORT");
+ if (!port) port = "0";
+
+ int portval = atoi(port);
+ if (portval <= 0)
+ return 5000;
+ else
+ return portval;
+}
+
+bool RemoteClient::connect(int port)
+{
+ assert(!active);
+
+ if (port <= 0)
+ port = GetDefaultPort();
+
+ if (!socket.Initialize())
+ {
+ std::cerr << "Socket init failed." << endl;
+ return false;
+ }
+
+ if (!socket.Open((const uint8 *)"localhost", port))
+ {
+ std::cerr << "Could not connect to localhost:" << port << endl;
+ return false;
+ }
+
+ active = true;
+
+ RPCHandshakeHeader header;
+ memcpy(header.magic, RPCHandshakeHeader::REQUEST_MAGIC, sizeof(header.magic));
+ header.version = 1;
+
+ if (socket.Send((uint8*)&header, sizeof(header)) != sizeof(header))
+ {
+ std::cerr << "Could not send header." << endl;
+ socket.Close();
+ return active = false;
+ }
+
+ if (!readFullBuffer(socket, &header, sizeof(header)))
+ {
+ std::cerr << "Could not read header." << endl;
+ socket.Close();
+ return active = false;
+ }
+
+ if (memcmp(header.magic, RPCHandshakeHeader::RESPONSE_MAGIC, sizeof(header.magic)) ||
+ header.version != 1)
+ {
+ std::cerr << "Invalid handshake response." << endl;
+ socket.Close();
+ return active = false;
+ }
+
+ bind_call.name = "BindMethod";
+ bind_call.p_client = this;
+ bind_call.id = 0;
+
+ return true;
+}
+
+void RemoteClient::disconnect()
+{
+ if (active && socket.IsSocketValid())
+ {
+ RPCMessageHeader header;
+ header.id = RPC_REQUEST_QUIT;
+ header.size = 0;
+ if (socket.Send((uint8_t*)&header, sizeof(header)) != sizeof(header))
+ std::cerr << "Could not send the disconnect message." << endl;
+ }
+
+ socket.Close();
+}
+
+bool RemoteClient::bind(color_ostream &out, RemoteFunctionBase *function,
+ const std::string &name, const std::string &proto)
+{
+ if (!active || !socket.IsSocketValid())
+ return false;
+
+ bind_call.reset();
+
+ {
+ auto in = bind_call.in();
+
+ in->set_method(name);
+ if (!proto.empty())
+ in->set_plugin(proto);
+ in->set_input_msg(function->p_in_template->GetTypeName());
+ in->set_output_msg(function->p_out_template->GetTypeName());
+ }
+
+ if (bind_call.execute(out) != CR_OK)
+ return false;
+
+ function->p_client = this;
+ function->id = bind_call.out()->assigned_id();
+
+ return true;
+}
+
+void RPCFunctionBase::reset(bool free)
+{
+ if (free)
+ {
+ delete p_in;
+ delete p_out;
+ p_in = p_out = NULL;
+ }
+ else
+ {
+ if (p_in)
+ p_in->Clear();
+ if (p_out)
+ p_out->Clear();
+ }
+}
+
+bool RemoteFunctionBase::bind(color_ostream &out, RemoteClient *client,
+ const std::string &name, const std::string &proto)
+{
+ if (p_client == client)
+ return true;
+
+ if (p_client)
+ {
+ out.printerr("Function already bound to %s::%s\n",
+ this->proto.c_str(), this->name.c_str());
+ return false;
+ }
+
+ this->name = name;
+ this->proto = proto;
+
+ return client->bind(out, this, name, proto);
+}
+
+bool DFHack::sendRemoteMessage(CSimpleSocket &socket, int16_t id, const MessageLite *msg)
+{
+ int size = msg->ByteSize();
+ int fullsz = size + sizeof(RPCMessageHeader);
+
+ std::auto_ptr<uint8_t> data(new uint8_t[fullsz]);
+ RPCMessageHeader *hdr = (RPCMessageHeader*)data.get();
+
+ hdr->id = id;
+ hdr->size = size;
+
+ if (!msg->SerializeToArray(data.get() + sizeof(RPCMessageHeader), size))
+ return false;
+
+ return (socket.Send(data.get(), fullsz) == fullsz);
+}
+
+command_result RemoteFunctionBase::execute(color_ostream &out,
+ const message_type *input, message_type *output)
+{
+ if (!p_client)
+ {
+ out.printerr("Calling an unbound RPC function.\n");
+ return CR_NOT_IMPLEMENTED;
+ }
+
+ if (!p_client->socket.IsSocketValid())
+ {
+ out.printerr("In call to %s::%s: invalid socket.\n",
+ this->proto.c_str(), this->name.c_str());
+ return CR_FAILURE;
+ }
+
+ if (!sendRemoteMessage(p_client->socket, id, input))
+ {
+ out.printerr("In call to %s::%s: I/O error in send.\n",
+ this->proto.c_str(), this->name.c_str());
+ return CR_FAILURE;
+ }
+
+ color_ostream_proxy text_decoder(out);
+ CoreTextNotification text_data;
+
+ output->Clear();
+
+ for (;;) {
+ RPCMessageHeader header;
+
+ if (!readFullBuffer(p_client->socket, &header, sizeof(header)))
+ {
+ out.printerr("In call to %s::%s: I/O error in receive header.\n",
+ this->proto.c_str(), this->name.c_str());
+ return CR_FAILURE;
+ }
+
+ //out.print("Received %d:%d\n", header.id, header.size);
+
+ if (header.id == RPC_REPLY_FAIL)
+ return header.size == CR_OK ? CR_FAILURE : command_result(header.size);
+
+ if (header.size < 0 || header.size > 2*1048576)
+ {
+ out.printerr("In call to %s::%s: invalid received size %d.\n",
+ this->proto.c_str(), this->name.c_str(), header.size);
+ return CR_FAILURE;
+ }
+
+ std::auto_ptr<uint8_t> buf(new uint8_t[header.size]);
+
+ if (!readFullBuffer(p_client->socket, buf.get(), header.size))
+ {
+ out.printerr("In call to %s::%s: I/O error in receive %d bytes of data.\n",
+ this->proto.c_str(), this->name.c_str(), header.size);
+ return CR_FAILURE;
+ }
+
+ switch (header.id) {
+ case RPC_REPLY_RESULT:
+ if (!output->ParseFromArray(buf.get(), header.size))
+ {
+ out.printerr("In call to %s::%s: error parsing received result.\n",
+ this->proto.c_str(), this->name.c_str());
+ return CR_FAILURE;
+ }
+
+ return CR_OK;
+
+ case RPC_REPLY_TEXT:
+ text_data.Clear();
+ if (text_data.ParseFromArray(buf.get(), header.size))
+ text_decoder.decode(&text_data);
+ else
+ out.printerr("In call to %s::%s: received invalid text data.\n",
+ this->proto.c_str(), this->name.c_str());
+ break;
+
+ default:
+ break;
+ }
+ }
+}
diff --git a/library/RemoteServer.cpp b/library/RemoteServer.cpp
new file mode 100644
index 00000000..5b082570
--- /dev/null
+++ b/library/RemoteServer.cpp
@@ -0,0 +1,359 @@
+/*
+https://github.com/peterix/dfhack
+Copyright (c) 2011 Petr Mrázek <peterix@gmail.com>
+
+A thread-safe logging console with a line editor for windows.
+
+Based on linenoise win32 port,
+copyright 2010, Jon Griffiths <jon_p_griffiths at yahoo dot com>.
+All rights reserved.
+Based on linenoise, copyright 2010, Salvatore Sanfilippo <antirez at gmail dot com>.
+The original linenoise can be found at: http://github.com/antirez/linenoise
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of Redis nor the names of its contributors may be used
+ to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+#include <stdarg.h>
+#include <errno.h>
+#include <stdio.h>
+#include <iostream>
+#include <fstream>
+#include <istream>
+#include <string>
+
+#include "RemoteServer.h"
+#include "PluginManager.h"
+#include "MiscUtils.h"
+
+#include <cstdio>
+#include <cstdlib>
+#include <sstream>
+
+#include <memory>
+
+using namespace DFHack;
+
+#include "tinythread.h"
+using namespace tthread;
+
+using dfproto::CoreTextNotification;
+using dfproto::CoreTextFragment;
+using google::protobuf::MessageLite;
+
+CoreService::CoreService() {
+ // This must be the first method, so that it gets id 0
+ addMethod("BindMethod", &CoreService::BindMethod);
+
+ addMethod("RunCommand", &CoreService::RunCommand);
+}
+
+command_result CoreService::BindMethod(color_ostream &stream,
+ const dfproto::CoreBindRequest *in,
+ dfproto::CoreBindReply *out)
+{
+ ServerFunctionBase *fn = connection()->findFunction(in->plugin(), in->method());
+
+ if (!fn)
+ {
+ stream.printerr("RPC method not found: %s::%s\n",
+ in->plugin().c_str(), in->method().c_str());
+ return CR_FAILURE;
+ }
+
+ if (fn->p_in_template->GetTypeName() != in->input_msg() ||
+ fn->p_out_template->GetTypeName() != in->output_msg())
+ {
+ stream.printerr("Requested wrong signature for RPC method: %s::%s\n",
+ in->plugin().c_str(), in->method().c_str());
+ return CR_FAILURE;
+ }
+
+ out->set_assigned_id(fn->getId());
+ return CR_OK;
+}
+
+command_result CoreService::RunCommand(color_ostream &stream,
+ const dfproto::CoreRunCommandRequest *in,
+ CoreVoidReply*)
+{
+ std::string cmd = in->command();
+ std::vector<std::string> args;
+ for (int i = 0; i < in->arguments_size(); i++)
+ args.push_back(in->arguments(i));
+
+ return Core::getInstance().plug_mgr->InvokeCommand(stream, cmd, args, false);
+}
+
+RPCService::RPCService()
+{
+ owner = NULL;
+ holder = NULL;
+}
+
+RPCService::~RPCService()
+{
+ for (size_t i = 0; i < functions.size(); i++)
+ delete functions[i];
+}
+
+ServerFunctionBase *RPCService::getFunction(const std::string &name)
+{
+ assert(owner);
+ return lookup[name];
+}
+
+void RPCService::finalize(ServerConnection *owner, std::vector<ServerFunctionBase*> *ftable)
+{
+ this->owner = owner;
+
+ for (size_t i = 0; i < functions.size(); i++)
+ {
+ auto fn = functions[i];
+
+ fn->id = (int16_t)ftable->size();
+ ftable->push_back(fn);
+
+ lookup[fn->name] = fn;
+ }
+}
+
+ServerConnection::ServerConnection(CActiveSocket *socket)
+ : socket(socket), stream(this)
+{
+ in_error = false;
+
+ core_service = new CoreService();
+ core_service->finalize(this, &functions);
+
+ thread = new tthread::thread(threadFn, (void*)this);
+}
+
+ServerConnection::~ServerConnection()
+{
+ in_error = true;
+ socket->Close();
+ delete socket;
+
+ for (auto it = plugin_services.begin(); it != plugin_services.end(); ++it)
+ delete it->second;
+
+ delete core_service;
+}
+
+ServerFunctionBase *ServerConnection::findFunction(const std::string &plugin, const std::string &name)
+{
+ if (plugin.empty())
+ return core_service->getFunction(name);
+ else
+ return NULL; // todo: add plugin api support
+}
+
+void ServerConnection::connection_ostream::flush_proxy()
+{
+ if (owner->in_error)
+ {
+ buffer.clear();
+ return;
+ }
+
+ if (buffer.empty())
+ return;
+
+ CoreTextNotification msg;
+
+ for (auto it = buffer.begin(); it != buffer.end(); ++it)
+ {
+ auto frag = msg.add_fragments();
+ frag->set_text(it->second);
+ if (it->first >= 0)
+ frag->set_color(CoreTextFragment::Color(it->first));
+ }
+
+ buffer.clear();
+
+ if (!sendRemoteMessage(*owner->socket, RPC_REPLY_TEXT, &msg))
+ {
+ owner->in_error = true;
+ Core::printerr("Error writing text into client socket.\n");
+ }
+}
+
+void ServerConnection::threadFn(void *arg)
+{
+ ServerConnection *me = (ServerConnection*)arg;
+ color_ostream_proxy out(Core::getInstance().getConsole());
+
+ /* Handshake */
+
+ {
+ RPCHandshakeHeader header;
+
+ if (!readFullBuffer(*me->socket, &header, sizeof(header)))
+ {
+ out << "In RPC server: could not read handshake header." << endl;
+ delete me;
+ return;
+ }
+
+ if (memcmp(header.magic, RPCHandshakeHeader::REQUEST_MAGIC, sizeof(header.magic)) ||
+ header.version != 1)
+ {
+ out << "In RPC server: invalid handshake header." << endl;
+ delete me;
+ return;
+ }
+
+ memcpy(header.magic, RPCHandshakeHeader::RESPONSE_MAGIC, sizeof(header.magic));
+ header.version = 1;
+
+ if (me->socket->Send((uint8*)&header, sizeof(header)) != sizeof(header))
+ {
+ out << "In RPC server: could not send handshake response." << endl;
+ delete me;
+ return;
+ }
+ }
+
+ /* Processing */
+
+ std::cerr << "Client connection established." << endl;
+
+ while (!me->in_error) {
+ RPCMessageHeader header;
+
+ if (!readFullBuffer(*me->socket, &header, sizeof(header)))
+ {
+ out.printerr("In RPC server: I/O error in receive header.\n");
+ break;
+ }
+
+ if (header.id == RPC_REQUEST_QUIT)
+ break;
+
+ if (header.size < 0 || header.size > 2*1048576)
+ {
+ out.printerr("In RPC server: invalid received size %d.\n", header.size);
+ break;
+ }
+
+ std::auto_ptr<uint8_t> buf(new uint8_t[header.size]);
+
+ if (!readFullBuffer(*me->socket, buf.get(), header.size))
+ {
+ out.printerr("In RPC server: I/O error in receive %d bytes of data.\n", header.size);
+ break;
+ }
+
+ //out.print("Handling %d:%d\n", header.id, header.size);
+
+ ServerFunctionBase *fn = vector_get(me->functions, header.id);
+ MessageLite *reply = NULL;
+ command_result res = CR_FAILURE;
+
+ if (!fn)
+ {
+ me->stream.printerr("RPC call of invalid id %d\n", header.id);
+ }
+ else
+ {
+ if (!fn->in()->ParseFromArray(buf.get(), header.size))
+ {
+ me->stream.printerr("In call to %s: could not decode input args.\n", fn->name);
+ }
+ else
+ {
+ reply = fn->out();
+ res = fn->execute(me->stream);
+ }
+ }
+
+ if (me->in_error)
+ break;
+
+ me->stream.flush();
+
+ //out.print("Answer %d:%d\n", res, reply);
+
+ if (res == CR_OK && reply)
+ {
+ if (!sendRemoteMessage(*me->socket, RPC_REPLY_RESULT, reply))
+ {
+ out.printerr("In RPC server: I/O error in send result.\n");
+ break;
+ }
+ }
+ else
+ {
+ header.id = RPC_REPLY_FAIL;
+ header.size = res;
+
+ if (me->socket->Send((uint8_t*)&header, sizeof(header)) != sizeof(header))
+ {
+ out.printerr("In RPC server: I/O error in send failure code.\n");
+ break;
+ }
+ }
+ }
+
+ std::cerr << "Shutting down client connection." << endl;
+
+ delete me;
+}
+
+ServerMain::ServerMain()
+{
+ thread = NULL;
+}
+
+ServerMain::~ServerMain()
+{
+ socket.Close();
+}
+
+bool ServerMain::listen(int port)
+{
+ if (thread)
+ return true;
+
+ socket.Initialize();
+
+ if (!socket.Listen((const uint8 *)"127.0.0.1", port))
+ return false;
+
+ thread = new tthread::thread(threadFn, this);
+ return true;
+}
+
+void ServerMain::threadFn(void *arg)
+{
+ ServerMain *me = (ServerMain*)arg;
+ CActiveSocket *client;
+
+ while ((client = me->socket.Accept()) != NULL)
+ {
+ new ServerConnection(client);
+ }
+}
diff --git a/library/dfhack-run.cpp b/library/dfhack-run.cpp
new file mode 100644
index 00000000..941ce8d0
--- /dev/null
+++ b/library/dfhack-run.cpp
@@ -0,0 +1,90 @@
+/*
+https://github.com/peterix/dfhack
+Copyright (c) 2011 Petr Mrázek <peterix@gmail.com>
+
+A thread-safe logging console with a line editor for windows.
+
+Based on linenoise win32 port,
+copyright 2010, Jon Griffiths <jon_p_griffiths at yahoo dot com>.
+All rights reserved.
+Based on linenoise, copyright 2010, Salvatore Sanfilippo <antirez at gmail dot com>.
+The original linenoise can be found at: http://github.com/antirez/linenoise
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ * Neither the name of Redis nor the names of its contributors may be used
+ to endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+*/
+
+
+#include <stdarg.h>
+#include <errno.h>
+#include <stdio.h>
+#include <iostream>
+#include <fstream>
+#include <istream>
+#include <string>
+
+#include "RemoteClient.h"
+
+#include <cstdio>
+#include <cstdlib>
+#include <sstream>
+
+#include <memory>
+
+using namespace DFHack;
+using namespace dfproto;
+using std::cout;
+
+int main (int argc, char *argv[])
+{
+ color_ostream_wrapper out(cout);
+
+ if (argc <= 1)
+ {
+ fprintf(stderr, "Usage: dfhack-run <command> [args...]\n");
+ return 2;
+ }
+
+ // Connect to DFHack
+ RemoteClient client;
+ if (!client.connect())
+ return 2;
+
+ // Bind to RunCommand
+ RemoteFunction<CoreRunCommandRequest,CoreVoidReply> command;
+
+ if (!command.bind(out, &client, "RunCommand"))
+ return 2;
+
+ // Execute it
+ command.in()->set_command(argv[1]);
+ for (int i = 2; i < argc; i++)
+ command.in()->add_arguments(argv[i]);
+
+ if (command.execute(out) != CR_OK)
+ return 1;
+
+ out.flush();
+ return 0;
+}
diff --git a/library/include/ColorText.h b/library/include/ColorText.h
index cdeaa394..651f5831 100644
--- a/library/include/ColorText.h
+++ b/library/include/ColorText.h
@@ -34,6 +34,11 @@ distribution.
#include <stdarg.h>
#include <sstream>
+namespace dfproto
+{
+ class CoreTextNotification;
+}
+
namespace DFHack
{
class DFHACK_EXPORT color_ostream : public std::ostream
@@ -112,6 +117,25 @@ namespace DFHack
void reset_color(void);
virtual bool is_console() { return false; }
+ virtual color_ostream *proxy_target() { return NULL; }
+ };
+
+ inline color_ostream &operator << (color_ostream &out, color_ostream::color_value clr)
+ {
+ out.color(clr);
+ return out;
+ }
+
+ class DFHACK_EXPORT color_ostream_wrapper : public color_ostream
+ {
+ std::ostream &out;
+
+ protected:
+ virtual void add_text(color_value color, const std::string &text);
+ virtual void flush_proxy();
+
+ public:
+ color_ostream_wrapper(std::ostream &os) : out(os) {}
};
class DFHACK_EXPORT buffered_color_ostream : public color_ostream
@@ -139,7 +163,11 @@ namespace DFHack
virtual void flush_proxy();
public:
- color_ostream_proxy(color_ostream &target);
+ color_ostream_proxy(color_ostream &target) : target(&target) {}
~color_ostream_proxy();
+
+ virtual color_ostream *proxy_target() { return target; }
+
+ void decode(dfproto::CoreTextNotification *data);
};
}
diff --git a/library/include/Core.h b/library/include/Core.h
index 321939c5..1705bcc1 100644
--- a/library/include/Core.h
+++ b/library/include/Core.h
@@ -60,6 +60,7 @@ namespace DFHack
class VersionInfoFactory;
class PluginManager;
class Core;
+ class ServerMain;
namespace Windows
{
class df_window;
@@ -195,6 +196,9 @@ namespace DFHack
tthread::mutex * misc_data_mutex;
std::map<std::string,void*> misc_data_map;
+
+ friend class CoreService;
+ ServerMain *server;
};
class CoreSuspender {
diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h
index 8e8f7760..e617814c 100644
--- a/library/include/PluginManager.h
+++ b/library/include/PluginManager.h
@@ -30,6 +30,9 @@ distribution.
#include <map>
#include <string>
#include <vector>
+
+#include "RemoteClient.h"
+
struct DFLibrary;
namespace tthread
{
@@ -46,14 +49,6 @@ namespace DFHack
class PluginManager;
class virtual_identity;
- enum command_result
- {
- CR_WOULD_BREAK = -2,
- CR_NOT_IMPLEMENTED = -1,
- CR_FAILURE = 0,
- CR_OK = 1,
- CR_WRONG_USAGE = 2
- };
enum state_change_event
{
SC_GAME_LOADED,
diff --git a/library/include/RemoteClient.h b/library/include/RemoteClient.h
new file mode 100644
index 00000000..453f2479
--- /dev/null
+++ b/library/include/RemoteClient.h
@@ -0,0 +1,169 @@
+/*
+https://github.com/peterix/dfhack
+Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com)
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any
+damages arising from the use of this software.
+
+Permission is granted to anyone to use this software for any
+purpose, including commercial applications, and to alter it and
+redistribute it freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must
+not claim that you wrote the original software. If you use this
+software in a product, an acknowledgment in the product documentation
+would be appreciated but is not required.
+
+2. Altered source versions must be plainly marked as such, and
+must not be misrepresented as being the original software.
+
+3. This notice may not be removed or altered from any source
+distribution.
+*/
+
+#pragma once
+#include "Pragma.h"
+#include "Export.h"
+#include "ColorText.h"
+
+#include "ActiveSocket.h"
+
+#include "CoreProtocol.pb.h"
+
+namespace DFHack
+{
+ using dfproto::CoreVoidReply;
+
+ enum command_result
+ {
+ CR_WOULD_BREAK = -2,
+ CR_NOT_IMPLEMENTED = -1,
+ CR_OK = 0,
+ CR_FAILURE = 1,
+ CR_WRONG_USAGE = 2
+ };
+
+ enum DFHackReplyCode : int16_t {
+ RPC_REPLY_RESULT = -1,
+ RPC_REPLY_FAIL = -2,
+ RPC_REPLY_TEXT = -3,
+ RPC_REQUEST_QUIT = -4
+ };
+
+ struct RPCHandshakeHeader {
+ char magic[8];
+ int version;
+
+ static const char REQUEST_MAGIC[9];
+ static const char RESPONSE_MAGIC[9];
+ };
+
+ struct RPCMessageHeader {
+ int16_t id;
+ int32_t size;
+ };
+
+ class DFHACK_EXPORT RemoteClient;
+
+ class DFHACK_EXPORT RPCFunctionBase {
+ public:
+ typedef ::google::protobuf::MessageLite message_type;
+
+ const message_type *const p_in_template;
+ const message_type *const p_out_template;
+
+ message_type *make_in() const {
+ return p_in_template->New();
+ }
+
+ message_type *in() {
+ if (!p_in) p_in = make_in();
+ return p_in;
+ }
+
+ message_type *make_out() const {
+ return p_out_template->New();
+ }
+
+ message_type *out() {
+ if (!p_out) p_out = make_out();
+ return p_out;
+ }
+
+ void reset(bool free = false);
+
+ protected:
+ RPCFunctionBase(const message_type *in, const message_type *out)
+ : p_in_template(in), p_out_template(out), p_in(NULL), p_out(NULL)
+ {}
+ ~RPCFunctionBase() { delete p_in; delete p_out; }
+
+ message_type *p_in, *p_out;
+ };
+
+ class DFHACK_EXPORT RemoteFunctionBase : public RPCFunctionBase {
+ public:
+ bool bind(color_ostream &out,
+ RemoteClient *client, const std::string &name,
+ const std::string &proto = std::string());
+ bool isValid() { return (p_client != NULL); }
+
+ protected:
+ friend class RemoteClient;
+
+ RemoteFunctionBase(const message_type *in, const message_type *out)
+ : RPCFunctionBase(in, out), p_client(NULL), id(-1)
+ {}
+
+ command_result execute(color_ostream &out, const message_type *input, message_type *output);
+
+ std::string name, proto;
+ RemoteClient *p_client;
+ int16_t id;
+ };
+
+ template<typename In, typename Out>
+ class RemoteFunction : public RemoteFunctionBase {
+ public:
+ In *make_in() const { return static_cast<In*>(RemoteFunctionBase::make_in()); }
+ In *in() { return static_cast<In*>(RemoteFunctionBase::in()); }
+ Out *make_out() const { return static_cast<Out*>(RemoteFunctionBase::make_out()); }
+ Out *out() { return static_cast<Out*>(RemoteFunctionBase::out()); }
+
+ RemoteFunction() : RemoteFunctionBase(&In::default_instance(), &Out::default_instance()) {}
+
+ command_result execute(color_ostream &out) {
+ return RemoteFunctionBase::execute(out, this->in(), this->out());
+ }
+ command_result operator() (color_ostream &out, const In *input, Out *output) {
+ return RemoteFunctionBase::execute(out, input, output);
+ }
+ };
+
+ bool readFullBuffer(CSimpleSocket &socket, void *buf, int size);
+ bool sendRemoteMessage(CSimpleSocket &socket, int16_t id, const ::google::protobuf::MessageLite *msg);
+
+ class DFHACK_EXPORT RemoteClient
+ {
+ friend class RemoteFunctionBase;
+
+ bool bind(color_ostream &out, RemoteFunctionBase *function,
+ const std::string &name, const std::string &proto);
+
+ public:
+ RemoteClient();
+ ~RemoteClient();
+
+ static int GetDefaultPort();
+
+ bool connect(int port = -1);
+ void disconnect();
+
+ private:
+ bool active;
+ CActiveSocket socket;
+
+ RemoteFunction<dfproto::CoreBindRequest,dfproto::CoreBindReply> bind_call;
+ };
+}
diff --git a/library/include/RemoteServer.h b/library/include/RemoteServer.h
new file mode 100644
index 00000000..a3cc6c04
--- /dev/null
+++ b/library/include/RemoteServer.h
@@ -0,0 +1,189 @@
+/*
+https://github.com/peterix/dfhack
+Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com)
+
+This software is provided 'as-is', without any express or implied
+warranty. In no event will the authors be held liable for any
+damages arising from the use of this software.
+
+Permission is granted to anyone to use this software for any
+purpose, including commercial applications, and to alter it and
+redistribute it freely, subject to the following restrictions:
+
+1. The origin of this software must not be misrepresented; you must
+not claim that you wrote the original software. If you use this
+software in a product, an acknowledgment in the product documentation
+would be appreciated but is not required.
+
+2. Altered source versions must be plainly marked as such, and
+must not be misrepresented as being the original software.
+
+3. This notice may not be removed or altered from any source
+distribution.
+*/
+
+#pragma once
+#include "Pragma.h"
+#include "Export.h"
+#include "RemoteClient.h"
+#include "Core.h"
+
+#include "PassiveSocket.h"
+
+namespace DFHack
+{
+ class DFHACK_EXPORT ServerConnection;
+ class DFHACK_EXPORT RPCService;
+
+ class DFHACK_EXPORT ServerFunctionBase : public RPCFunctionBase {
+ public:
+ const char *const name;
+
+ virtual command_result execute(color_ostream &stream) = 0;
+
+ int16_t getId() { return id; }
+
+ protected:
+ friend class RPCService;
+
+ ServerFunctionBase(const message_type *in, const message_type *out, RPCService *owner, const char *name)
+ : RPCFunctionBase(in, out), name(name), owner(owner), id(-1)
+ {}
+
+ RPCService *owner;
+ int16_t id;
+ };
+
+ template<typename In, typename Out>
+ class ServerFunction : public ServerFunctionBase {
+ public:
+ typedef command_result (*function_type)(color_ostream &out, const In *input, Out *output);
+
+ In *in() { return static_cast<In*>(RPCFunctionBase::in()); }
+ Out *out() { return static_cast<Out*>(RPCFunctionBase::out()); }
+
+ ServerFunction(RPCService *owner, const char *name, function_type fptr)
+ : ServerFunctionBase(&In::default_instance(), &Out::default_instance(), owner, name),
+ fptr(fptr) {}
+
+ virtual command_result execute(color_ostream &stream) { return fptr(stream, in(), out()); }
+
+ private:
+ function_type fptr;
+ };
+
+ template<typename Svc, typename In, typename Out>
+ class ServerMethod : public ServerFunctionBase {
+ public:
+ typedef command_result (Svc::*function_type)(color_ostream &out, const In *input, Out *output);
+
+ In *in() { return static_cast<In*>(RPCFunctionBase::in()); }
+ Out *out() { return static_cast<Out*>(RPCFunctionBase::out()); }
+
+ ServerMethod(RPCService *owner, const char *name, function_type fptr)
+ : ServerFunctionBase(&In::default_instance(), &Out::default_instance(), owner, name),
+ fptr(fptr) {}
+
+ virtual command_result execute(color_ostream &stream) {
+ return (static_cast<Svc*>(owner)->*fptr)(stream, in(), out());
+ }
+
+ private:
+ function_type fptr;
+ };
+
+ class Plugin;
+
+ class DFHACK_EXPORT RPCService {
+ friend class ServerConnection;
+
+ std::vector<ServerFunctionBase*> functions;
+ std::map<std::string, ServerFunctionBase*> lookup;
+ ServerConnection *owner;
+
+ Plugin *holder;
+
+ void finalize(ServerConnection *owner, std::vector<ServerFunctionBase*> *ftable);
+
+ public:
+ RPCService();
+ virtual ~RPCService();
+
+ ServerFunctionBase *getFunction(const std::string &name);
+
+ template<typename In, typename Out>
+ void addFunction(
+ const char *name,
+ command_result (*fptr)(color_ostream &out, const In *input, Out *output)
+ ) {
+ assert(!owner);
+ functions.push_back(new ServerFunction<In,Out>(this, name, fptr));
+ }
+
+ protected:
+ ServerConnection *connection() { return owner; }
+
+ template<typename Svc, typename In, typename Out>
+ void addMethod(
+ const char *name,
+ command_result (Svc::*fptr)(color_ostream &out, const In *input, Out *output)
+ ) {
+ assert(!owner);
+ functions.push_back(new ServerMethod<Svc,In,Out>(this, name, fptr));
+ }
+ };
+
+ class CoreService : public RPCService {
+ public:
+ CoreService();
+
+ command_result BindMethod(color_ostream &stream,
+ const dfproto::CoreBindRequest *in,
+ dfproto::CoreBindReply *out);
+ command_result RunCommand(color_ostream &stream,
+ const dfproto::CoreRunCommandRequest *in,
+ CoreVoidReply*);
+ };
+
+ class DFHACK_EXPORT ServerConnection {
+ class connection_ostream : public buffered_color_ostream {
+ ServerConnection *owner;
+
+ protected:
+ virtual void flush_proxy();
+
+ public:
+ connection_ostream(ServerConnection *owner) : owner(owner) {}
+ };
+
+ bool in_error;
+ CActiveSocket *socket;
+ connection_ostream stream;
+
+ std::vector<ServerFunctionBase*> functions;
+
+ CoreService *core_service;
+ std::map<std::string, RPCService*> plugin_services;
+
+ tthread::thread *thread;
+ static void threadFn(void *);
+
+ public:
+ ServerConnection(CActiveSocket *socket);
+ ~ServerConnection();
+
+ ServerFunctionBase *findFunction(const std::string &plugin, const std::string &name);
+ };
+
+ class DFHACK_EXPORT ServerMain {
+ CPassiveSocket socket;
+
+ tthread::thread *thread;
+ static void threadFn(void *);
+ public:
+ ServerMain();
+ ~ServerMain();
+
+ bool listen(int port);
+ };
+}
diff --git a/library/proto/CoreProtocol.proto b/library/proto/CoreProtocol.proto
index 33ec0f4a..eec5ef7f 100644
--- a/library/proto/CoreProtocol.proto
+++ b/library/proto/CoreProtocol.proto
@@ -34,26 +34,28 @@ message CoreErrorNotification {
enum ErrorCode {
CR_WOULD_BREAK = -2;
CR_NOT_IMPLEMENTED = -1;
- CR_FAILURE = 0;
- CR_OK = 1;
+ CR_OK = 0;
+ CR_FAILURE = 1;
CR_WRONG_USAGE = 2;
};
required ErrorCode code = 1;
}
+message CoreVoidReply {}
+
message CoreBindRequest {
required string method = 1;
- optional string plugin = 2;
- optional int32 min_version = 3;
+ required string input_msg = 2;
+ required string output_msg = 3;
+ optional string plugin = 4;
}
message CoreBindReply {
required int32 assigned_id = 1;
- required int32 version = 2;
}
-message CoreRunStringRequest {
+message CoreRunCommandRequest {
required string command = 1;
repeated string arguments = 2;
}
diff --git a/plugins/Plugins.cmake b/plugins/Plugins.cmake
index 221e57ea..8d68827a 100644
--- a/plugins/Plugins.cmake
+++ b/plugins/Plugins.cmake
@@ -6,6 +6,7 @@ IF(UNIX)
ENDIF()
include_directories("${dfhack_SOURCE_DIR}/library/include")
+include_directories("${dfhack_SOURCE_DIR}/library/proto")
include_directories("${dfhack_SOURCE_DIR}/library/depends/xgetopt")
MACRO(CAR var)