commit
1ac8025025
@ -0,0 +1,384 @@
|
|||||||
|
/*
|
||||||
|
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;
|
||||||
|
|
||||||
|
runcmd_call.name = "RunCommand";
|
||||||
|
runcmd_call.p_client = this;
|
||||||
|
runcmd_call.id = 1;
|
||||||
|
|
||||||
|
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(out) != CR_OK)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
function->p_client = this;
|
||||||
|
function->id = bind_call.out()->assigned_id();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
command_result RemoteClient::run_command(color_ostream &out, const std::string &cmd,
|
||||||
|
const std::vector<std::string> &args)
|
||||||
|
{
|
||||||
|
if (!active || !socket.IsSocketValid())
|
||||||
|
{
|
||||||
|
out.printerr("In RunCommand: client connection not valid.\n");
|
||||||
|
return CR_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
runcmd_call.reset();
|
||||||
|
|
||||||
|
runcmd_call.in()->set_command(cmd);
|
||||||
|
for (size_t i = 0; i < args.size(); i++)
|
||||||
|
runcmd_call.in()->add_arguments(args[i]);
|
||||||
|
|
||||||
|
return runcmd_call(out);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 *psz)
|
||||||
|
{
|
||||||
|
int size = msg->ByteSize();
|
||||||
|
int fullsz = size + sizeof(RPCMessageHeader);
|
||||||
|
|
||||||
|
if (psz)
|
||||||
|
*psz = size;
|
||||||
|
|
||||||
|
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,405 @@
|
|||||||
|
/*
|
||||||
|
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() {
|
||||||
|
// These 2 methods must be first, so that they get id 0 and 1
|
||||||
|
addMethod("BindMethod", &CoreService::BindMethod);
|
||||||
|
addMethod("RunCommand", &CoreService::RunCommand);
|
||||||
|
|
||||||
|
// Add others here:
|
||||||
|
}
|
||||||
|
|
||||||
|
command_result CoreService::BindMethod(color_ostream &stream,
|
||||||
|
const dfproto::CoreBindRequest *in,
|
||||||
|
dfproto::CoreBindReply *out)
|
||||||
|
{
|
||||||
|
ServerFunctionBase *fn = connection()->findFunction(stream, 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)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
RPCService::RPCService()
|
||||||
|
{
|
||||||
|
owner = NULL;
|
||||||
|
holder = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
RPCService::~RPCService()
|
||||||
|
{
|
||||||
|
if (holder)
|
||||||
|
holder->detach_connection(this);
|
||||||
|
|
||||||
|
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(color_ostream &out, const std::string &plugin, const std::string &name)
|
||||||
|
{
|
||||||
|
RPCService *svc;
|
||||||
|
|
||||||
|
if (plugin.empty())
|
||||||
|
svc = core_service;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
svc = plugin_services[plugin];
|
||||||
|
|
||||||
|
if (!svc)
|
||||||
|
{
|
||||||
|
Plugin *plug = Core::getInstance().plug_mgr->getPluginByName(plugin);
|
||||||
|
if (!plug)
|
||||||
|
{
|
||||||
|
out.printerr("No such plugin: %s\n", plugin.c_str());
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
svc = plug->rpc_connect(out);
|
||||||
|
if (!svc)
|
||||||
|
{
|
||||||
|
out.printerr("Plugin %s doesn't export any RPC methods.\n", plugin.c_str());
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
svc->finalize(this, &functions);
|
||||||
|
plugin_services[plugin] = svc;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return svc->getFunction(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
// Read the message
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Find and call the function
|
||||||
|
int in_size = 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush all text output
|
||||||
|
if (me->in_error)
|
||||||
|
break;
|
||||||
|
|
||||||
|
me->stream.flush();
|
||||||
|
|
||||||
|
//out.print("Answer %d:%d\n", res, reply);
|
||||||
|
|
||||||
|
// Send reply
|
||||||
|
int out_size = 0;
|
||||||
|
|
||||||
|
if (res == CR_OK && reply)
|
||||||
|
{
|
||||||
|
if (!sendRemoteMessage(*me->socket, RPC_REPLY_RESULT, reply, &out_size))
|
||||||
|
{
|
||||||
|
out.printerr("In RPC server: I/O error in send result.\n");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (reply)
|
||||||
|
out_size = reply->ByteSize();
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
if (fn)
|
||||||
|
{
|
||||||
|
fn->reset(out_size > 32768 || in_size > 32768);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Call the command
|
||||||
|
std::vector<std::string> args;
|
||||||
|
for (int i = 2; i < argc; i++)
|
||||||
|
args.push_back(argv[i]);
|
||||||
|
|
||||||
|
command_result rv = client.run_command(out, argv[1], args);
|
||||||
|
|
||||||
|
if (rv != CR_OK) {
|
||||||
|
if (rv == CR_NOT_IMPLEMENTED)
|
||||||
|
out.printerr("%s is not a recognized command.\n", argv[1]);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.flush();
|
||||||
|
return 0;
|
||||||
|
}
|
@ -0,0 +1,228 @@
|
|||||||
|
/*
|
||||||
|
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::EmptyMessage;
|
||||||
|
|
||||||
|
enum command_result
|
||||||
|
{
|
||||||
|
CR_WOULD_BREAK = -2, // Attempt to call interactive command without console
|
||||||
|
CR_NOT_IMPLEMENTED = -1, // Command not implemented, or plugin not loaded
|
||||||
|
CR_OK = 0, // Success
|
||||||
|
CR_FAILURE = 1, // Failure
|
||||||
|
CR_WRONG_USAGE = 2, // Wrong arguments or ui state
|
||||||
|
CR_NOT_FOUND = 3 // Target object not found (for RPC mainly)
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Protocol description:
|
||||||
|
*
|
||||||
|
* 1. Handshake
|
||||||
|
*
|
||||||
|
* Client initiates connection by sending the handshake
|
||||||
|
* request header. The server responds with the response
|
||||||
|
* magic. Currently both versions must be 1.
|
||||||
|
*
|
||||||
|
* 2. Interaction
|
||||||
|
*
|
||||||
|
* Requests are done by exchanging messages between the
|
||||||
|
* client and the server. Messages consist of a serialized
|
||||||
|
* protobuf message preceeded by RPCMessageHeader. The size
|
||||||
|
* field specifies the length of the protobuf part.
|
||||||
|
*
|
||||||
|
* NOTE: As a special exception, RPC_REPLY_FAIL uses the size
|
||||||
|
* field to hold the error code directly.
|
||||||
|
*
|
||||||
|
* Every callable function is assigned a non-negative id by
|
||||||
|
* the server. Id 0 is reserved for BindMethod, which can be
|
||||||
|
* used to request any other id by function name. Id 1 is
|
||||||
|
* RunCommand, used to call console commands remotely.
|
||||||
|
*
|
||||||
|
* The client initiates every call by sending a message with
|
||||||
|
* appropriate function id and input arguments. The server
|
||||||
|
* responds with zero or more RPC_REPLY_TEXT:CoreTextNotification
|
||||||
|
* messages, followed by RPC_REPLY_RESULT containing the output
|
||||||
|
* of the function if it succeeded, or RPC_REPLY_FAIL with the
|
||||||
|
* error code if it did not.
|
||||||
|
*
|
||||||
|
* 3. Disconnect
|
||||||
|
*
|
||||||
|
* The client terminates the connection by sending an
|
||||||
|
* RPC_REQUEST_QUIT header with zero size and immediately
|
||||||
|
* closing the socket.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 = EmptyMessage>
|
||||||
|
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 operator() (color_ostream &stream) {
|
||||||
|
return RemoteFunctionBase::execute(stream, in(), out());
|
||||||
|
}
|
||||||
|
command_result operator() (color_ostream &stream, const In *input, Out *output) {
|
||||||
|
return RemoteFunctionBase::execute(stream, input, output);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename In>
|
||||||
|
class RemoteFunction<In,EmptyMessage> : public RemoteFunctionBase {
|
||||||
|
public:
|
||||||
|
In *make_in() const { return static_cast<In*>(RemoteFunctionBase::make_in()); }
|
||||||
|
In *in() { return static_cast<In*>(RemoteFunctionBase::in()); }
|
||||||
|
|
||||||
|
RemoteFunction() : RemoteFunctionBase(&In::default_instance(), &EmptyMessage::default_instance()) {}
|
||||||
|
|
||||||
|
command_result operator() (color_ostream &stream) {
|
||||||
|
return RemoteFunctionBase::execute(stream, in(), out());
|
||||||
|
}
|
||||||
|
command_result operator() (color_ostream &stream, const In *input) {
|
||||||
|
return RemoteFunctionBase::execute(stream, input, out());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool readFullBuffer(CSimpleSocket &socket, void *buf, int size);
|
||||||
|
bool sendRemoteMessage(CSimpleSocket &socket, int16_t id,
|
||||||
|
const ::google::protobuf::MessageLite *msg, int *psz = NULL);
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
command_result run_command(color_ostream &out, const std::string &cmd,
|
||||||
|
const std::vector<std::string> &args);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool active;
|
||||||
|
CActiveSocket socket;
|
||||||
|
|
||||||
|
RemoteFunction<dfproto::CoreBindRequest,dfproto::CoreBindReply> bind_call;
|
||||||
|
RemoteFunction<dfproto::CoreRunCommandRequest> runcmd_call;
|
||||||
|
};
|
||||||
|
}
|
@ -0,0 +1,243 @@
|
|||||||
|
/*
|
||||||
|
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 In>
|
||||||
|
class VoidServerFunction : public ServerFunctionBase {
|
||||||
|
public:
|
||||||
|
typedef command_result (*function_type)(color_ostream &out, const In *input);
|
||||||
|
|
||||||
|
In *in() { return static_cast<In*>(RPCFunctionBase::in()); }
|
||||||
|
|
||||||
|
VoidServerFunction(RPCService *owner, const char *name, function_type fptr)
|
||||||
|
: ServerFunctionBase(&In::default_instance(), &EmptyMessage::default_instance(), owner, name),
|
||||||
|
fptr(fptr) {}
|
||||||
|
|
||||||
|
virtual command_result execute(color_ostream &stream) { return fptr(stream, in()); }
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Svc, typename In>
|
||||||
|
class VoidServerMethod : public ServerFunctionBase {
|
||||||
|
public:
|
||||||
|
typedef command_result (Svc::*function_type)(color_ostream &out, const In *input);
|
||||||
|
|
||||||
|
In *in() { return static_cast<In*>(RPCFunctionBase::in()); }
|
||||||
|
|
||||||
|
VoidServerMethod(RPCService *owner, const char *name, function_type fptr)
|
||||||
|
: ServerFunctionBase(&In::default_instance(), &EmptyMessage::default_instance(), owner, name),
|
||||||
|
fptr(fptr) {}
|
||||||
|
|
||||||
|
virtual command_result execute(color_ostream &stream) {
|
||||||
|
return (static_cast<Svc*>(owner)->*fptr)(stream, in());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
function_type fptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Plugin;
|
||||||
|
|
||||||
|
class DFHACK_EXPORT RPCService {
|
||||||
|
friend class ServerConnection;
|
||||||
|
friend class Plugin;
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename In>
|
||||||
|
void addFunction(
|
||||||
|
const char *name,
|
||||||
|
command_result (*fptr)(color_ostream &out, const In *input)
|
||||||
|
) {
|
||||||
|
assert(!owner);
|
||||||
|
functions.push_back(new VoidServerFunction<In>(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));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename Svc, typename In>
|
||||||
|
void addMethod(
|
||||||
|
const char *name,
|
||||||
|
command_result (Svc::*fptr)(color_ostream &out, const In *input)
|
||||||
|
) {
|
||||||
|
assert(!owner);
|
||||||
|
functions.push_back(new VoidServerMethod<Svc,In>(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);
|
||||||
|
};
|
||||||
|
|
||||||
|
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(color_ostream &out, 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);
|
||||||
|
};
|
||||||
|
}
|
@ -1,2 +1,3 @@
|
|||||||
*.pb.cc
|
*.pb.cc
|
||||||
|
*.pb.cc.rule
|
||||||
*.pb.h
|
*.pb.h
|
||||||
|
@ -1 +1 @@
|
|||||||
Subproject commit 24e809abe07eb139c9f53c358ed3fbd3d8075410
|
Subproject commit 136181f067a0a5ed19a19c9f98eece41003fe372
|
@ -0,0 +1,8 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
DF_DIR=$(dirname "$0")
|
||||||
|
cd "${DF_DIR}"
|
||||||
|
|
||||||
|
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./stonesense/deplibs":"./hack"
|
||||||
|
|
||||||
|
exec hack/dfhack-run "$@"
|
@ -1,7 +1,7 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
DF_DIR=$(dirname "$0")
|
DF_DIR=$(dirname "$0")
|
||||||
cd "${DF_DIR}"
|
cd "${DF_DIR}"
|
||||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./stonesense/deplibs":"./hack/deplibs"
|
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./stonesense/deplibs":"./hack"
|
||||||
export SDL_DISABLE_LOCK_KEYS=1 # Work around for bug in Debian/Ubuntu SDL patch.
|
export SDL_DISABLE_LOCK_KEYS=1 # Work around for bug in Debian/Ubuntu SDL patch.
|
||||||
#export SDL_VIDEO_CENTERED=1 # Centre the screen. Messes up resizing.
|
#export SDL_VIDEO_CENTERED=1 # Centre the screen. Messes up resizing.
|
||||||
./libs/Dwarf_Fortress $* # Go, go, go! :)
|
./libs/Dwarf_Fortress $* # Go, go, go! :)
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
*.pb.cc
|
||||||
|
*.pb.cc.rule
|
||||||
|
*.pb.h
|
@ -0,0 +1,17 @@
|
|||||||
|
package dfproto;
|
||||||
|
|
||||||
|
option optimize_for = LITE_RUNTIME;
|
||||||
|
|
||||||
|
message RenameSquadRq {
|
||||||
|
required int32 squad_id = 1;
|
||||||
|
|
||||||
|
optional string nickname = 2;
|
||||||
|
optional string alias = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RenameUnitRq {
|
||||||
|
required int32 unit_id = 1;
|
||||||
|
|
||||||
|
optional string nickname = 2;
|
||||||
|
optional string profession = 3;
|
||||||
|
}
|
@ -1 +1 @@
|
|||||||
Subproject commit dfae65496e4b4a591fdb8db9feda37dede714a89
|
Subproject commit bb7bcc00c4eb2e46e5d7141fb2d0ce8a30386568
|
Loading…
Reference in New Issue