diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 0db0042e6..63107456a 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -218,8 +218,7 @@ 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_LIBRARY(dfhack-client SHARED RemoteClient.cpp ColorText.cpp MiscUtils.cpp ${PROJECT_PROTO_SRCS}) ADD_DEPENDENCIES(dfhack-client dfhack) ADD_EXECUTABLE(dfhack-run dfhack-run.cpp) diff --git a/library/RemoteClient.cpp b/library/RemoteClient.cpp index 3a497ba8c..a66421aa0 100644 --- a/library/RemoteClient.cpp +++ b/library/RemoteClient.cpp @@ -112,7 +112,7 @@ RemoteClient::~RemoteClient() delete p_default_output; } -bool DFHack::readFullBuffer(CSimpleSocket *socket, void *buf, int size) +bool readFullBuffer(CSimpleSocket *socket, void *buf, int size) { if (!socket->IsSocketValid()) return false; @@ -324,14 +324,11 @@ bool RemoteFunctionBase::bind(color_ostream &out, RemoteClient *client, return client->bind(out, this, name, proto); } -bool DFHack::sendRemoteMessage(CSimpleSocket *socket, int16_t id, const MessageLite *msg, int *psz) +bool sendRemoteMessage(CSimpleSocket *socket, int16_t id, const MessageLite *msg, bool size_ready) { - int size = msg->ByteSize(); + int size = size_ready ? msg->GetCachedSize() : msg->ByteSize(); int fullsz = size + sizeof(RPCMessageHeader); - if (psz) - *psz = size; - std::auto_ptr data(new uint8_t[fullsz]); RPCMessageHeader *hdr = (RPCMessageHeader*)data.get(); @@ -361,7 +358,16 @@ command_result RemoteFunctionBase::execute(color_ostream &out, return CR_LINK_FAILURE; } - if (!sendRemoteMessage(p_client->socket, id, input)) + int send_size = input->ByteSize(); + + if (send_size > RPCMessageHeader::MAX_MESSAGE_SIZE) + { + out.printerr("In call to %s::%s: message too large: %d.\n", + this->proto.c_str(), this->name.c_str(), send_size); + return CR_LINK_FAILURE; + } + + if (!sendRemoteMessage(p_client->socket, id, input, true)) { out.printerr("In call to %s::%s: I/O error in send.\n", this->proto.c_str(), this->name.c_str()); @@ -388,7 +394,7 @@ command_result RemoteFunctionBase::execute(color_ostream &out, 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) + if (header.size < 0 || header.size > RPCMessageHeader::MAX_MESSAGE_SIZE) { out.printerr("In call to %s::%s: invalid received size %d.\n", this->proto.c_str(), this->name.c_str(), header.size); diff --git a/library/RemoteServer.cpp b/library/RemoteServer.cpp index ae8b0024c..bae62bb75 100644 --- a/library/RemoteServer.cpp +++ b/library/RemoteServer.cpp @@ -67,6 +67,11 @@ using dfproto::CoreTextNotification; using dfproto::CoreTextFragment; using google::protobuf::MessageLite; +bool readFullBuffer(CSimpleSocket *socket, void *buf, int size); +bool sendRemoteMessage(CSimpleSocket *socket, int16_t id, + const ::google::protobuf::MessageLite *msg, bool size_ready); + + RPCService::RPCService() { owner = NULL; @@ -183,7 +188,7 @@ void ServerConnection::connection_ostream::flush_proxy() buffer.clear(); - if (!sendRemoteMessage(owner->socket, RPC_REPLY_TEXT, &msg)) + if (!sendRemoteMessage(owner->socket, RPC_REPLY_TEXT, &msg, false)) { owner->in_error = true; Core::printerr("Error writing text into client socket.\n"); @@ -243,7 +248,7 @@ void ServerConnection::threadFn(void *arg) if (header.id == RPC_REQUEST_QUIT) break; - if (header.size < 0 || header.size > 2*1048576) + if (header.size < 0 || header.size > RPCMessageHeader::MAX_MESSAGE_SIZE) { out.printerr("In RPC server: invalid received size %d.\n", header.size); break; @@ -278,6 +283,8 @@ void ServerConnection::threadFn(void *arg) } else { + buf.reset(); + reply = fn->out(); res = fn->execute(me->stream); } @@ -287,16 +294,23 @@ void ServerConnection::threadFn(void *arg) if (me->in_error) break; - me->stream.flush(); - //out.print("Answer %d:%d\n", res, reply); // Send reply - int out_size = 0; + int out_size = (reply ? reply->ByteSize() : 0); + + if (out_size > RPCMessageHeader::MAX_MESSAGE_SIZE) + { + me->stream.printerr("In call to %s: reply too large: %d.\n", + (fn ? fn->name : "UNKNOWN"), out_size); + res = CR_LINK_FAILURE; + } + + me->stream.flush(); if (res == CR_OK && reply) { - if (!sendRemoteMessage(me->socket, RPC_REPLY_RESULT, reply, &out_size)) + if (!sendRemoteMessage(me->socket, RPC_REPLY_RESULT, reply, true)) { out.printerr("In RPC server: I/O error in send result.\n"); break; @@ -304,9 +318,6 @@ void ServerConnection::threadFn(void *arg) } else { - if (reply) - out_size = reply->ByteSize(); - header.id = RPC_REPLY_FAIL; header.size = res; diff --git a/library/RemoteTools.cpp b/library/RemoteTools.cpp index aa6324356..42bc84630 100644 --- a/library/RemoteTools.cpp +++ b/library/RemoteTools.cpp @@ -49,6 +49,18 @@ POSSIBILITY OF SUCH DAMAGE. #include "PluginManager.h" #include "MiscUtils.h" +#include "modules/Materials.h" + +#include "DataDefs.h" +#include "df/material.h" +#include "df/matter_state.h" +#include "df/inorganic_raw.h" +#include "df/creature_raw.h" +#include "df/plant_raw.h" +#include "df/historical_figure.h" + +#include "BasicApi.pb.h" + #include #include #include @@ -56,9 +68,9 @@ POSSIBILITY OF SUCH DAMAGE. #include using namespace DFHack; +using namespace df::enums; +using namespace dfproto; -using dfproto::CoreTextNotification; -using dfproto::CoreTextFragment; using google::protobuf::MessageLite; void DFHack::strVectorToRepeatedField(RepeatedPtrField *pf, @@ -68,6 +80,149 @@ void DFHack::strVectorToRepeatedField(RepeatedPtrField *pf, *pf->Add() = vec[i]; } +void DFHack::describeMaterial(BasicMaterialInfo *info, df::material *mat, + const BasicMaterialInfoMask *mask) +{ + info->set_token(mat->id); + + if (mask && mask->flags()) + flagarray_to_string(info->mutable_flags(), mat->flags); + + info->set_name_prefix(mat->prefix); + + if (!mask || mask->states_size() == 0) + { + df::matter_state state = matter_state::Solid; + int temp = (mask && mask->has_temperature()) ? mask->temperature() : 10015; + + if (temp >= mat->heat.melting_point) + state = matter_state::Liquid; + if (temp >= mat->heat.boiling_point) + state = matter_state::Gas; + + info->add_state_color(mat->state_color[state]); + info->add_state_name(mat->state_name[state]); + info->add_state_adj(mat->state_adj[state]); + } + else + { + for (int i = 0; i < mask->states_size(); i++) + { + info->add_state_color(mat->state_color[i]); + info->add_state_name(mat->state_name[i]); + info->add_state_adj(mat->state_adj[i]); + } + } + + if (mask && mask->reaction()) + { + for (size_t i = 0; i < mat->reaction_class.size(); i++) + info->add_reaction_class(*mat->reaction_class[i]); + + for (size_t i = 0; i < mat->reaction_product.id.size(); i++) + { + auto ptr = info->add_reaction_product(); + ptr->set_id(*mat->reaction_product.id[i]); + ptr->set_type(mat->reaction_product.material.mat_type[i]); + ptr->set_index(mat->reaction_product.material.mat_index[i]); + } + } +} + +void DFHack::describeMaterial(BasicMaterialInfo *info, const MaterialInfo &mat, + const BasicMaterialInfoMask *mask) +{ + assert(mat.isValid()); + + info->set_type(mat.type); + info->set_index(mat.index); + + describeMaterial(info, mat.material, mask); + + switch (mat.mode) { + case MaterialInfo::Inorganic: + info->set_token(mat.inorganic->id); + if (mask && mask->flags()) + flagarray_to_string(info->mutable_inorganic_flags(), mat.inorganic->flags); + break; + + case MaterialInfo::Creature: + info->set_subtype(mat.subtype); + if (mat.figure) + { + info->set_hfig_id(mat.index); + info->set_creature_id(mat.figure->race); + } + else + info->set_creature_id(mat.index); + break; + + case MaterialInfo::Plant: + info->set_plant_id(mat.index); + break; + } +} + +static void listMaterial(ListMaterialsRes *out, int type, int index, const BasicMaterialInfoMask *mask) +{ + MaterialInfo info(type, index); + if (info.isValid()) + describeMaterial(out->add_value(), info, mask); +} + +static command_result ListMaterials(color_ostream &stream, + const ListMaterialsRq *in, ListMaterialsRes *out) +{ + CoreSuspender suspend; + + auto mask = in->has_mask() ? &in->mask() : NULL; + + for (int i = 0; i < in->id_list_size(); i++) + { + auto &elt = in->id_list(i); + listMaterial(out, elt.type(), elt.index(), mask); + } + + if (in->builtin()) + { + for (int i = 0; i < MaterialInfo::NUM_BUILTIN; i++) + listMaterial(out, i, -1, mask); + } + + if (in->inorganic()) + { + auto &vec = df::inorganic_raw::get_vector(); + for (size_t i = 0; i < vec.size(); i++) + listMaterial(out, 0, i, mask); + } + + if (in->creatures()) + { + auto &vec = df::creature_raw::get_vector(); + for (size_t i = 0; i < vec.size(); i++) + { + auto praw = vec[i]; + + for (size_t j = 0; j < praw->material.size(); j++) + listMaterial(out, MaterialInfo::CREATURE_BASE+j, i, mask); + } + } + + if (in->plants()) + { + auto &vec = df::plant_raw::get_vector(); + for (size_t i = 0; i < vec.size(); i++) + { + auto praw = vec[i]; + + for (size_t j = 0; j < praw->material.size(); j++) + listMaterial(out, MaterialInfo::PLANT_BASE+j, i, mask); + } + } + + return out->value_size() ? CR_OK : CR_NOT_FOUND; +} + CoreService::CoreService() { suspend_depth = 0; @@ -78,6 +233,8 @@ CoreService::CoreService() { // Add others here: addMethod("CoreSuspend", &CoreService::CoreSuspend); addMethod("CoreResume", &CoreService::CoreResume); + + addFunction("ListMaterials", ListMaterials); } CoreService::~CoreService() diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h index 46662efca..99694d949 100644 --- a/library/include/DataDefs.h +++ b/library/include/DataDefs.h @@ -373,7 +373,7 @@ namespace DFHack { * Find a flag array item by key string. Returns success code. */ template - inline bool find_bitfield_field(unsigned *idx, const std::string &name, const BitArray*) { + inline bool find_flagarray_field(unsigned *idx, const std::string &name, const BitArray*) { T tmp; if (!find_enum_item(&tmp, name) || tmp < 0) return false; *idx = unsigned(tmp); @@ -384,7 +384,7 @@ namespace DFHack { * Find a flag array item by key and set its value. Returns success code. */ template - inline bool set_bitfield_field(BitArray *bitfield, const std::string &name, int value) + inline bool set_flagarray_field(BitArray *bitfield, const std::string &name, int value) { T tmp; if (!find_enum_item(&tmp, name) || tmp < 0) return false; @@ -396,7 +396,7 @@ namespace DFHack { * Find a flag array item by key and retrieve its value. Returns success code. */ template - inline bool get_bitfield_field(int *value, const BitArray &bitfield, const std::string &name) + inline bool get_flagarray_field(int *value, const BitArray &bitfield, const std::string &name) { T tmp; if (!find_enum_item(&tmp, name) || tmp < 0) return false; @@ -411,13 +411,22 @@ namespace DFHack { * Represent flag array bits as strings in a vector. */ template - inline void bitfield_to_string(std::vector *pvec, const BitArray &val) { + inline void flagarray_to_string(std::vector *pvec, const BitArray &val) { typedef df::enum_traits traits; int size = traits::last_item_value-traits::first_item_value+1; flagarrayToString(pvec, val.bits, val.size, (int)traits::first_item_value, size, traits::key_table); } + /** + * Represent flag array bits as a string, using sep as join separator. + */ + template + inline std::string bitfield_to_string(const BitArray &val, const std::string &sep = " ") { + std::vector tmp; + flagarray_to_string(&tmp, val); + return join_strings(sep, tmp); + } } #define ENUM_ATTR(enum,attr,val) (df::enum_traits::attrs(val).attr) diff --git a/library/include/RemoteClient.h b/library/include/RemoteClient.h index 7858223f1..9eb8fb427 100644 --- a/library/include/RemoteClient.h +++ b/library/include/RemoteClient.h @@ -66,6 +66,8 @@ namespace DFHack }; struct RPCMessageHeader { + static const int MAX_MESSAGE_SIZE = 8*1048756; + int16_t id; int32_t size; }; @@ -220,10 +222,6 @@ namespace DFHack } }; - 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; diff --git a/library/include/RemoteTools.h b/library/include/RemoteTools.h index 00d5e5281..44a414467 100644 --- a/library/include/RemoteTools.h +++ b/library/include/RemoteTools.h @@ -29,8 +29,17 @@ distribution. #include "DataDefs.h" +#include "Basic.pb.h" + +namespace df +{ + struct material; +} + namespace DFHack { + class MaterialInfo; + using google::protobuf::RepeatedPtrField; DFHACK_EXPORT void strVectorToRepeatedField(RepeatedPtrField *pf, @@ -46,6 +55,27 @@ namespace DFHack strVectorToRepeatedField(pf, tmp); } + /** + * Represent flagarray bits as a repeated string field. + */ + template + inline void flagarray_to_string(RepeatedPtrField *pf, const BitArray &val) { + std::vector tmp; + flagarray_to_string(&tmp, val); + strVectorToRepeatedField(pf, tmp); + } + + ///// + + using dfproto::BasicMaterialInfo; + using dfproto::BasicMaterialInfoMask; + + DFHACK_EXPORT void describeMaterial(BasicMaterialInfo *info, df::material *mat, + const BasicMaterialInfoMask *mask = NULL); + DFHACK_EXPORT void describeMaterial(BasicMaterialInfo *info, const MaterialInfo &mat, + const BasicMaterialInfoMask *mask = NULL); + + ///// class CoreService : public RPCService { int suspend_depth; diff --git a/library/proto/Basic.proto b/library/proto/Basic.proto new file mode 100644 index 000000000..92a993b70 --- /dev/null +++ b/library/proto/Basic.proto @@ -0,0 +1,54 @@ +package dfproto; + +option optimize_for = LITE_RUNTIME; + +message BasicMaterialId { + required int32 type = 1; + required sint32 index = 2; +}; + +message BasicMaterialInfo { + required int32 type = 1; + required sint32 index = 2; + + required string token = 3; + repeated string flags = 4; + + optional int32 subtype = 5 [default = -1]; + optional int32 creature_id = 6 [default = -1]; + optional int32 plant_id = 7 [default = -1]; + optional int32 hfig_id = 8 [default = -1]; + + optional string name_prefix = 9 [default = ""]; + + repeated fixed32 state_color = 10; + repeated string state_name = 11; + repeated string state_adj = 12; + + message Product { + required string id = 1; + required int32 type = 2; + required sint32 index = 3; + }; + repeated string reaction_class = 13; + repeated Product reaction_product = 14; + + repeated string inorganic_flags = 15; +}; + +message BasicMaterialInfoMask { + enum StateType { + Solid = 0; + Liquid = 1; + Gas = 2; + Powder = 3; + Paste = 4; + Pressed = 5; + }; + repeated StateType states = 1; + + optional bool flags = 2 [default = false]; + optional bool reaction = 3 [default = false]; + optional int32 temperature = 4; +}; + diff --git a/library/proto/BasicApi.proto b/library/proto/BasicApi.proto new file mode 100644 index 000000000..b5a569862 --- /dev/null +++ b/library/proto/BasicApi.proto @@ -0,0 +1,17 @@ +package dfproto; + +option optimize_for = LITE_RUNTIME; + +import "Basic.proto"; + +message ListMaterialsRq { + optional BasicMaterialInfoMask mask = 1; + repeated BasicMaterialId id_list = 2; + optional bool builtin = 3; + optional bool inorganic = 4; + optional bool creatures = 5; + optional bool plants = 6; +}; +message ListMaterialsRes { + repeated BasicMaterialInfo value = 1; +};