Implement trivial RPC interface for dfhack via TCP & protobufs.

Use it to make an executable capable of calling commands remotely.
develop
Alexander Gavrilov 2012-03-14 19:57:29 +04:00
parent c42e2ff053
commit 560e977f05
15 changed files with 1260 additions and 25 deletions

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

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

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

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

@ -24,7 +24,6 @@ distribution.
#include "Internal.h"
#include "Export.h"
#include "Core.h"
#include "MiscUtils.h"
#ifndef LINUX_BUILD

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

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

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

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

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

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

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

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

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

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