Conflicts:
	plugins/workflow.cpp
develop
Petr Mrázek 2012-03-15 14:43:05 +01:00
commit 1ac8025025
35 changed files with 1907 additions and 212 deletions

1
.gitignore vendored

@ -34,6 +34,7 @@ build/bin
build/library
build/tools
build/plugins
build/depends
#ignore Kdevelop stuff
.kdev4

@ -53,7 +53,7 @@ set(DF_VERSION_MINOR "34")
set(DF_VERSION_PATCH "05")
set(DF_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}")
set(DFHACK_RELEASE "1d")
set(DFHACK_RELEASE "1e")
set(DFHACK_VERSION "${DF_VERSION_MAJOR}.${DF_VERSION_MINOR}.${DF_VERSION_PATCH}-r${DFHACK_RELEASE}")
add_definitions(-DDFHACK_VERSION="${DFHACK_VERSION}")
@ -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
@ -154,11 +159,11 @@ ENDIF()
# Protobuf
FILE(GLOB PROJECT_PROTOS ${CMAKE_CURRENT_SOURCE_DIR}/proto/*.proto)
STRING(REPLACE ".proto" ".pb.cc" PROJECT_PROTO_SRCS ${PROJECT_PROTOS})
STRING(REPLACE ".proto" ".pb.h" PROJECT_PROTO_HDRS ${PROJECT_PROTOS})
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,12 @@ ENDIF()
ADD_LIBRARY(dfhack SHARED ${PROJECT_SOURCES})
ADD_DEPENDENCIES(dfhack generate_headers)
ADD_LIBRARY(dfhack-client SHARED RemoteClient.cpp ColorText.cpp MiscUtils.cpp
proto/CoreProtocol.pb.cc)
ADD_DEPENDENCIES(dfhack-client dfhack)
ADD_EXECUTABLE(dfhack-run dfhack-run.cpp)
IF(BUILD_EGGY)
SET_TARGET_PROPERTIES(dfhack PROPERTIES OUTPUT_NAME "egg" )
else()
@ -221,8 +232,10 @@ endif()
IF(WIN32)
SET_TARGET_PROPERTIES(dfhack PROPERTIES COMPILE_FLAGS "/FI\"Export.h\"" )
SET_TARGET_PROPERTIES(dfhack-client PROPERTIES COMPILE_FLAGS "/FI\"Export.h\"" )
ELSE()
SET_TARGET_PROPERTIES(dfhack PROPERTIES COMPILE_FLAGS "-include Export.h" )
SET_TARGET_PROPERTIES(dfhack-client PROPERTIES COMPILE_FLAGS "-include Export.h" )
ENDIF()
#effectively disables debug builds...
@ -231,10 +244,15 @@ 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-client protobuf-lite clsocket)
TARGET_LINK_LIBRARIES(dfhack-run dfhack-client)
IF(UNIX)
# On linux, copy our version of the df launch script which sets LD_PRELOAD
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack
DESTINATION .)
install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack-run
DESTINATION .)
ELSE()
if(NOT BUILD_EGGY)
# On windows, copy the renamed SDL so DF can still run.
@ -253,6 +271,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 +279,10 @@ install(FILES xml/symbols.xml
install(FILES ../dfhack.init-example
DESTINATION ${DFHACK_BINARY_DESTINATION})
install(TARGETS dfhack-run dfhack-client
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)

@ -56,6 +56,8 @@ using namespace DFHack;
#include "tinythread.h"
using namespace tthread;
bool color_ostream::log_errors_to_stderr = false;
void color_ostream::flush_buffer(bool flush)
{
auto buffer = buf();
@ -122,7 +124,8 @@ void color_ostream::vprinterr(const char *format, va_list args)
{
color_value save = cur_color;
fprintf(stderr, format, args);
if (log_errors_to_stderr)
fprintf(stderr, format, args);
color(COLOR_LIGHTRED);
vprint(format, args);
@ -143,6 +146,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 +178,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"
@ -170,7 +171,7 @@ void fHKthread(void * iodata)
string first = args[0];
args.erase(args.begin());
command_result cr = plug_mgr->InvokeCommand(out, first, args, false);
command_result cr = plug_mgr->InvokeCommand(out, first, args);
if(cr == CR_WOULD_BREAK)
{
@ -277,7 +278,7 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
for(size_t i = 0; i < plug_mgr->size();i++)
{
Plugin * plug = (plug_mgr->operator[](i));
plug->load();
plug->load(con);
}
}
else
@ -289,7 +290,7 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
}
else
{
plug->load();
plug->load(con);
}
}
}
@ -304,7 +305,7 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
for(size_t i = 0; i < plug_mgr->size();i++)
{
Plugin * plug = (plug_mgr->operator[](i));
plug->reload();
plug->reload(con);
}
}
else
@ -316,7 +317,7 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
}
else
{
plug->reload();
plug->reload(con);
}
}
}
@ -331,7 +332,7 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
for(size_t i = 0; i < plug_mgr->size();i++)
{
Plugin * plug = (plug_mgr->operator[](i));
plug->unload();
plug->unload(con);
}
}
else
@ -343,7 +344,7 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue
}
else
{
plug->unload();
plug->unload(con);
}
}
}
@ -596,6 +597,9 @@ Core::Core()
last_world_data_ptr = NULL;
top_viewscreen = NULL;
screen_window = NULL;
server = NULL;
color_ostream::log_errors_to_stderr = true;
};
void Core::fatal (std::string output, bool deactivate)
@ -704,6 +708,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

@ -26,9 +26,11 @@ distribution.
#include "Core.h"
#include "MemAccess.h"
#include "PluginManager.h"
#include "RemoteServer.h"
#include "Console.h"
#include "DataDefs.h"
#include "MiscUtils.h"
using namespace DFHack;
@ -146,6 +148,7 @@ Plugin::Plugin(Core * core, const std::string & filepath, const std::string & _f
plugin_status = 0;
plugin_onupdate = 0;
plugin_onstatechange = 0;
plugin_rpcconnect = 0;
state = PS_UNLOADED;
access = new RefLock();
}
@ -154,12 +157,12 @@ Plugin::~Plugin()
{
if(state == PS_LOADED)
{
unload();
unload(Core::getInstance().getConsole());
}
delete access;
}
bool Plugin::load()
bool Plugin::load(color_ostream &con)
{
RefAutolock lock(access);
if(state == PS_BROKEN)
@ -170,8 +173,6 @@ bool Plugin::load()
{
return true;
}
Core & c = Core::getInstance();
Console & con = c.getConsole();
DFLibrary * plug = OpenPlugin(filename.c_str());
if(!plug)
{
@ -208,6 +209,7 @@ bool Plugin::load()
plugin_onupdate = (command_result (*)(color_ostream &)) LookupPlugin(plug, "plugin_onupdate");
plugin_shutdown = (command_result (*)(color_ostream &)) LookupPlugin(plug, "plugin_shutdown");
plugin_onstatechange = (command_result (*)(color_ostream &, state_change_event)) LookupPlugin(plug, "plugin_onstatechange");
plugin_rpcconnect = (RPCService* (*)(color_ostream &)) LookupPlugin(plug, "plugin_rpcconnect");
this->name = *plug_name;
plugin_lib = plug;
if(plugin_init(con,commands) == CR_OK)
@ -225,10 +227,8 @@ bool Plugin::load()
}
}
bool Plugin::unload()
bool Plugin::unload(color_ostream &con)
{
Core & c = Core::getInstance();
Console & con = c.getConsole();
// get the mutex
access->lock();
// if we are actually loaded
@ -266,18 +266,18 @@ bool Plugin::unload()
return false;
}
bool Plugin::reload()
bool Plugin::reload(color_ostream &out)
{
if(state != PS_LOADED)
return false;
if(!unload())
if(!unload(out))
return false;
if(!load())
if(!load(out))
return false;
return true;
}
command_result Plugin::invoke(color_ostream &out, std::string & command, std::vector <std::string> & parameters, bool interactive_)
command_result Plugin::invoke(color_ostream &out, const std::string & command, std::vector <std::string> & parameters)
{
Core & c = Core::getInstance();
command_result cr = CR_NOT_IMPLEMENTED;
@ -290,7 +290,7 @@ command_result Plugin::invoke(color_ostream &out, std::string & command, std::ve
if(cmd.name == command)
{
// running interactive things from some other source than the console would break it
if(!(interactive_ && out.is_console()) && cmd.interactive)
if(!out.is_console() && cmd.interactive)
cr = CR_WOULD_BREAK;
else if (cmd.guard)
{
@ -325,7 +325,7 @@ command_result Plugin::invoke(color_ostream &out, std::string & command, std::ve
return cr;
}
bool Plugin::can_invoke_hotkey( std::string & command, df::viewscreen *top )
bool Plugin::can_invoke_hotkey(const std::string & command, df::viewscreen *top )
{
Core & c = Core::getInstance();
bool cr = false;
@ -375,6 +375,42 @@ command_result Plugin::on_state_change(color_ostream &out, state_change_event ev
return cr;
}
RPCService *Plugin::rpc_connect(color_ostream &out)
{
RPCService *rv = NULL;
access->lock_add();
if(state == PS_LOADED && plugin_rpcconnect)
{
rv = plugin_rpcconnect(out);
}
if (rv)
{
// Retain the access reference
assert(!rv->holder);
services.push_back(rv);
rv->holder = this;
return rv;
}
else
{
access->lock_sub();
return NULL;
}
}
void Plugin::detach_connection(RPCService *svc)
{
int idx = linear_index(services, svc);
assert(svc->holder == this && idx >= 0);
vector_erase_at(services, idx);
access->lock_sub();
}
Plugin::plugin_state Plugin::getState() const
{
return state;
@ -399,7 +435,7 @@ PluginManager::PluginManager(Core * core)
Plugin * p = new Plugin(core, path + filez[i], filez[i], this);
all_plugins.push_back(p);
// make all plugins load by default (until a proper design emerges).
p->load();
p->load(core->getConsole());
}
}
}
@ -435,13 +471,13 @@ Plugin *PluginManager::getPluginByCommand(const std::string &command)
}
// FIXME: handle name collisions...
command_result PluginManager::InvokeCommand(color_ostream &out, std::string & command, std::vector <std::string> & parameters, bool interactive)
command_result PluginManager::InvokeCommand(color_ostream &out, const std::string & command, std::vector <std::string> & parameters)
{
Plugin *plugin = getPluginByCommand(command);
return plugin ? plugin->invoke(out, command, parameters, interactive) : CR_NOT_IMPLEMENTED;
return plugin ? plugin->invoke(out, command, parameters) : CR_NOT_IMPLEMENTED;
}
bool PluginManager::CanInvokeHotkey(std::string &command, df::viewscreen *top)
bool PluginManager::CanInvokeHotkey(const std::string &command, df::viewscreen *top)
{
Plugin *plugin = getPluginByCommand(command);
return plugin ? plugin->can_invoke_hotkey(command, top) : false;

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

@ -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,27 @@ namespace DFHack
void reset_color(void);
virtual bool is_console() { return false; }
virtual color_ostream *proxy_target() { return NULL; }
static bool log_errors_to_stderr;
};
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 +165,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,10 @@ namespace DFHack
tthread::mutex * misc_data_mutex;
std::map<std::string,void*> misc_data_map;
friend class CoreService;
friend class ServerConnection;
ServerMain *server;
};
class CoreSuspender {

@ -134,7 +134,7 @@ namespace DFHack
template<class T>
struct enum_list_attr {
int size;
size_t size;
const T *items;
};

@ -30,6 +30,9 @@ distribution.
#include <map>
#include <string>
#include <vector>
#include "RemoteClient.h"
struct DFLibrary;
namespace tthread
{
@ -45,15 +48,8 @@ namespace DFHack
class Core;
class PluginManager;
class virtual_identity;
class RPCService;
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,
@ -111,17 +107,23 @@ namespace DFHack
PS_BROKEN
};
friend class PluginManager;
friend class RPCService;
Plugin(DFHack::Core* core, const std::string& filepath, const std::string& filename, PluginManager * pm);
~Plugin();
command_result on_update(color_ostream &out);
command_result on_state_change(color_ostream &out, state_change_event event);
void detach_connection(RPCService *svc);
public:
bool load();
bool unload();
bool reload();
command_result invoke(color_ostream &out, std::string & command, std::vector <std::string> & parameters, bool interactive );
bool can_invoke_hotkey( std::string & command, df::viewscreen *top );
bool load(color_ostream &out);
bool unload(color_ostream &out);
bool reload(color_ostream &out);
command_result invoke(color_ostream &out, const std::string & command, std::vector <std::string> & parameters);
bool can_invoke_hotkey(const std::string & command, df::viewscreen *top );
plugin_state getState () const;
RPCService *rpc_connect(color_ostream &out);
const PluginCommand& operator[] (std::size_t index) const
{
return commands[index];
@ -137,6 +139,7 @@ namespace DFHack
private:
RefLock * access;
std::vector <PluginCommand> commands;
std::vector <RPCService*> services;
std::string filename;
std::string name;
DFLibrary * plugin_lib;
@ -147,6 +150,7 @@ namespace DFHack
command_result (*plugin_shutdown)(color_ostream &);
command_result (*plugin_onupdate)(color_ostream &);
command_result (*plugin_onstatechange)(color_ostream &, state_change_event);
RPCService* (*plugin_rpcconnect)(color_ostream &);
};
class DFHACK_EXPORT PluginManager
{
@ -163,8 +167,8 @@ namespace DFHack
public:
Plugin *getPluginByName (const std::string & name);
Plugin *getPluginByCommand (const std::string &command);
command_result InvokeCommand(color_ostream &out, std::string & command, std::vector <std::string> & parameters, bool interactive = true );
bool CanInvokeHotkey(std::string &command, df::viewscreen *top);
command_result InvokeCommand(color_ostream &out, const std::string & command, std::vector <std::string> & parameters);
bool CanInvokeHotkey(const std::string &command, df::viewscreen *top);
Plugin* operator[] (std::size_t index)
{
if(index >= all_plugins.size())

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

@ -32,6 +32,7 @@ distribution.
#include <cstring>
#include "df/map_block.h"
#include "df/block_square_event_mineralst.h"
#include "df/construction.h"
using namespace DFHack;
namespace MapExtras
{
@ -40,56 +41,78 @@ void SquashVeins (DFCoord bcoord, mapblock40d & mb, t_blockmaterials & materials
memset(materials,-1,sizeof(materials));
std::vector <df::block_square_event_mineralst *> veins;
Maps::SortBlockEvents(bcoord.x,bcoord.y,bcoord.z,&veins);
//iterate through block rows
for(uint32_t j = 0;j<16;j++)
for (uint32_t x = 0;x<16;x++) for (uint32_t y = 0; y< 16;y++)
{
//iterate through columns
for (uint32_t k = 0; k< 16;k++)
df::tiletype tt = mb.tiletypes[x][y];
if (tileMaterial(tt) == tiletype_material::MINERAL)
{
df::tiletype tt = mb.tiletypes[k][j];
if(DFHack::tileMaterial(tt) == tiletype_material::MINERAL)
for (size_t i = 0; i < veins.size(); i++)
{
for(int i = (int) veins.size() - 1; i >= 0;i--)
if (veins[i]->getassignment(x,y))
{
if(!!(((1 << k) & veins[i]->tile_bitmask[j]) >> k))
{
materials[k][j] = veins[i]->inorganic_mat;
i = -1;
}
materials[x][y] = veins[i]->inorganic_mat;
break;
}
}
}
}
}
void SquashRocks ( std::vector< std::vector <uint16_t> > * layerassign, DFHack::mapblock40d & mb, DFHack::t_blockmaterials & materials)
void SquashFrozenLiquids (DFCoord bcoord, mapblock40d & mb, tiletypes40d & frozen)
{
// get the layer materials
for(uint32_t xx = 0;xx<16;xx++)
std::vector <df::block_square_event_frozen_liquidst *> ices;
Maps::SortBlockEvents(bcoord.x,bcoord.y,bcoord.z,NULL,&ices);
for (uint32_t x = 0; x < 16; x++) for (uint32_t y = 0; y < 16; y++)
{
for (uint32_t yy = 0; yy< 16;yy++)
df::tiletype tt = mb.tiletypes[x][y];
frozen[x][y] = tiletype::Void;
if (tileMaterial(tt) == tiletype_material::FROZEN_LIQUID)
{
uint8_t test = mb.designation[xx][yy].bits.biome;
if( test >= sizeof(mb.biome_indices))
{
materials[xx][yy] = -1;
continue;
}
if (mb.biome_indices[test] >= layerassign->size())
for (size_t i = 0; i < ices.size(); i++)
{
materials[xx][yy] = -1;
continue;
df::tiletype tt2 = ices[i]->tiles[x][y];
if (tt2 != tiletype::Void)
{
frozen[x][y] = tt2;
break;
}
}
materials[xx][yy] =
layerassign->at(mb.biome_indices[test])[mb.designation[xx][yy].bits.geolayer_index];
}
}
}
void SquashConstructions (DFCoord bcoord, mapblock40d & mb, tiletypes40d & constructions)
{
for (uint32_t x = 0; x < 16; x++) for (uint32_t y = 0; y < 16; y++)
{
df::tiletype tt = mb.tiletypes[x][y];
constructions[x][y] = tiletype::Void;
if (tileMaterial(tt) == tiletype_material::CONSTRUCTION)
{
DFCoord coord(bcoord.x*16 + x, bcoord.y*16 + y, bcoord.z);
df::construction *con = df::construction::find(coord);
if (con)
constructions[x][y] = con->original_tile;
}
}
}
void SquashRocks ( std::vector< std::vector <uint16_t> > * layerassign, mapblock40d & mb, t_blockmaterials & materials)
{
// get the layer materials
for (uint32_t x = 0; x < 16; x++) for (uint32_t y = 0; y < 16; y++)
{
materials[x][y] = -1;
uint8_t test = mb.designation[x][y].bits.biome;
if ((test < sizeof(mb.biome_indices)) && (mb.biome_indices[test] < layerassign->size()))
materials[x][y] = layerassign->at(mb.biome_indices[test])[mb.designation[x][y].bits.geolayer_index];
}
}
class Block
{
public:
Block(DFHack::DFCoord _bcoord, std::vector< std::vector <uint16_t> > * layerassign = 0)
Block(DFCoord _bcoord, std::vector< std::vector <uint16_t> > * layerassign = 0)
{
dirty_designations = false;
dirty_tiletypes = false;
@ -102,6 +125,8 @@ class Block
{
Maps::ReadTemperatures(bcoord.x,bcoord.y, bcoord.z,&temp1,&temp2);
SquashVeins(bcoord,raw,veinmats);
SquashConstructions(bcoord, raw, contiles);
SquashFrozenLiquids(bcoord, raw, icetiles);
if(layerassign)
SquashRocks(layerassign,raw,basemats);
else
@ -122,6 +147,15 @@ class Block
veinmats[p.x][p.y] = -1;
}
df::tiletype BaseTileTypeAt(df::coord2d p)
{
if (contiles[p.x][p.y] != tiletype::Void)
return contiles[p.x][p.y];
else if (icetiles[p.x][p.y] != tiletype::Void)
return icetiles[p.x][p.y];
else
return raw.tiletypes[p.x][p.y];
}
df::tiletype TileTypeAt(df::coord2d p)
{
return raw.tiletypes[p.x][p.y];
@ -189,11 +223,11 @@ class Block
return true;
}
DFHack::t_blockflags BlockFlags()
t_blockflags BlockFlags()
{
return raw.blockflags;
}
bool setBlockFlags(DFHack::t_blockflags des)
bool setBlockFlags(t_blockflags des)
{
if(!valid) return false;
dirty_blockflags = true;
@ -239,12 +273,14 @@ class Block
bool dirty_temperatures:1;
bool dirty_blockflags:1;
bool dirty_occupancies:1;
DFHack::mapblock40d raw;
DFHack::DFCoord bcoord;
DFHack::t_blockmaterials veinmats;
DFHack::t_blockmaterials basemats;
DFHack::t_temperatures temp1;
DFHack::t_temperatures temp2;
mapblock40d raw;
DFCoord bcoord;
t_blockmaterials veinmats;
t_blockmaterials basemats;
t_temperatures temp1;
t_temperatures temp2;
tiletypes40d contiles; // what's underneath constructions
tiletypes40d icetiles; // what's underneath ice
};
class MapCache
@ -266,18 +302,20 @@ class MapCache
return valid;
}
/// get the map block at a *block* coord. Block coord = tile coord / 16
Block * BlockAt (DFHack::DFCoord blockcoord)
Block * BlockAt (DFCoord blockcoord)
{
if(!valid)
return 0;
std::map <DFHack::DFCoord, Block*>::iterator iter = blocks.find(blockcoord);
std::map <DFCoord, Block*>::iterator iter = blocks.find(blockcoord);
if(iter != blocks.end())
{
return (*iter).second;
}
else
{
if(blockcoord.x < x_bmax && blockcoord.y < y_bmax && blockcoord.z < z_max)
if(blockcoord.x >= 0 && blockcoord.x < x_bmax &&
blockcoord.y >= 0 && blockcoord.y < y_bmax &&
blockcoord.z >= 0 && blockcoord.z < z_max)
{
Block * nblo;
if(validgeo)
@ -290,7 +328,16 @@ class MapCache
return 0;
}
}
df::tiletype tiletypeAt (DFHack::DFCoord tilecoord)
df::tiletype baseTiletypeAt (DFCoord tilecoord)
{
Block * b= BlockAt(tilecoord / 16);
if(b && b->valid)
{
return b->BaseTileTypeAt(tilecoord % 16);
}
return tiletype::Void;
}
df::tiletype tiletypeAt (DFCoord tilecoord)
{
Block * b= BlockAt(tilecoord / 16);
if(b && b->valid)
@ -299,7 +346,7 @@ class MapCache
}
return tiletype::Void;
}
bool setTiletypeAt(DFHack::DFCoord tilecoord, df::tiletype tiletype)
bool setTiletypeAt(DFCoord tilecoord, df::tiletype tiletype)
{
Block * b= BlockAt(tilecoord / 16);
if(b && b->valid)
@ -310,7 +357,7 @@ class MapCache
return false;
}
uint16_t temperature1At (DFHack::DFCoord tilecoord)
uint16_t temperature1At (DFCoord tilecoord)
{
Block * b= BlockAt(tilecoord / 16);
if(b && b->valid)
@ -319,7 +366,7 @@ class MapCache
}
return 0;
}
bool setTemp1At(DFHack::DFCoord tilecoord, uint16_t temperature)
bool setTemp1At(DFCoord tilecoord, uint16_t temperature)
{
Block * b= BlockAt(tilecoord / 16);
if(b && b->valid)
@ -330,7 +377,7 @@ class MapCache
return false;
}
uint16_t temperature2At (DFHack::DFCoord tilecoord)
uint16_t temperature2At (DFCoord tilecoord)
{
Block * b= BlockAt(tilecoord / 16);
if(b && b->valid)
@ -339,7 +386,7 @@ class MapCache
}
return 0;
}
bool setTemp2At(DFHack::DFCoord tilecoord, uint16_t temperature)
bool setTemp2At(DFCoord tilecoord, uint16_t temperature)
{
Block * b= BlockAt(tilecoord / 16);
if(b && b->valid)
@ -350,7 +397,7 @@ class MapCache
return false;
}
int16_t veinMaterialAt (DFHack::DFCoord tilecoord)
int16_t veinMaterialAt (DFCoord tilecoord)
{
Block * b= BlockAt(tilecoord / 16);
if(b && b->valid)
@ -359,7 +406,7 @@ class MapCache
}
return 0;
}
int16_t baseMaterialAt (DFHack::DFCoord tilecoord)
int16_t baseMaterialAt (DFCoord tilecoord)
{
Block * b= BlockAt(tilecoord / 16);
if(b && b->valid)
@ -368,7 +415,7 @@ class MapCache
}
return 0;
}
bool clearMaterialAt (DFHack::DFCoord tilecoord)
bool clearMaterialAt (DFCoord tilecoord)
{
Block * b= BlockAt(tilecoord / 16);
if(b && b->valid)
@ -378,7 +425,7 @@ class MapCache
return 0;
}
df::tile_designation designationAt (DFHack::DFCoord tilecoord)
df::tile_designation designationAt (DFCoord tilecoord)
{
Block * b= BlockAt(tilecoord / 16);
if(b && b->valid)
@ -389,7 +436,7 @@ class MapCache
temp.whole = 0;
return temp;
}
bool setDesignationAt (DFHack::DFCoord tilecoord, df::tile_designation des)
bool setDesignationAt (DFCoord tilecoord, df::tile_designation des)
{
Block * b= BlockAt(tilecoord / 16);
if(b && b->valid)
@ -400,7 +447,7 @@ class MapCache
return false;
}
df::tile_occupancy occupancyAt (DFHack::DFCoord tilecoord)
df::tile_occupancy occupancyAt (DFCoord tilecoord)
{
Block * b= BlockAt(tilecoord / 16);
if(b && b->valid)
@ -411,7 +458,7 @@ class MapCache
temp.whole = 0;
return temp;
}
bool setOccupancyAt (DFHack::DFCoord tilecoord, df::tile_occupancy occ)
bool setOccupancyAt (DFCoord tilecoord, df::tile_occupancy occ)
{
Block * b= BlockAt(tilecoord / 16);
if(b && b->valid)
@ -422,7 +469,7 @@ class MapCache
return false;
}
bool testCoord (DFHack::DFCoord tilecoord)
bool testCoord (DFCoord tilecoord)
{
Block * b= BlockAt(tilecoord / 16);
if(b && b->valid)
@ -433,7 +480,7 @@ class MapCache
}
bool WriteAll()
{
std::map<DFHack::DFCoord, Block *>::iterator p;
std::map<DFCoord, Block *>::iterator p;
for(p = blocks.begin(); p != blocks.end(); p++)
{
p->second->Write();
@ -442,7 +489,7 @@ class MapCache
}
void trash()
{
std::map<DFHack::DFCoord, Block *>::iterator p;
std::map<DFCoord, Block *>::iterator p;
for(p = blocks.begin(); p != blocks.end(); p++)
{
delete p->second;
@ -458,7 +505,7 @@ class MapCache
uint32_t y_tmax;
uint32_t z_max;
std::vector< std::vector <uint16_t> > layerassign;
std::map<DFHack::DFCoord, Block *> blocks;
std::map<DFCoord, Block *> blocks;
};
}
#endif

@ -209,7 +209,7 @@ void World::SetCurrentWeather(uint8_t weather)
string World::ReadWorldFolder()
{
return world->unk_192bd8.save_dir;
return world->cur_savegame.save_dir;
}
static PersistentDataItem dataFromHFig(df::historical_figure *hfig)

@ -1,2 +1,3 @@
*.pb.cc
*.pb.cc.rule
*.pb.h

@ -34,26 +34,29 @@ 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;
CR_NOT_FOUND = 3;
};
required ErrorCode code = 1;
}
message EmptyMessage {}
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;
}

@ -1 +1 @@
Subproject commit 24e809abe07eb139c9f53c358ed3fbd3d8075410
Subproject commit 136181f067a0a5ed19a19c9f98eece41003fe372

@ -35,7 +35,7 @@ fi
# Now run
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./stonesense/deplibs":"./hack/deplibs"
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"./stonesense/deplibs":"./hack"
case "$1" in
-g | --gdb)

@ -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
DF_DIR=$(dirname "$0")
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_VIDEO_CENTERED=1 # Centre the screen. Messes up resizing.
./libs/Dwarf_Fortress $* # Go, go, go! :)

@ -45,6 +45,22 @@ if (BUILD_DWARFEXPORT)
add_subdirectory (dwarfexport)
endif()
# Protobuf
FILE(GLOB PROJECT_PROTOS ${CMAKE_CURRENT_SOURCE_DIR}/proto/*.proto)
STRING(REPLACE ".proto" ".pb.cc" PROJECT_PROTO_SRCS "${PROJECT_PROTOS}")
STRING(REPLACE ".proto" ".pb.h" PROJECT_PROTO_HDRS "${PROJECT_PROTOS}")
ADD_CUSTOM_COMMAND(
OUTPUT ${PROJECT_PROTO_SRCS} ${PROJECT_PROTO_HDRS}
COMMAND protoc-bin -I=${dfhack_SOURCE_DIR}/library/proto/
-I=${CMAKE_CURRENT_SOURCE_DIR}/proto/
--cpp_out=${CMAKE_CURRENT_SOURCE_DIR}/proto/
${PROJECT_PROTOS}
DEPENDS protoc-bin ${PROJECT_PROTOS}
)
# Plugins
OPTION(BUILD_SUPPORTED "Build the supported plugins (reveal, probe, etc.)." ON)
if (BUILD_SUPPORTED)
DFHACK_PLUGIN(reveal reveal.cpp)
@ -70,7 +86,7 @@ if (BUILD_SUPPORTED)
DFHACK_PLUGIN(seedwatch seedwatch.cpp)
DFHACK_PLUGIN(initflags initflags.cpp)
DFHACK_PLUGIN(stockpiles stockpiles.cpp)
DFHACK_PLUGIN(rename rename.cpp)
DFHACK_PLUGIN(rename rename.cpp PROTOBUFS rename)
DFHACK_PLUGIN(jobutils jobutils.cpp)
DFHACK_PLUGIN(workflow workflow.cpp)
DFHACK_PLUGIN(showmood showmood.cpp)

@ -6,7 +6,9 @@ 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")
include_directories("${CMAKE_CURRENT_SOURCE_DIR}/proto")
MACRO(CAR var)
SET(${var} ${ARGV1})
@ -57,21 +59,28 @@ ENDMACRO()
MACRO(DFHACK_PLUGIN)
PARSE_ARGUMENTS(PLUGIN
"LINK_LIBRARIES;DEPENDS"
"LINK_LIBRARIES;DEPENDS;PROTOBUFS"
"SOME_OPT"
${ARGN}
)
CAR(PLUGIN_NAME ${PLUGIN_DEFAULT_ARGS})
CDR(PLUGIN_SOURCES ${PLUGIN_DEFAULT_ARGS})
FOREACH(pbuf ${PLUGIN_PROTOBUFS})
SET(PLUGIN_SOURCES ${PLUGIN_SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/proto/${pbuf}.pb.cc)
ENDFOREACH()
ADD_LIBRARY(${PLUGIN_NAME} MODULE ${PLUGIN_SOURCES})
IDE_FOLDER(${PLUGIN_NAME} "Plugins")
TARGET_LINK_LIBRARIES(${PLUGIN_NAME} dfhack ${PLUGIN_LINK_LIBRARIES})
IF(UNIX)
SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES SUFFIX .plug.so PREFIX "")
SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES COMPILE_FLAGS "-include Export.h")
ELSE()
SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES SUFFIX .plug.dll)
SET_TARGET_PROPERTIES(${PLUGIN_NAME} PROPERTIES COMPILE_FLAGS "/FI\"Export.h\"")
ENDIF()
install(TARGETS ${PLUGIN_NAME}
LIBRARY DESTINATION ${DFHACK_PLUGIN_DESTINATION}
RUNTIME DESTINATION ${DFHACK_PLUGIN_DESTINATION})

@ -33,9 +33,9 @@ static command_result feature(color_ostream &out, vector <string> &parameters)
{
if (parameters.size() != 1)
return CR_WRONG_USAGE;
for (size_t i = 0; i < world->unk_192bd8.map_features.size(); i++)
for (size_t i = 0; i < world->cur_savegame.map_features.size(); i++)
{
df::feature_init *feature_init = world->unk_192bd8.map_features[i];
df::feature_init *feature_init = world->cur_savegame.map_features[i];
string name;
feature_init->getName(&name);
out.print("Feature #%i (\"%s\", type %s) is %s\n", i, name.c_str(), ENUM_KEY_STR(feature_type, feature_init->getType()), feature_init->flags.is_set(feature_init_flags::Discovered) ? "discovered" : "hidden");
@ -46,12 +46,12 @@ static command_result feature(color_ostream &out, vector <string> &parameters)
if (parameters.size() != 2)
return CR_WRONG_USAGE;
size_t i = atoi(parameters[1].c_str());
if ((i < 0) || (i >= world->unk_192bd8.map_features.size()))
if ((i < 0) || (i >= world->cur_savegame.map_features.size()))
{
out.print("No such feature!\n");
return CR_FAILURE;
}
df::feature_init *feature_init = world->unk_192bd8.map_features[i];
df::feature_init *feature_init = world->cur_savegame.map_features[i];
if (feature_init->flags.is_set(feature_init_flags::Discovered))
{
out.print("Selected feature is already discovered!\n");
@ -67,12 +67,12 @@ static command_result feature(color_ostream &out, vector <string> &parameters)
if (parameters.size() != 2)
return CR_WRONG_USAGE;
size_t i = atoi(parameters[1].c_str());
if ((i < 0) || (i >= world->unk_192bd8.map_features.size()))
if ((i < 0) || (i >= world->cur_savegame.map_features.size()))
{
out.print("No such feature!\n");
return CR_FAILURE;
}
df::feature_init *feature_init = world->unk_192bd8.map_features[i];
df::feature_init *feature_init = world->cur_savegame.map_features[i];
if (!feature_init->flags.is_set(feature_init_flags::Discovered))
{
out.print("Selected feature is already hidden!\n");

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

@ -17,6 +17,9 @@
#include "df/assumed_identity.h"
#include "df/language_name.h"
#include "RemoteServer.h"
#include "rename.pb.h"
#include <stdlib.h>
using std::vector;
@ -24,6 +27,7 @@ using std::string;
using std::endl;
using namespace DFHack;
using namespace df::enums;
using namespace dfproto;
using df::global::ui;
using df::global::world;
@ -79,6 +83,85 @@ static df::squad *getSquadByIndex(unsigned idx)
return df::squad::find(entity->squads[idx]);
}
void setUnitNickname(df::unit *unit, const std::string &nick)
{
// There are >=3 copies of the name, and the one
// in the unit is not the authoritative one.
// This is the reason why military units often
// lose nicknames set from Dwarf Therapist.
set_nickname(&unit->name, nick);
if (unit->status.current_soul)
set_nickname(&unit->status.current_soul->name, nick);
df::historical_figure *figure = df::historical_figure::find(unit->hist_figure_id);
if (figure)
{
set_nickname(&figure->name, nick);
// v0.34.01: added the vampire's assumed identity
if (figure->info && figure->info->reputation)
{
auto identity = df::assumed_identity::find(figure->info->reputation->cur_identity);
if (identity)
{
auto id_hfig = df::historical_figure::find(identity->histfig_id);
if (id_hfig)
{
// Even DF doesn't do this bit, because it's apparently
// only used for demons masquerading as gods, so you
// can't ever change their nickname in-game.
set_nickname(&id_hfig->name, nick);
}
else
set_nickname(&identity->name, nick);
}
}
}
}
static command_result RenameSquad(color_ostream &stream, const RenameSquadRq *in)
{
CoreSuspender suspend;
df::squad *squad = df::squad::find(in->squad_id());
if (!squad)
return CR_NOT_FOUND;
if (in->has_nickname())
set_nickname(&squad->name, in->nickname());
if (in->has_alias())
squad->alias = in->alias();
return CR_OK;
}
static command_result RenameUnit(color_ostream &stream, const RenameUnitRq *in)
{
CoreSuspender suspend;
df::unit *unit = df::unit::find(in->unit_id());
if (!unit)
return CR_NOT_FOUND;
if (in->has_nickname())
setUnitNickname(unit, in->nickname());
if (in->has_profession())
unit->custom_profession = in->profession();
return CR_OK;
}
DFhackCExport RPCService *plugin_rpcconnect(color_ostream &)
{
RPCService *svc = new RPCService();
svc->addFunction("RenameSquad", RenameSquad);
svc->addFunction("RenameUnit", RenameUnit);
return svc;
}
static command_result rename(color_ostream &out, vector <string> &parameters)
{
CoreSuspender suspend;
@ -124,29 +207,7 @@ static command_result rename(color_ostream &out, vector <string> &parameters)
if (!unit)
return CR_WRONG_USAGE;
// There are 3 copies of the name, and the one
// in the unit is not the authoritative one.
// This is the reason why military units often
// lose nicknames set from Dwarf Therapist.
set_nickname(&unit->name, parameters[1]);
if (unit->status.current_soul)
set_nickname(&unit->status.current_soul->name, parameters[1]);
df::historical_figure *figure = df::historical_figure::find(unit->hist_figure_id);
if (figure)
{
set_nickname(&figure->name, parameters[1]);
// v0.34.01: added the vampire's assumed identity
if (figure->info && figure->info->reputation)
{
auto identity = df::assumed_identity::find(figure->info->reputation->cur_identity);
if (identity)
set_nickname(&identity->name, parameters[1]);
}
}
setUnitNickname(unit, parameters[1]);
}
else if (cmd == "unit-profession")
{

@ -10,9 +10,16 @@
#include "modules/World.h"
#include "modules/MapCache.h"
#include "modules/Gui.h"
#include "df/construction.h"
#include "df/block_square_event_frozen_liquidst.h"
using MapExtras::MapCache;
using std::string;
using std::vector;
using namespace DFHack;
using namespace df::enums;
using df::global::world;
/*
@ -20,8 +27,8 @@ using df::global::world;
*/
bool isSafe(df::coord c)
{
DFHack::t_feature local_feature;
DFHack::t_feature global_feature;
t_feature local_feature;
t_feature global_feature;
// get features of block
// error -> obviously not safe to manipulate
if(!Maps::ReadFeatures(c.x >> 4,c.y >> 4,c.z,&local_feature,&global_feature))
@ -45,7 +52,7 @@ struct hideblock
// the saved data. we keep map size to check if things still match
uint32_t x_max, y_max, z_max;
std::vector <hideblock> hidesaved;
vector <hideblock> hidesaved;
bool nopause_state = false;
enum revealstate
@ -58,16 +65,16 @@ enum revealstate
revealstate revealed = NOT_REVEALED;
command_result reveal(color_ostream &out, std::vector<std::string> & params);
command_result unreveal(color_ostream &out, std::vector<std::string> & params);
command_result revtoggle(color_ostream &out, std::vector<std::string> & params);
command_result revflood(color_ostream &out, std::vector<std::string> & params);
command_result revforget(color_ostream &out, std::vector<std::string> & params);
command_result nopause(color_ostream &out, std::vector<std::string> & params);
command_result reveal(color_ostream &out, vector<string> & params);
command_result unreveal(color_ostream &out, vector<string> & params);
command_result revtoggle(color_ostream &out, vector<string> & params);
command_result revflood(color_ostream &out, vector<string> & params);
command_result revforget(color_ostream &out, vector<string> & params);
command_result nopause(color_ostream &out, vector<string> & params);
DFHACK_PLUGIN("reveal");
DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <PluginCommand> &commands)
DFhackCExport command_result plugin_init ( color_ostream &out, vector <PluginCommand> &commands)
{
commands.clear();
commands.push_back(PluginCommand("reveal","Reveal the map. 'reveal hell' will also reveal hell. 'reveal demon' won't pause.",reveal));
@ -81,7 +88,7 @@ DFhackCExport command_result plugin_init ( color_ostream &out, std::vector <Plug
DFhackCExport command_result plugin_onupdate ( color_ostream &out )
{
DFHack::World *World = Core::getInstance().getWorld();
World *World = Core::getInstance().getWorld();
t_gamemodes gm;
World->ReadGameMode(gm);
if(gm.g_mode == GAMEMODE_DWARF)
@ -104,7 +111,7 @@ DFhackCExport command_result plugin_shutdown ( color_ostream &out )
return CR_OK;
}
command_result nopause (color_ostream &out, std::vector <std::string> & parameters)
command_result nopause (color_ostream &out, vector <string> & parameters)
{
if (parameters.size() == 1 && (parameters[0] == "0" || parameters[0] == "1"))
{
@ -130,7 +137,7 @@ void revealAdventure(color_ostream &out)
// in 'no-hell'/'safe' mode, don't reveal blocks with hell and adamantine
if (!isSafe(block->map_pos))
continue;
DFHack::designations40d & designations = block->designation;
designations40d & designations = block->designation;
// for each tile in block
for (uint32_t x = 0; x < 16; x++) for (uint32_t y = 0; y < 16; y++)
{
@ -143,7 +150,7 @@ void revealAdventure(color_ostream &out)
out.print("Local map revealed.\n");
}
command_result reveal(color_ostream &out, std::vector<std::string> & params)
command_result reveal(color_ostream &out, vector<string> & params)
{
bool no_hell = true;
bool pause = true;
@ -179,7 +186,7 @@ command_result reveal(color_ostream &out, std::vector<std::string> & params)
CoreSuspender suspend;
DFHack::World *World = Core::getInstance().getWorld();
World *World = Core::getInstance().getWorld();
if (!Maps::IsValid())
{
out.printerr("Map is not available!\n");
@ -208,7 +215,7 @@ command_result reveal(color_ostream &out, std::vector<std::string> & params)
continue;
hideblock hb;
hb.c = block->map_pos;
DFHack::designations40d & designations = block->designation;
designations40d & designations = block->designation;
// for each tile in block
for (uint32_t x = 0; x < 16; x++) for (uint32_t y = 0; y < 16; y++)
{
@ -240,7 +247,7 @@ command_result reveal(color_ostream &out, std::vector<std::string> & params)
return CR_OK;
}
command_result unreveal(color_ostream &out, std::vector<std::string> & params)
command_result unreveal(color_ostream &out, vector<string> & params)
{
auto & con = out;
for(size_t i = 0; i < params.size();i++)
@ -258,7 +265,7 @@ command_result unreveal(color_ostream &out, std::vector<std::string> & params)
}
CoreSuspender suspend;
DFHack::World *World = Core::getInstance().getWorld();
World *World = Core::getInstance().getWorld();
if (!Maps::IsValid())
{
out.printerr("Map is not available!\n");
@ -297,7 +304,7 @@ command_result unreveal(color_ostream &out, std::vector<std::string> & params)
return CR_OK;
}
command_result revtoggle (color_ostream &out, std::vector<std::string> & params)
command_result revtoggle (color_ostream &out, vector<string> & params)
{
for(size_t i = 0; i < params.size();i++)
{
@ -317,7 +324,7 @@ command_result revtoggle (color_ostream &out, std::vector<std::string> & params)
}
}
command_result revflood(color_ostream &out, std::vector<std::string> & params)
command_result revflood(color_ostream &out, vector<string> & params)
{
for(size_t i = 0; i < params.size();i++)
{
@ -396,7 +403,7 @@ command_result revflood(color_ostream &out, std::vector<std::string> & params)
if(!MCache->testCoord(current))
continue;
df::tiletype tt = MCache->tiletypeAt(current);
df::tiletype tt = MCache->baseTiletypeAt(current);
df::tile_designation des = MCache->designationAt(current);
if(!des.bits.hidden)
{
@ -469,7 +476,7 @@ command_result revflood(color_ostream &out, std::vector<std::string> & params)
return CR_OK;
}
command_result revforget(color_ostream &out, std::vector<std::string> & params)
command_result revforget(color_ostream &out, vector<string> & params)
{
auto & con = out;
for(size_t i = 0; i < params.size();i++)

@ -15,7 +15,7 @@ IF(UNIX)
${include_directories}
${server_SOURCE_DIR}/zeromq
)
install(PROGRAMS ${server_SOURCE_DIR}/zeromq/libzmq.so.1 DESTINATION "hack/deplibs")
install(PROGRAMS ${server_SOURCE_DIR}/zeromq/libzmq.so.1 DESTINATION ${DFHACK_LIBRARY_DESTINATION})
ELSE()
SET(PROJECT_LIBS
zmq
@ -31,7 +31,7 @@ ELSE()
${include_directories}
${server_SOURCE_DIR}/zeromq
)
install(PROGRAMS ${server_SOURCE_DIR}/zeromq/libzmq.dll DESTINATION ".")
install(PROGRAMS ${server_SOURCE_DIR}/zeromq/libzmq.dll DESTINATION ${DFHACK_LIBRARY_DESTINATION})
ENDIF()

@ -1 +1 @@
Subproject commit dfae65496e4b4a591fdb8db9feda37dede714a89
Subproject commit bb7bcc00c4eb2e46e5d7141fb2d0ce8a30386568

@ -90,7 +90,7 @@ bool dig (MapExtras::MapCache & MCache,
df::tiletype tt = MCache.tiletypeAt(at);
df::tile_designation des = MCache.designationAt(at);
// could be potentially used to locate hidden constructions?
if(tileMaterial(tt) == df::tiletype_material::CONSTRUCTION && !des.bits.hidden)
if(tileMaterial(tt) == tiletype_material::CONSTRUCTION && !des.bits.hidden)
return false;
df::tiletype_shape ts = tileShape(tt);
if (ts == tiletype_shape::EMPTY)

@ -78,13 +78,15 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
" List workflow-controlled jobs (if in a workshop, filtered by it).\n"
" workflow list\n"
" List active constraints, and their job counts.\n"
" workflow list-commands\n"
" List workflow commands that re-create existing constraints.\n"
" workflow count <constraint-spec> <cnt-limit> [cnt-gap]\n"
" workflow amount <constraint-spec> <cnt-limit> [cnt-gap]\n"
" Set a constraint. The first form counts each stack as only 1 item.\n"
" workflow unlimit <constraint-spec>\n"
" Delete a constraint.\n"
" workflow clear all\n"
" Deletes all constraints. Be sure you want to do this.\n"
" workflow unlimit-all\n"
" Delete all constraints.\n"
"Function:\n"
" - When the plugin is enabled, it protects all repeat jobs from removal.\n"
" If they do disappear due to any cause, they are immediately re-added\n"
@ -108,6 +110,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector <Plugi
" workflow count BAR//COAL 20\n"
" workflow count BAR//COPPER 30\n"
" Make sure there are always 15-20 coal and 25-30 copper bars.\n"
" workflow count CRAFTS//GOLD 20\n"
" Produce 15-20 gold crafts.\n"
" workflow count POWDER_MISC/SAND 20\n"
" workflow count BOULDER/CLAY 20\n"
" Collect 15-20 sand bags and clay boulders.\n"
@ -271,6 +275,7 @@ typedef std::map<std::pair<int,int>, bool> TMaterialCache;
struct ItemConstraint {
PersistentDataItem config;
bool is_craft;
ItemTypeInfo item;
MaterialInfo material;
@ -288,7 +293,7 @@ struct ItemConstraint {
public:
ItemConstraint()
: weight(0), item_amount(0), item_count(0), item_inuse(0)
: is_craft(false), weight(0), item_amount(0), item_count(0), item_inuse(0)
, is_active(false), cant_resume_reported(false)
{}
@ -647,8 +652,12 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str
int weight = 0;
bool is_craft = false;
ItemTypeInfo item;
if (!item.find(tokens[0]) || !item.isValid()) {
if (tokens[0] == "ANY_CRAFT" || tokens[0] == "CRAFTS") {
is_craft = true;
} else if (!item.find(tokens[0]) || !item.isValid()) {
out.printerr("Cannot find item type: %s\n", tokens[0].c_str());
return NULL;
}
@ -684,12 +693,14 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str
for (size_t i = 0; i < constraints.size(); i++)
{
ItemConstraint *ct = constraints[i];
if (ct->item == item && ct->material == material &&
if (ct->is_craft == is_craft &&
ct->item == item && ct->material == material &&
ct->mat_mask.whole == mat_mask.whole)
return ct;
}
ItemConstraint *nct = new ItemConstraint;
nct->is_craft = is_craft;
nct->item = item;
nct->material = material;
nct->mat_mask = mat_mask;
@ -707,20 +718,6 @@ static ItemConstraint *get_constraint(color_ostream &out, const std::string &str
return nct;
}
static void delete_all_constraints()
{
DFHack::World * w = Core::getInstance().getWorld();
for(std::vector<ItemConstraint*>::iterator iter = constraints.begin();
iter != constraints.end();
++iter)
{
w->DeletePersistentData((*iter)->config);
delete (*iter);
}
constraints.clear();
}
static void delete_constraint(ItemConstraint *cv)
{
int idx = linear_index(constraints, cv);
@ -735,9 +732,22 @@ static void delete_constraint(ItemConstraint *cv)
* JOB-CONSTRAINT MAPPING *
******************************/
static bool isCraftItem(df::item_type type)
{
using namespace df::enums::job_type;
auto lst = ENUM_ATTR(job_type, possible_item, MakeCrafts);
for (size_t i = 0; i < lst.size; i++)
if (lst.items[i] == type)
return true;
return false;
}
static void link_job_constraint(ProtectedJob *pj, df::item_type itype, int16_t isubtype,
df::dfhack_material_category mat_mask,
int16_t mat_type, int32_t mat_index)
int16_t mat_type, int32_t mat_index,
bool is_craft = false)
{
MaterialInfo mat(mat_type, mat_index);
@ -745,9 +755,18 @@ static void link_job_constraint(ProtectedJob *pj, df::item_type itype, int16_t i
{
ItemConstraint *ct = constraints[i];
if (ct->item.type != itype ||
(ct->item.subtype != -1 && ct->item.subtype != isubtype))
continue;
if (is_craft)
{
if (!ct->is_craft && !isCraftItem(ct->item.type))
continue;
}
else
{
if (ct->item.type != itype ||
(ct->item.subtype != -1 && ct->item.subtype != isubtype))
continue;
}
if (!mat.matches(ct->material))
continue;
if (ct->mat_mask.whole)
@ -894,7 +913,8 @@ static void compute_job_outputs(color_ostream &out, ProtectedJob *pj)
// Item type & subtype
df::item_type itype = ENUM_ATTR(job_type, item, job->job_type);
int16_t isubtype = job->item_subtype;
if (itype == item_type::NONE)
if (itype == item_type::NONE && job->job_type != MakeCrafts)
return;
// Item material & material category
@ -934,6 +954,10 @@ static void compute_job_outputs(color_ostream &out, ProtectedJob *pj)
}
break;
case MakeCrafts:
link_job_constraint(pj, item_type::NONE, -1, mat_mask, mat.type, mat.index, true);
return;
#define PLANT_PROCESS_MAT(flag, tag) \
if (mat.plant && mat.plant->flags.is_set(plant_raw_flags::flag)) \
mat.decode(mat.plant->material_defs.type_##tag, \
@ -1132,9 +1156,18 @@ static void map_job_items(color_ostream &out)
for (size_t i = 0; i < constraints.size(); i++)
{
ItemConstraint *cv = constraints[i];
if (cv->item.type != itype ||
(cv->item.subtype != -1 && cv->item.subtype != isubtype))
continue;
if (cv->is_craft)
{
if (!isCraftItem(itype))
continue;
}
else
{
if (cv->item.type != itype ||
(cv->item.subtype != -1 && cv->item.subtype != isubtype))
continue;
}
TMaterialCache::iterator it = cv->material_cache.find(matkey);
@ -1244,6 +1277,9 @@ static void update_jobs_by_constraints(color_ostream &out)
std::string info = ct->item.toString();
if (ct->is_craft)
info = "crafts";
if (ct->material.isValid())
info = ct->material.toString() + " " + info;
else if (ct->mat_mask.whole)
@ -1534,6 +1570,17 @@ static command_result workflow_cmd(color_ostream &out, vector <string> & paramet
return CR_OK;
}
else if (cmd == "list-commands")
{
for (size_t i = 0; i < constraints.size(); i++)
{
auto cv = constraints[i];
out << "workflow " << (cv->goalByCount() ? "count " : "amount ")
<< cv->config.val() << " " << cv->goalCount() << " " << cv->goalGap() << endl;
}
return CR_OK;
}
else if (cmd == "count" || cmd == "amount")
{
if (parameters.size() < 3)
@ -1577,14 +1624,16 @@ static command_result workflow_cmd(color_ostream &out, vector <string> & paramet
out.printerr("Constraint not found: %s\n", parameters[1].c_str());
return CR_FAILURE;
}
else if (cmd == "clear")
else if (cmd == "unlimit-all")
{
if(parameters.size() == 2 && parameters[1] == "all")
{
delete_all_constraints();
return CR_OK;
}
return CR_WRONG_USAGE;
if (parameters.size() != 1)
return CR_WRONG_USAGE;
while (!constraints.empty())
delete_constraint(constraints[0]);
out.print("Removed all constraints.\n");
return CR_OK;
}
else
return CR_WRONG_USAGE;