From 8e933fb75e3c0607b39b60d2caae15c1be8abeff Mon Sep 17 00:00:00 2001 From: Quietust Date: Tue, 13 Mar 2012 12:17:00 -0500 Subject: [PATCH 01/12] Ignore the .rule file too --- library/proto/.gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/library/proto/.gitignore b/library/proto/.gitignore index a1fb10cce..befabf79d 100644 --- a/library/proto/.gitignore +++ b/library/proto/.gitignore @@ -1,2 +1,3 @@ *.pb.cc +*.pb.cc.rule *.pb.h From 181f1c6ac660618537d5ae22b6a5d26c36f58e93 Mon Sep 17 00:00:00 2001 From: Quietust Date: Tue, 13 Mar 2012 13:15:12 -0500 Subject: [PATCH 02/12] Silence warning --- plugins/vdig.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/vdig.cpp b/plugins/vdig.cpp index a56d95db0..9d96f2fef 100644 --- a/plugins/vdig.cpp +++ b/plugins/vdig.cpp @@ -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) From 778a1cd6bb004fd82f01a8289e61811733d3c45b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Tue, 13 Mar 2012 20:00:20 +0100 Subject: [PATCH 03/12] Fix problem with solution folders being generated for MSVC Express Uses MFC header availability to detect Express versions. --- CMakeLists.txt | 6 ++++-- depends/CMakeLists.txt | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 367e68f60..a340f2f5c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,13 +11,15 @@ else(CMAKE_CONFIGURATION_TYPES) endif(CMAKE_CONFIGURATION_TYPES) # set up folder structures for IDE solutions -if(NOT CMAKE_USING_VC_FREE_TOOLS) +# MSVC Express won't load solutions that use this. It also doesn't include MFC supported +# Check for MFC! +find_package(MFC QUIET) +if(MFC_FOUND OR (NOT MSVC)) SET_PROPERTY(GLOBAL PROPERTY USE_FOLDERS ON) OPTION(CMAKE_USE_FOLDERS "Enable folder grouping of projects in IDEs." ON) else() OPTION(CMAKE_USE_FOLDERS "Enable folder grouping of projects in IDEs." OFF) endif() -MARK_AS_ADVANCED(CMAKE_USE_FOLDERS) # macro for setting up IDE folders without nasty IF()s everywhere MACRO(IDE_FOLDER target folder) diff --git a/depends/CMakeLists.txt b/depends/CMakeLists.txt index d879657e0..4f091ed63 100644 --- a/depends/CMakeLists.txt +++ b/depends/CMakeLists.txt @@ -8,3 +8,4 @@ add_subdirectory(tthread) OPTION(CLSOCKET_SHARED "Build clsocket lib as shared." OFF) OPTION(CLSOCKET_DEP_ONLY "Build for use inside other CMake projects as dependency." ON) add_subdirectory(clsocket) +IDE_FOLDER(clsocket "Depends") \ No newline at end of file From 36e3aada8e1064b678fc704f74191157aa327206 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Tue, 13 Mar 2012 21:05:25 +0100 Subject: [PATCH 04/12] More tweaks to MSVC solution folders. --- CMakeLists.txt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index a340f2f5c..6bb92ec40 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -15,12 +15,17 @@ endif(CMAKE_CONFIGURATION_TYPES) # Check for MFC! find_package(MFC QUIET) if(MFC_FOUND OR (NOT MSVC)) - SET_PROPERTY(GLOBAL PROPERTY USE_FOLDERS ON) OPTION(CMAKE_USE_FOLDERS "Enable folder grouping of projects in IDEs." ON) else() OPTION(CMAKE_USE_FOLDERS "Enable folder grouping of projects in IDEs." OFF) endif() +if(CMAKE_USE_FOLDERS) + SET_PROPERTY(GLOBAL PROPERTY USE_FOLDERS ON) +else() + SET_PROPERTY(GLOBAL PROPERTY USE_FOLDERS OFF) +endif() + # macro for setting up IDE folders without nasty IF()s everywhere MACRO(IDE_FOLDER target folder) if(CMAKE_USE_FOLDERS) From 2725fe5568e616c7c870b81b456367d9b336fe2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Tue, 13 Mar 2012 21:16:30 +0100 Subject: [PATCH 05/12] More tweaking. --- CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6bb92ec40..c34202f2c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,10 @@ else(CMAKE_CONFIGURATION_TYPES) endif (NOT CMAKE_BUILD_TYPE) endif(CMAKE_CONFIGURATION_TYPES) +## some generic CMake magic +cmake_minimum_required(VERSION 2.8 FATAL_ERROR) +project(dfhack) + # set up folder structures for IDE solutions # MSVC Express won't load solutions that use this. It also doesn't include MFC supported # Check for MFC! @@ -33,10 +37,6 @@ MACRO(IDE_FOLDER target folder) endif() ENDMACRO() -## some generic CMake magic -cmake_minimum_required(VERSION 2.8 FATAL_ERROR) -project(dfhack) - SET(CMAKE_MODULE_PATH ${dfhack_SOURCE_DIR}/CMake/Modules ${CMAKE_MODULE_PATH} From a4ce1fff1386d1aa531fa6bfdd875d79b6acaaa4 Mon Sep 17 00:00:00 2001 From: Quietust Date: Tue, 13 Mar 2012 15:40:38 -0500 Subject: [PATCH 06/12] Update MapCache to allow decoding tiles underneath ice and constructions, and allow revflood to take those into account --- library/include/modules/MapCache.h | 159 +++++++++++++++++++---------- plugins/reveal.cpp | 51 +++++---- 2 files changed, 132 insertions(+), 78 deletions(-) diff --git a/library/include/modules/MapCache.h b/library/include/modules/MapCache.h index 8468f5a0d..ac1737c41 100644 --- a/library/include/modules/MapCache.h +++ b/library/include/modules/MapCache.h @@ -32,6 +32,7 @@ distribution. #include #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 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 > * 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 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 > * 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 > * layerassign = 0) + Block(DFCoord _bcoord, std::vector< std::vector > * 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 ::iterator iter = blocks.find(blockcoord); + std::map ::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::iterator p; + std::map::iterator p; for(p = blocks.begin(); p != blocks.end(); p++) { p->second->Write(); @@ -442,7 +489,7 @@ class MapCache } void trash() { - std::map::iterator p; + std::map::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 > layerassign; - std::map blocks; + std::map blocks; }; } #endif \ No newline at end of file diff --git a/plugins/reveal.cpp b/plugins/reveal.cpp index 4109140d3..92ae89eb3 100644 --- a/plugins/reveal.cpp +++ b/plugins/reveal.cpp @@ -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 hidesaved; +vector 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 & params); -command_result unreveal(color_ostream &out, std::vector & params); -command_result revtoggle(color_ostream &out, std::vector & params); -command_result revflood(color_ostream &out, std::vector & params); -command_result revforget(color_ostream &out, std::vector & params); -command_result nopause(color_ostream &out, std::vector & params); +command_result reveal(color_ostream &out, vector & params); +command_result unreveal(color_ostream &out, vector & params); +command_result revtoggle(color_ostream &out, vector & params); +command_result revflood(color_ostream &out, vector & params); +command_result revforget(color_ostream &out, vector & params); +command_result nopause(color_ostream &out, vector & params); DFHACK_PLUGIN("reveal"); -DFhackCExport command_result plugin_init ( color_ostream &out, std::vector &commands) +DFhackCExport command_result plugin_init ( color_ostream &out, vector &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 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 & parameters) +command_result nopause (color_ostream &out, vector & 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 & params) +command_result reveal(color_ostream &out, vector & params) { bool no_hell = true; bool pause = true; @@ -179,7 +186,7 @@ command_result reveal(color_ostream &out, std::vector & 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 & 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 & params) return CR_OK; } -command_result unreveal(color_ostream &out, std::vector & params) +command_result unreveal(color_ostream &out, vector & 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 & 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 & params) return CR_OK; } -command_result revtoggle (color_ostream &out, std::vector & params) +command_result revtoggle (color_ostream &out, vector & params) { for(size_t i = 0; i < params.size();i++) { @@ -317,7 +324,7 @@ command_result revtoggle (color_ostream &out, std::vector & params) } } -command_result revflood(color_ostream &out, std::vector & params) +command_result revflood(color_ostream &out, vector & params) { for(size_t i = 0; i < params.size();i++) { @@ -396,7 +403,7 @@ command_result revflood(color_ostream &out, std::vector & 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 & params) return CR_OK; } -command_result revforget(color_ostream &out, std::vector & params) +command_result revforget(color_ostream &out, vector & params) { auto & con = out; for(size_t i = 0; i < params.size();i++) From a05cbcc2de1921ba02a937333246a8410eed8296 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 14 Mar 2012 10:56:30 +0400 Subject: [PATCH 07/12] Rename the world substruct with the save directory name. --- .gitignore | 1 + library/modules/World.cpp | 2 +- library/xml | 2 +- plugins/feature.cpp | 12 ++++++------ 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index a19302fe4..81b55ffdb 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ build/bin build/library build/tools build/plugins +build/depends #ignore Kdevelop stuff .kdev4 diff --git a/library/modules/World.cpp b/library/modules/World.cpp index 406303ebd..cf3140e69 100644 --- a/library/modules/World.cpp +++ b/library/modules/World.cpp @@ -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) diff --git a/library/xml b/library/xml index 24e809abe..136181f06 160000 --- a/library/xml +++ b/library/xml @@ -1 +1 @@ -Subproject commit 24e809abe07eb139c9f53c358ed3fbd3d8075410 +Subproject commit 136181f067a0a5ed19a19c9f98eece41003fe372 diff --git a/plugins/feature.cpp b/plugins/feature.cpp index de0b270e5..cadd91b69 100644 --- a/plugins/feature.cpp +++ b/plugins/feature.cpp @@ -33,9 +33,9 @@ static command_result feature(color_ostream &out, vector ¶meters) { 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 ¶meters) 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 ¶meters) 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"); From c42e2ff053bc3acbded353112cd6412c8211f279 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 14 Mar 2012 12:09:02 +0400 Subject: [PATCH 08/12] Support crafts and add a couple of options in workflow. --- library/include/DataDefs.h | 2 +- plugins/workflow.cpp | 96 +++++++++++++++++++++++++++++++++----- 2 files changed, 86 insertions(+), 12 deletions(-) diff --git a/library/include/DataDefs.h b/library/include/DataDefs.h index 4e18f02f2..78c59f51d 100644 --- a/library/include/DataDefs.h +++ b/library/include/DataDefs.h @@ -134,7 +134,7 @@ namespace DFHack template struct enum_list_attr { - int size; + size_t size; const T *items; }; diff --git a/plugins/workflow.cpp b/plugins/workflow.cpp index 57d7c62da..e59856160 100644 --- a/plugins/workflow.cpp +++ b/plugins/workflow.cpp @@ -78,11 +78,15 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector [cnt-gap]\n" " workflow amount [cnt-gap]\n" " Set a constraint. The first form counts each stack as only 1 item.\n" " workflow unlimit \n" " Delete a constraint.\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" @@ -106,6 +110,8 @@ DFhackCExport command_result plugin_init (color_ostream &out, std::vector , bool> TMaterialCache; struct ItemConstraint { PersistentDataItem config; + bool is_craft; ItemTypeInfo item; MaterialInfo material; @@ -286,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) {} @@ -645,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; } @@ -682,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; @@ -719,9 +732,22 @@ static void delete_constraint(color_ostream &out, 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); @@ -729,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) @@ -878,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 @@ -918,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, \ @@ -1116,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); @@ -1228,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) @@ -1518,6 +1570,17 @@ static command_result workflow_cmd(color_ostream &out, vector & 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) @@ -1561,6 +1624,17 @@ static command_result workflow_cmd(color_ostream &out, vector & paramet out.printerr("Constraint not found: %s\n", parameters[1].c_str()); return CR_FAILURE; } + else if (cmd == "unlimit-all") + { + if (parameters.size() != 1) + return CR_WRONG_USAGE; + + while (!constraints.empty()) + delete_constraint(out, constraints[0]); + + out.print("Removed all constraints.\n"); + return CR_OK; + } else return CR_WRONG_USAGE; } From 560e977f0589ac1c0feb6ea825d20d351e325826 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 14 Mar 2012 19:57:29 +0400 Subject: [PATCH 09/12] Implement trivial RPC interface for dfhack via TCP & protobufs. Use it to make an executable capable of calling commands remotely. --- CMakeLists.txt | 6 + library/CMakeLists.txt | 21 +- library/ColorText.cpp | 23 +- library/Core.cpp | 8 + library/MiscUtils.cpp | 1 - library/RemoteClient.cpp | 359 +++++++++++++++++++++++++++++++ library/RemoteServer.cpp | 359 +++++++++++++++++++++++++++++++ library/dfhack-run.cpp | 90 ++++++++ library/include/ColorText.h | 30 ++- library/include/Core.h | 4 + library/include/PluginManager.h | 11 +- library/include/RemoteClient.h | 169 +++++++++++++++ library/include/RemoteServer.h | 189 ++++++++++++++++ library/proto/CoreProtocol.proto | 14 +- plugins/Plugins.cmake | 1 + 15 files changed, 1260 insertions(+), 25 deletions(-) create mode 100644 library/RemoteClient.cpp create mode 100644 library/RemoteServer.cpp create mode 100644 library/dfhack-run.cpp create mode 100644 library/include/RemoteClient.h create mode 100644 library/include/RemoteServer.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c34202f2c..4d8977bfd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -104,6 +104,12 @@ ENDIF() # use shared libraries for protobuf ADD_DEFINITIONS(-DPROTOBUF_USE_DLLS) +if(UNIX) + add_definitions(-D_LINUX) +elseif(WIN32) + add_definitions(-DWIN32) +endif() + #### expose depends #### # find and make available libz diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 0521184ce..cef56d4da 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -10,6 +10,7 @@ IF(UNIX) ENDIF() include_directories (include) +include_directories (proto) SET(PERL_EXECUTABLE "perl" CACHE FILEPATH "This is the perl executable to run in the codegen step. Tweak it if you need to run a specific one.") @@ -41,6 +42,8 @@ include/Types.h include/VersionInfo.h include/VersionInfoFactory.h include/Virtual.h +include/RemoteClient.h +include/RemoteServer.h ) SET(MAIN_HEADERS_WINDOWS @@ -58,6 +61,8 @@ PluginManager.cpp TileTypes.cpp VersionInfoFactory.cpp Virtual.cpp +RemoteClient.cpp +RemoteServer.cpp ) SET(MAIN_SOURCES_WINDOWS @@ -158,7 +163,7 @@ STRING(REPLACE ".proto" ".pb.cc" PROJECT_PROTO_SRCS ${PROJECT_PROTOS}) STRING(REPLACE ".proto" ".pb.h" PROJECT_PROTO_HDRS ${PROJECT_PROTOS}) LIST(APPEND PROJECT_HEADERS ${PROJECT_PROTO_HDRS}) -LIST(APPEND PROJECT_HEADERS ${PROJECT_PROTO_SRCS}) +LIST(APPEND PROJECT_SOURCES ${PROJECT_PROTO_SRCS}) ADD_CUSTOM_COMMAND( OUTPUT ${PROJECT_PROTO_SRCS} ${PROJECT_PROTO_HDRS} @@ -211,6 +216,11 @@ ENDIF() ADD_LIBRARY(dfhack SHARED ${PROJECT_SOURCES}) ADD_DEPENDENCIES(dfhack generate_headers) +ADD_EXECUTABLE(dfhack-run dfhack-run.cpp + RemoteClient.cpp ColorText.cpp MiscUtils.cpp + ${PROJECT_PROTO_SRCS}) +ADD_DEPENDENCIES(dfhack-run dfhack) + IF(BUILD_EGGY) SET_TARGET_PROPERTIES(dfhack PROPERTIES OUTPUT_NAME "egg" ) else() @@ -221,8 +231,10 @@ endif() IF(WIN32) SET_TARGET_PROPERTIES(dfhack PROPERTIES COMPILE_FLAGS "/FI\"Export.h\"" ) + SET_TARGET_PROPERTIES(dfhack-run PROPERTIES COMPILE_FLAGS "/FI\"Export.h\"" ) ELSE() SET_TARGET_PROPERTIES(dfhack PROPERTIES COMPILE_FLAGS "-include Export.h" ) + SET_TARGET_PROPERTIES(dfhack-run PROPERTIES COMPILE_FLAGS "-include Export.h" ) ENDIF() #effectively disables debug builds... @@ -231,6 +243,8 @@ SET_TARGET_PROPERTIES(dfhack PROPERTIES DEBUG_POSTFIX "-debug" ) TARGET_LINK_LIBRARIES(dfhack protobuf-lite clsocket ${PROJECT_LIBS}) SET_TARGET_PROPERTIES(dfhack PROPERTIES LINK_INTERFACE_LIBRARIES "") +TARGET_LINK_LIBRARIES(dfhack-run protobuf-lite clsocket) + IF(UNIX) # On linux, copy our version of the df launch script which sets LD_PRELOAD install(PROGRAMS ${dfhack_SOURCE_DIR}/package/linux/dfhack @@ -253,6 +267,7 @@ else() LIBRARY DESTINATION ${DFHACK_EGGY_DESTINATION} RUNTIME DESTINATION ${DFHACK_EGGY_DESTINATION}) endif() + #install the offset file install(FILES xml/symbols.xml DESTINATION ${DFHACK_DATA_DESTINATION}) #linux: share/dfhack @@ -260,6 +275,10 @@ install(FILES xml/symbols.xml install(FILES ../dfhack.init-example DESTINATION ${DFHACK_BINARY_DESTINATION}) +install(TARGETS dfhack-run + LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION} + RUNTIME DESTINATION ${DFHACK_LIBRARY_DESTINATION}) + # Unused for so long that it's not even relevant now... if(BUILD_DEVEL) if(WIN32) diff --git a/library/ColorText.cpp b/library/ColorText.cpp index fa5d57e0b..c399504be 100644 --- a/library/ColorText.cpp +++ b/library/ColorText.cpp @@ -143,6 +143,16 @@ void color_ostream::reset_color(void) color(COLOR_RESET); } +void color_ostream_wrapper::add_text(color_value, const std::string &text) +{ + out << text; +} + +void color_ostream_wrapper::flush_proxy() +{ + out << std::flush; +} + void buffered_color_ostream::add_text(color_value color, const std::string &text) { if (text.empty()) @@ -165,23 +175,20 @@ void buffered_color_ostream::add_text(color_value color, const std::string &text void color_ostream_proxy::flush_proxy() { - if (!buffer.empty()) + if (buffer.empty()) + return; + + if (target) { target->begin_batch(); for (auto it = buffer.begin(); it != buffer.end(); ++it) target->add_text(it->first, it->second); - buffer.clear(); - target->end_batch(); } -} -color_ostream_proxy::color_ostream_proxy(color_ostream &target) - : target(&target) -{ - // + buffer.clear(); } color_ostream_proxy::~color_ostream_proxy() diff --git a/library/Core.cpp b/library/Core.cpp index 9a005d332..0765f4cd1 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -48,6 +48,7 @@ using namespace std; #include "modules/World.h" #include "modules/Graphic.h" #include "modules/Windows.h" +#include "RemoteServer.h" using namespace DFHack; #include "df/ui.h" @@ -570,6 +571,7 @@ Core::Core() last_world_data_ptr = NULL; top_viewscreen = NULL; screen_window = NULL; + server = NULL; }; void Core::fatal (std::string output, bool deactivate) @@ -678,6 +680,12 @@ bool Core::Init() screen_window = new Windows::top_level_window(); screen_window->addChild(new Windows::dfhack_dummy(5,10)); started = true; + + cerr << "Starting the TCP listener.\n"; + server = new ServerMain(); + if (!server->listen(RemoteClient::GetDefaultPort())) + cerr << "TCP listen failed.\n"; + cerr << "DFHack is running.\n"; return true; } diff --git a/library/MiscUtils.cpp b/library/MiscUtils.cpp index aa141313b..dff8af0a0 100644 --- a/library/MiscUtils.cpp +++ b/library/MiscUtils.cpp @@ -24,7 +24,6 @@ distribution. #include "Internal.h" #include "Export.h" -#include "Core.h" #include "MiscUtils.h" #ifndef LINUX_BUILD diff --git a/library/RemoteClient.cpp b/library/RemoteClient.cpp new file mode 100644 index 000000000..a8c804664 --- /dev/null +++ b/library/RemoteClient.cpp @@ -0,0 +1,359 @@ +/* +https://github.com/peterix/dfhack +Copyright (c) 2011 Petr Mrázek + +A thread-safe logging console with a line editor for windows. + +Based on linenoise win32 port, +copyright 2010, Jon Griffiths . +All rights reserved. +Based on linenoise, copyright 2010, Salvatore Sanfilippo . +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 +#include +#include +#include +#include +#include +#include + +#include "RemoteClient.h" +#include "MiscUtils.h" + +#include +#include +#include + +#include + +using namespace DFHack; + +#include "tinythread.h" +using namespace tthread; + +using dfproto::CoreTextNotification; + +using google::protobuf::MessageLite; + +const char RPCHandshakeHeader::REQUEST_MAGIC[9] = "DFHack?\n"; +const char RPCHandshakeHeader::RESPONSE_MAGIC[9] = "DFHack!\n"; + +void color_ostream_proxy::decode(CoreTextNotification *data) +{ + flush_proxy(); + + int cnt = data->fragments_size(); + if (cnt > 0) { + target->begin_batch(); + + for (int i = 0; i < cnt; i++) + { + auto &frag = data->fragments(i); + + color_value color = frag.has_color() ? color_value(frag.color()) : COLOR_RESET; + target->add_text(color, frag.text()); + } + + target->end_batch(); + } +} + +RemoteClient::RemoteClient() +{ + active = false; +} + +RemoteClient::~RemoteClient() +{ + disconnect(); +} + +bool DFHack::readFullBuffer(CSimpleSocket &socket, void *buf, int size) +{ + if (!socket.IsSocketValid()) + return false; + + char *ptr = (char*)buf; + while (size > 0) { + int cnt = socket.Receive(size); + if (cnt <= 0) + return false; + memcpy(ptr, socket.GetData(), cnt); + ptr += cnt; + size -= cnt; + } + + return true; +} + +int RemoteClient::GetDefaultPort() +{ + const char *port = getenv("DFHACK_PORT"); + if (!port) port = "0"; + + int portval = atoi(port); + if (portval <= 0) + return 5000; + else + return portval; +} + +bool RemoteClient::connect(int port) +{ + assert(!active); + + if (port <= 0) + port = GetDefaultPort(); + + if (!socket.Initialize()) + { + std::cerr << "Socket init failed." << endl; + return false; + } + + if (!socket.Open((const uint8 *)"localhost", port)) + { + std::cerr << "Could not connect to localhost:" << port << endl; + return false; + } + + active = true; + + RPCHandshakeHeader header; + memcpy(header.magic, RPCHandshakeHeader::REQUEST_MAGIC, sizeof(header.magic)); + header.version = 1; + + if (socket.Send((uint8*)&header, sizeof(header)) != sizeof(header)) + { + std::cerr << "Could not send header." << endl; + socket.Close(); + return active = false; + } + + if (!readFullBuffer(socket, &header, sizeof(header))) + { + std::cerr << "Could not read header." << endl; + socket.Close(); + return active = false; + } + + if (memcmp(header.magic, RPCHandshakeHeader::RESPONSE_MAGIC, sizeof(header.magic)) || + header.version != 1) + { + std::cerr << "Invalid handshake response." << endl; + socket.Close(); + return active = false; + } + + bind_call.name = "BindMethod"; + bind_call.p_client = this; + bind_call.id = 0; + + return true; +} + +void RemoteClient::disconnect() +{ + if (active && socket.IsSocketValid()) + { + RPCMessageHeader header; + header.id = RPC_REQUEST_QUIT; + header.size = 0; + if (socket.Send((uint8_t*)&header, sizeof(header)) != sizeof(header)) + std::cerr << "Could not send the disconnect message." << endl; + } + + socket.Close(); +} + +bool RemoteClient::bind(color_ostream &out, RemoteFunctionBase *function, + const std::string &name, const std::string &proto) +{ + if (!active || !socket.IsSocketValid()) + return false; + + bind_call.reset(); + + { + auto in = bind_call.in(); + + in->set_method(name); + if (!proto.empty()) + in->set_plugin(proto); + in->set_input_msg(function->p_in_template->GetTypeName()); + in->set_output_msg(function->p_out_template->GetTypeName()); + } + + if (bind_call.execute(out) != CR_OK) + return false; + + function->p_client = this; + function->id = bind_call.out()->assigned_id(); + + return true; +} + +void RPCFunctionBase::reset(bool free) +{ + if (free) + { + delete p_in; + delete p_out; + p_in = p_out = NULL; + } + else + { + if (p_in) + p_in->Clear(); + if (p_out) + p_out->Clear(); + } +} + +bool RemoteFunctionBase::bind(color_ostream &out, RemoteClient *client, + const std::string &name, const std::string &proto) +{ + if (p_client == client) + return true; + + if (p_client) + { + out.printerr("Function already bound to %s::%s\n", + this->proto.c_str(), this->name.c_str()); + return false; + } + + this->name = name; + this->proto = proto; + + return client->bind(out, this, name, proto); +} + +bool DFHack::sendRemoteMessage(CSimpleSocket &socket, int16_t id, const MessageLite *msg) +{ + int size = msg->ByteSize(); + int fullsz = size + sizeof(RPCMessageHeader); + + std::auto_ptr 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 buf(new uint8_t[header.size]); + + if (!readFullBuffer(p_client->socket, buf.get(), header.size)) + { + out.printerr("In call to %s::%s: I/O error in receive %d bytes of data.\n", + this->proto.c_str(), this->name.c_str(), header.size); + return CR_FAILURE; + } + + switch (header.id) { + case RPC_REPLY_RESULT: + if (!output->ParseFromArray(buf.get(), header.size)) + { + out.printerr("In call to %s::%s: error parsing received result.\n", + this->proto.c_str(), this->name.c_str()); + return CR_FAILURE; + } + + return CR_OK; + + case RPC_REPLY_TEXT: + text_data.Clear(); + if (text_data.ParseFromArray(buf.get(), header.size)) + text_decoder.decode(&text_data); + else + out.printerr("In call to %s::%s: received invalid text data.\n", + this->proto.c_str(), this->name.c_str()); + break; + + default: + break; + } + } +} diff --git a/library/RemoteServer.cpp b/library/RemoteServer.cpp new file mode 100644 index 000000000..5b0825700 --- /dev/null +++ b/library/RemoteServer.cpp @@ -0,0 +1,359 @@ +/* +https://github.com/peterix/dfhack +Copyright (c) 2011 Petr Mrázek + +A thread-safe logging console with a line editor for windows. + +Based on linenoise win32 port, +copyright 2010, Jon Griffiths . +All rights reserved. +Based on linenoise, copyright 2010, Salvatore Sanfilippo . +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 +#include +#include +#include +#include +#include +#include + +#include "RemoteServer.h" +#include "PluginManager.h" +#include "MiscUtils.h" + +#include +#include +#include + +#include + +using namespace DFHack; + +#include "tinythread.h" +using namespace tthread; + +using dfproto::CoreTextNotification; +using dfproto::CoreTextFragment; +using google::protobuf::MessageLite; + +CoreService::CoreService() { + // This must be the first method, so that it gets id 0 + addMethod("BindMethod", &CoreService::BindMethod); + + addMethod("RunCommand", &CoreService::RunCommand); +} + +command_result CoreService::BindMethod(color_ostream &stream, + const dfproto::CoreBindRequest *in, + dfproto::CoreBindReply *out) +{ + ServerFunctionBase *fn = connection()->findFunction(in->plugin(), in->method()); + + if (!fn) + { + stream.printerr("RPC method not found: %s::%s\n", + in->plugin().c_str(), in->method().c_str()); + return CR_FAILURE; + } + + if (fn->p_in_template->GetTypeName() != in->input_msg() || + fn->p_out_template->GetTypeName() != in->output_msg()) + { + stream.printerr("Requested wrong signature for RPC method: %s::%s\n", + in->plugin().c_str(), in->method().c_str()); + return CR_FAILURE; + } + + out->set_assigned_id(fn->getId()); + return CR_OK; +} + +command_result CoreService::RunCommand(color_ostream &stream, + const dfproto::CoreRunCommandRequest *in, + CoreVoidReply*) +{ + std::string cmd = in->command(); + std::vector args; + for (int i = 0; i < in->arguments_size(); i++) + args.push_back(in->arguments(i)); + + return Core::getInstance().plug_mgr->InvokeCommand(stream, cmd, args, false); +} + +RPCService::RPCService() +{ + owner = NULL; + holder = NULL; +} + +RPCService::~RPCService() +{ + for (size_t i = 0; i < functions.size(); i++) + delete functions[i]; +} + +ServerFunctionBase *RPCService::getFunction(const std::string &name) +{ + assert(owner); + return lookup[name]; +} + +void RPCService::finalize(ServerConnection *owner, std::vector *ftable) +{ + this->owner = owner; + + for (size_t i = 0; i < functions.size(); i++) + { + auto fn = functions[i]; + + fn->id = (int16_t)ftable->size(); + ftable->push_back(fn); + + lookup[fn->name] = fn; + } +} + +ServerConnection::ServerConnection(CActiveSocket *socket) + : socket(socket), stream(this) +{ + in_error = false; + + core_service = new CoreService(); + core_service->finalize(this, &functions); + + thread = new tthread::thread(threadFn, (void*)this); +} + +ServerConnection::~ServerConnection() +{ + in_error = true; + socket->Close(); + delete socket; + + for (auto it = plugin_services.begin(); it != plugin_services.end(); ++it) + delete it->second; + + delete core_service; +} + +ServerFunctionBase *ServerConnection::findFunction(const std::string &plugin, const std::string &name) +{ + if (plugin.empty()) + return core_service->getFunction(name); + else + return NULL; // todo: add plugin api support +} + +void ServerConnection::connection_ostream::flush_proxy() +{ + if (owner->in_error) + { + buffer.clear(); + return; + } + + if (buffer.empty()) + return; + + CoreTextNotification msg; + + for (auto it = buffer.begin(); it != buffer.end(); ++it) + { + auto frag = msg.add_fragments(); + frag->set_text(it->second); + if (it->first >= 0) + frag->set_color(CoreTextFragment::Color(it->first)); + } + + buffer.clear(); + + if (!sendRemoteMessage(*owner->socket, RPC_REPLY_TEXT, &msg)) + { + owner->in_error = true; + Core::printerr("Error writing text into client socket.\n"); + } +} + +void ServerConnection::threadFn(void *arg) +{ + ServerConnection *me = (ServerConnection*)arg; + color_ostream_proxy out(Core::getInstance().getConsole()); + + /* Handshake */ + + { + RPCHandshakeHeader header; + + if (!readFullBuffer(*me->socket, &header, sizeof(header))) + { + out << "In RPC server: could not read handshake header." << endl; + delete me; + return; + } + + if (memcmp(header.magic, RPCHandshakeHeader::REQUEST_MAGIC, sizeof(header.magic)) || + header.version != 1) + { + out << "In RPC server: invalid handshake header." << endl; + delete me; + return; + } + + memcpy(header.magic, RPCHandshakeHeader::RESPONSE_MAGIC, sizeof(header.magic)); + header.version = 1; + + if (me->socket->Send((uint8*)&header, sizeof(header)) != sizeof(header)) + { + out << "In RPC server: could not send handshake response." << endl; + delete me; + return; + } + } + + /* Processing */ + + std::cerr << "Client connection established." << endl; + + while (!me->in_error) { + RPCMessageHeader header; + + if (!readFullBuffer(*me->socket, &header, sizeof(header))) + { + out.printerr("In RPC server: I/O error in receive header.\n"); + break; + } + + if (header.id == RPC_REQUEST_QUIT) + break; + + if (header.size < 0 || header.size > 2*1048576) + { + out.printerr("In RPC server: invalid received size %d.\n", header.size); + break; + } + + std::auto_ptr buf(new uint8_t[header.size]); + + if (!readFullBuffer(*me->socket, buf.get(), header.size)) + { + out.printerr("In RPC server: I/O error in receive %d bytes of data.\n", header.size); + break; + } + + //out.print("Handling %d:%d\n", header.id, header.size); + + ServerFunctionBase *fn = vector_get(me->functions, header.id); + MessageLite *reply = NULL; + command_result res = CR_FAILURE; + + if (!fn) + { + me->stream.printerr("RPC call of invalid id %d\n", header.id); + } + else + { + if (!fn->in()->ParseFromArray(buf.get(), header.size)) + { + me->stream.printerr("In call to %s: could not decode input args.\n", fn->name); + } + else + { + reply = fn->out(); + res = fn->execute(me->stream); + } + } + + if (me->in_error) + break; + + me->stream.flush(); + + //out.print("Answer %d:%d\n", res, reply); + + if (res == CR_OK && reply) + { + if (!sendRemoteMessage(*me->socket, RPC_REPLY_RESULT, reply)) + { + out.printerr("In RPC server: I/O error in send result.\n"); + break; + } + } + else + { + header.id = RPC_REPLY_FAIL; + header.size = res; + + if (me->socket->Send((uint8_t*)&header, sizeof(header)) != sizeof(header)) + { + out.printerr("In RPC server: I/O error in send failure code.\n"); + break; + } + } + } + + std::cerr << "Shutting down client connection." << endl; + + delete me; +} + +ServerMain::ServerMain() +{ + thread = NULL; +} + +ServerMain::~ServerMain() +{ + socket.Close(); +} + +bool ServerMain::listen(int port) +{ + if (thread) + return true; + + socket.Initialize(); + + if (!socket.Listen((const uint8 *)"127.0.0.1", port)) + return false; + + thread = new tthread::thread(threadFn, this); + return true; +} + +void ServerMain::threadFn(void *arg) +{ + ServerMain *me = (ServerMain*)arg; + CActiveSocket *client; + + while ((client = me->socket.Accept()) != NULL) + { + new ServerConnection(client); + } +} diff --git a/library/dfhack-run.cpp b/library/dfhack-run.cpp new file mode 100644 index 000000000..941ce8d0e --- /dev/null +++ b/library/dfhack-run.cpp @@ -0,0 +1,90 @@ +/* +https://github.com/peterix/dfhack +Copyright (c) 2011 Petr Mrázek + +A thread-safe logging console with a line editor for windows. + +Based on linenoise win32 port, +copyright 2010, Jon Griffiths . +All rights reserved. +Based on linenoise, copyright 2010, Salvatore Sanfilippo . +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 +#include +#include +#include +#include +#include +#include + +#include "RemoteClient.h" + +#include +#include +#include + +#include + +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 [args...]\n"); + return 2; + } + + // Connect to DFHack + RemoteClient client; + if (!client.connect()) + return 2; + + // Bind to RunCommand + RemoteFunction command; + + if (!command.bind(out, &client, "RunCommand")) + return 2; + + // Execute it + command.in()->set_command(argv[1]); + for (int i = 2; i < argc; i++) + command.in()->add_arguments(argv[i]); + + if (command.execute(out) != CR_OK) + return 1; + + out.flush(); + return 0; +} diff --git a/library/include/ColorText.h b/library/include/ColorText.h index cdeaa3948..651f5831e 100644 --- a/library/include/ColorText.h +++ b/library/include/ColorText.h @@ -34,6 +34,11 @@ distribution. #include #include +namespace dfproto +{ + class CoreTextNotification; +} + namespace DFHack { class DFHACK_EXPORT color_ostream : public std::ostream @@ -112,6 +117,25 @@ namespace DFHack void reset_color(void); virtual bool is_console() { return false; } + virtual color_ostream *proxy_target() { return NULL; } + }; + + inline color_ostream &operator << (color_ostream &out, color_ostream::color_value clr) + { + out.color(clr); + return out; + } + + class DFHACK_EXPORT color_ostream_wrapper : public color_ostream + { + std::ostream &out; + + protected: + virtual void add_text(color_value color, const std::string &text); + virtual void flush_proxy(); + + public: + color_ostream_wrapper(std::ostream &os) : out(os) {} }; class DFHACK_EXPORT buffered_color_ostream : public color_ostream @@ -139,7 +163,11 @@ namespace DFHack virtual void flush_proxy(); public: - color_ostream_proxy(color_ostream &target); + color_ostream_proxy(color_ostream &target) : target(&target) {} ~color_ostream_proxy(); + + virtual color_ostream *proxy_target() { return target; } + + void decode(dfproto::CoreTextNotification *data); }; } diff --git a/library/include/Core.h b/library/include/Core.h index 321939c5d..1705bcc16 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -60,6 +60,7 @@ namespace DFHack class VersionInfoFactory; class PluginManager; class Core; + class ServerMain; namespace Windows { class df_window; @@ -195,6 +196,9 @@ namespace DFHack tthread::mutex * misc_data_mutex; std::map misc_data_map; + + friend class CoreService; + ServerMain *server; }; class CoreSuspender { diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index 8e8f7760d..e617814cf 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -30,6 +30,9 @@ distribution. #include #include #include + +#include "RemoteClient.h" + struct DFLibrary; namespace tthread { @@ -46,14 +49,6 @@ namespace DFHack class PluginManager; class virtual_identity; - enum command_result - { - CR_WOULD_BREAK = -2, - CR_NOT_IMPLEMENTED = -1, - CR_FAILURE = 0, - CR_OK = 1, - CR_WRONG_USAGE = 2 - }; enum state_change_event { SC_GAME_LOADED, diff --git a/library/include/RemoteClient.h b/library/include/RemoteClient.h new file mode 100644 index 000000000..453f24794 --- /dev/null +++ b/library/include/RemoteClient.h @@ -0,0 +1,169 @@ +/* +https://github.com/peterix/dfhack +Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#pragma once +#include "Pragma.h" +#include "Export.h" +#include "ColorText.h" + +#include "ActiveSocket.h" + +#include "CoreProtocol.pb.h" + +namespace DFHack +{ + using dfproto::CoreVoidReply; + + enum command_result + { + CR_WOULD_BREAK = -2, + CR_NOT_IMPLEMENTED = -1, + CR_OK = 0, + CR_FAILURE = 1, + CR_WRONG_USAGE = 2 + }; + + enum DFHackReplyCode : int16_t { + RPC_REPLY_RESULT = -1, + RPC_REPLY_FAIL = -2, + RPC_REPLY_TEXT = -3, + RPC_REQUEST_QUIT = -4 + }; + + struct RPCHandshakeHeader { + char magic[8]; + int version; + + static const char REQUEST_MAGIC[9]; + static const char RESPONSE_MAGIC[9]; + }; + + struct RPCMessageHeader { + int16_t id; + int32_t size; + }; + + class DFHACK_EXPORT RemoteClient; + + class DFHACK_EXPORT RPCFunctionBase { + public: + typedef ::google::protobuf::MessageLite message_type; + + const message_type *const p_in_template; + const message_type *const p_out_template; + + message_type *make_in() const { + return p_in_template->New(); + } + + message_type *in() { + if (!p_in) p_in = make_in(); + return p_in; + } + + message_type *make_out() const { + return p_out_template->New(); + } + + message_type *out() { + if (!p_out) p_out = make_out(); + return p_out; + } + + void reset(bool free = false); + + protected: + RPCFunctionBase(const message_type *in, const message_type *out) + : p_in_template(in), p_out_template(out), p_in(NULL), p_out(NULL) + {} + ~RPCFunctionBase() { delete p_in; delete p_out; } + + message_type *p_in, *p_out; + }; + + class DFHACK_EXPORT RemoteFunctionBase : public RPCFunctionBase { + public: + bool bind(color_ostream &out, + RemoteClient *client, const std::string &name, + const std::string &proto = std::string()); + bool isValid() { return (p_client != NULL); } + + protected: + friend class RemoteClient; + + RemoteFunctionBase(const message_type *in, const message_type *out) + : RPCFunctionBase(in, out), p_client(NULL), id(-1) + {} + + command_result execute(color_ostream &out, const message_type *input, message_type *output); + + std::string name, proto; + RemoteClient *p_client; + int16_t id; + }; + + template + class RemoteFunction : public RemoteFunctionBase { + public: + In *make_in() const { return static_cast(RemoteFunctionBase::make_in()); } + In *in() { return static_cast(RemoteFunctionBase::in()); } + Out *make_out() const { return static_cast(RemoteFunctionBase::make_out()); } + Out *out() { return static_cast(RemoteFunctionBase::out()); } + + RemoteFunction() : RemoteFunctionBase(&In::default_instance(), &Out::default_instance()) {} + + command_result execute(color_ostream &out) { + return RemoteFunctionBase::execute(out, this->in(), this->out()); + } + command_result operator() (color_ostream &out, const In *input, Out *output) { + return RemoteFunctionBase::execute(out, input, output); + } + }; + + bool readFullBuffer(CSimpleSocket &socket, void *buf, int size); + bool sendRemoteMessage(CSimpleSocket &socket, int16_t id, const ::google::protobuf::MessageLite *msg); + + class DFHACK_EXPORT RemoteClient + { + friend class RemoteFunctionBase; + + bool bind(color_ostream &out, RemoteFunctionBase *function, + const std::string &name, const std::string &proto); + + public: + RemoteClient(); + ~RemoteClient(); + + static int GetDefaultPort(); + + bool connect(int port = -1); + void disconnect(); + + private: + bool active; + CActiveSocket socket; + + RemoteFunction bind_call; + }; +} diff --git a/library/include/RemoteServer.h b/library/include/RemoteServer.h new file mode 100644 index 000000000..a3cc6c044 --- /dev/null +++ b/library/include/RemoteServer.h @@ -0,0 +1,189 @@ +/* +https://github.com/peterix/dfhack +Copyright (c) 2009-2011 Petr Mrázek (peterix@gmail.com) + +This software is provided 'as-is', without any express or implied +warranty. In no event will the authors be held liable for any +damages arising from the use of this software. + +Permission is granted to anyone to use this software for any +purpose, including commercial applications, and to alter it and +redistribute it freely, subject to the following restrictions: + +1. The origin of this software must not be misrepresented; you must +not claim that you wrote the original software. If you use this +software in a product, an acknowledgment in the product documentation +would be appreciated but is not required. + +2. Altered source versions must be plainly marked as such, and +must not be misrepresented as being the original software. + +3. This notice may not be removed or altered from any source +distribution. +*/ + +#pragma once +#include "Pragma.h" +#include "Export.h" +#include "RemoteClient.h" +#include "Core.h" + +#include "PassiveSocket.h" + +namespace DFHack +{ + class DFHACK_EXPORT ServerConnection; + class DFHACK_EXPORT RPCService; + + class DFHACK_EXPORT ServerFunctionBase : public RPCFunctionBase { + public: + const char *const name; + + virtual command_result execute(color_ostream &stream) = 0; + + int16_t getId() { return id; } + + protected: + friend class RPCService; + + ServerFunctionBase(const message_type *in, const message_type *out, RPCService *owner, const char *name) + : RPCFunctionBase(in, out), name(name), owner(owner), id(-1) + {} + + RPCService *owner; + int16_t id; + }; + + template + class ServerFunction : public ServerFunctionBase { + public: + typedef command_result (*function_type)(color_ostream &out, const In *input, Out *output); + + In *in() { return static_cast(RPCFunctionBase::in()); } + Out *out() { return static_cast(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 + class ServerMethod : public ServerFunctionBase { + public: + typedef command_result (Svc::*function_type)(color_ostream &out, const In *input, Out *output); + + In *in() { return static_cast(RPCFunctionBase::in()); } + Out *out() { return static_cast(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(owner)->*fptr)(stream, in(), out()); + } + + private: + function_type fptr; + }; + + class Plugin; + + class DFHACK_EXPORT RPCService { + friend class ServerConnection; + + std::vector functions; + std::map lookup; + ServerConnection *owner; + + Plugin *holder; + + void finalize(ServerConnection *owner, std::vector *ftable); + + public: + RPCService(); + virtual ~RPCService(); + + ServerFunctionBase *getFunction(const std::string &name); + + template + void addFunction( + const char *name, + command_result (*fptr)(color_ostream &out, const In *input, Out *output) + ) { + assert(!owner); + functions.push_back(new ServerFunction(this, name, fptr)); + } + + protected: + ServerConnection *connection() { return owner; } + + template + void addMethod( + const char *name, + command_result (Svc::*fptr)(color_ostream &out, const In *input, Out *output) + ) { + assert(!owner); + functions.push_back(new ServerMethod(this, name, fptr)); + } + }; + + class CoreService : public RPCService { + public: + CoreService(); + + command_result BindMethod(color_ostream &stream, + const dfproto::CoreBindRequest *in, + dfproto::CoreBindReply *out); + command_result RunCommand(color_ostream &stream, + const dfproto::CoreRunCommandRequest *in, + CoreVoidReply*); + }; + + class DFHACK_EXPORT ServerConnection { + class connection_ostream : public buffered_color_ostream { + ServerConnection *owner; + + protected: + virtual void flush_proxy(); + + public: + connection_ostream(ServerConnection *owner) : owner(owner) {} + }; + + bool in_error; + CActiveSocket *socket; + connection_ostream stream; + + std::vector functions; + + CoreService *core_service; + std::map plugin_services; + + tthread::thread *thread; + static void threadFn(void *); + + public: + ServerConnection(CActiveSocket *socket); + ~ServerConnection(); + + ServerFunctionBase *findFunction(const std::string &plugin, const std::string &name); + }; + + class DFHACK_EXPORT ServerMain { + CPassiveSocket socket; + + tthread::thread *thread; + static void threadFn(void *); + public: + ServerMain(); + ~ServerMain(); + + bool listen(int port); + }; +} diff --git a/library/proto/CoreProtocol.proto b/library/proto/CoreProtocol.proto index 33ec0f4a0..eec5ef7f2 100644 --- a/library/proto/CoreProtocol.proto +++ b/library/proto/CoreProtocol.proto @@ -34,26 +34,28 @@ message CoreErrorNotification { enum ErrorCode { CR_WOULD_BREAK = -2; CR_NOT_IMPLEMENTED = -1; - CR_FAILURE = 0; - CR_OK = 1; + CR_OK = 0; + CR_FAILURE = 1; CR_WRONG_USAGE = 2; }; required ErrorCode code = 1; } +message CoreVoidReply {} + message CoreBindRequest { required string method = 1; - optional string plugin = 2; - optional int32 min_version = 3; + required string input_msg = 2; + required string output_msg = 3; + optional string plugin = 4; } message CoreBindReply { required int32 assigned_id = 1; - required int32 version = 2; } -message CoreRunStringRequest { +message CoreRunCommandRequest { required string command = 1; repeated string arguments = 2; } diff --git a/plugins/Plugins.cmake b/plugins/Plugins.cmake index 221e57ea9..8d68827a4 100644 --- a/plugins/Plugins.cmake +++ b/plugins/Plugins.cmake @@ -6,6 +6,7 @@ IF(UNIX) ENDIF() include_directories("${dfhack_SOURCE_DIR}/library/include") +include_directories("${dfhack_SOURCE_DIR}/library/proto") include_directories("${dfhack_SOURCE_DIR}/library/depends/xgetopt") MACRO(CAR var) From e7851f5abdd1a84d29020f2789efc0f932284bf0 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 15 Mar 2012 11:07:43 +0400 Subject: [PATCH 10/12] Improve support for void RPC functions, dfhack-run, etc. --- CMakeLists.txt | 2 +- library/CMakeLists.txt | 6 ++- library/ColorText.cpp | 5 ++- library/Core.cpp | 2 + library/RemoteClient.cpp | 27 +++++++++++- library/RemoteServer.cpp | 27 +++++++++--- library/dfhack-run.cpp | 18 ++++---- library/include/ColorText.h | 2 + library/include/RemoteClient.h | 72 ++++++++++++++++++++++++++++---- library/include/RemoteServer.h | 57 ++++++++++++++++++++++++- library/proto/CoreProtocol.proto | 2 +- package/linux/dfhack | 2 +- package/linux/dfhack-run | 8 ++++ package/linux/egghack | 2 +- plugins/server/CMakeLists.txt | 4 +- 15 files changed, 203 insertions(+), 33 deletions(-) create mode 100755 package/linux/dfhack-run diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d8977bfd..d39d940e2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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}") diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index cef56d4da..0bda587a7 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -159,8 +159,8 @@ 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_SOURCES ${PROJECT_PROTO_SRCS}) @@ -249,6 +249,8 @@ 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. diff --git a/library/ColorText.cpp b/library/ColorText.cpp index c399504be..5d6e1d82a 100644 --- a/library/ColorText.cpp +++ b/library/ColorText.cpp @@ -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); diff --git a/library/Core.cpp b/library/Core.cpp index 0765f4cd1..6f04f5edb 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -572,6 +572,8 @@ Core::Core() top_viewscreen = NULL; screen_window = NULL; server = NULL; + + color_ostream::log_errors_to_stderr = true; }; void Core::fatal (std::string output, bool deactivate) diff --git a/library/RemoteClient.cpp b/library/RemoteClient.cpp index a8c804664..6f284dd15 100644 --- a/library/RemoteClient.cpp +++ b/library/RemoteClient.cpp @@ -176,6 +176,10 @@ bool RemoteClient::connect(int port) bind_call.p_client = this; bind_call.id = 0; + runcmd_call.name = "RunCommand"; + runcmd_call.p_client = this; + runcmd_call.id = 1; + return true; } @@ -220,6 +224,24 @@ bool RemoteClient::bind(color_ostream &out, RemoteFunctionBase *function, return true; } +command_result RemoteClient::run_command(color_ostream &out, const std::string &cmd, + const std::vector &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.execute(out); +} + void RPCFunctionBase::reset(bool free) { if (free) @@ -256,11 +278,14 @@ 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) +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 data(new uint8_t[fullsz]); RPCMessageHeader *hdr = (RPCMessageHeader*)data.get(); diff --git a/library/RemoteServer.cpp b/library/RemoteServer.cpp index 5b0825700..302080780 100644 --- a/library/RemoteServer.cpp +++ b/library/RemoteServer.cpp @@ -64,10 +64,11 @@ using dfproto::CoreTextFragment; using google::protobuf::MessageLite; CoreService::CoreService() { - // This must be the first method, so that it gets id 0 + // 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, @@ -96,8 +97,7 @@ command_result CoreService::BindMethod(color_ostream &stream, } command_result CoreService::RunCommand(color_ostream &stream, - const dfproto::CoreRunCommandRequest *in, - CoreVoidReply*) + const dfproto::CoreRunCommandRequest *in) { std::string cmd = in->command(); std::vector args; @@ -242,6 +242,7 @@ void ServerConnection::threadFn(void *arg) std::cerr << "Client connection established." << endl; while (!me->in_error) { + // Read the message RPCMessageHeader header; if (!readFullBuffer(*me->socket, &header, sizeof(header))) @@ -269,6 +270,9 @@ void ServerConnection::threadFn(void *arg) //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; @@ -290,6 +294,7 @@ void ServerConnection::threadFn(void *arg) } } + // Flush all text output if (me->in_error) break; @@ -297,9 +302,12 @@ void ServerConnection::threadFn(void *arg) //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)) + if (!sendRemoteMessage(*me->socket, RPC_REPLY_RESULT, reply, &out_size)) { out.printerr("In RPC server: I/O error in send result.\n"); break; @@ -307,6 +315,9 @@ void ServerConnection::threadFn(void *arg) } else { + if (reply) + out_size = reply->ByteSize(); + header.id = RPC_REPLY_FAIL; header.size = res; @@ -316,6 +327,12 @@ void ServerConnection::threadFn(void *arg) break; } } + + // Cleanup + if (fn) + { + fn->reset(out_size > 32768 || in_size > 32768); + } } std::cerr << "Shutting down client connection." << endl; diff --git a/library/dfhack-run.cpp b/library/dfhack-run.cpp index 941ce8d0e..eab039013 100644 --- a/library/dfhack-run.cpp +++ b/library/dfhack-run.cpp @@ -71,19 +71,19 @@ int main (int argc, char *argv[]) if (!client.connect()) return 2; - // Bind to RunCommand - RemoteFunction command; + // Call the command + std::vector args; + for (int i = 2; i < argc; i++) + args.push_back(argv[i]); - if (!command.bind(out, &client, "RunCommand")) - return 2; + command_result rv = client.run_command(out, argv[1], args); - // Execute it - command.in()->set_command(argv[1]); - for (int i = 2; i < argc; i++) - command.in()->add_arguments(argv[i]); + if (rv != CR_OK) { + if (rv == CR_NOT_IMPLEMENTED) + out.printerr("%s is not a recognized command.\n", argv[1]); - if (command.execute(out) != CR_OK) return 1; + } out.flush(); return 0; diff --git a/library/include/ColorText.h b/library/include/ColorText.h index 651f5831e..105832efd 100644 --- a/library/include/ColorText.h +++ b/library/include/ColorText.h @@ -118,6 +118,8 @@ namespace DFHack 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) diff --git a/library/include/RemoteClient.h b/library/include/RemoteClient.h index 453f24794..6fd762080 100644 --- a/library/include/RemoteClient.h +++ b/library/include/RemoteClient.h @@ -33,7 +33,7 @@ distribution. namespace DFHack { - using dfproto::CoreVoidReply; + using dfproto::EmptyMessage; enum command_result { @@ -64,6 +64,43 @@ namespace DFHack 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 { @@ -123,7 +160,7 @@ namespace DFHack int16_t id; }; - template + template class RemoteFunction : public RemoteFunctionBase { public: In *make_in() const { return static_cast(RemoteFunctionBase::make_in()); } @@ -133,16 +170,33 @@ namespace DFHack RemoteFunction() : RemoteFunctionBase(&In::default_instance(), &Out::default_instance()) {} - command_result execute(color_ostream &out) { - return RemoteFunctionBase::execute(out, this->in(), this->out()); + command_result execute(color_ostream &stream) { + return RemoteFunctionBase::execute(stream, in(), out()); } - command_result operator() (color_ostream &out, const In *input, Out *output) { - return RemoteFunctionBase::execute(out, input, output); + command_result operator() (color_ostream &stream, const In *input, Out *output) { + return RemoteFunctionBase::execute(stream, input, output); + } + }; + + template + class RemoteFunction : public RemoteFunctionBase { + public: + In *make_in() const { return static_cast(RemoteFunctionBase::make_in()); } + In *in() { return static_cast(RemoteFunctionBase::in()); } + + RemoteFunction() : RemoteFunctionBase(&In::default_instance(), &EmptyMessage::default_instance()) {} + + command_result execute(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); + bool sendRemoteMessage(CSimpleSocket &socket, int16_t id, + const ::google::protobuf::MessageLite *msg, int *psz = NULL); class DFHACK_EXPORT RemoteClient { @@ -160,10 +214,14 @@ namespace DFHack bool connect(int port = -1); void disconnect(); + command_result run_command(color_ostream &out, const std::string &cmd, + const std::vector &args); + private: bool active; CActiveSocket socket; RemoteFunction bind_call; + RemoteFunction runcmd_call; }; } diff --git a/library/include/RemoteServer.h b/library/include/RemoteServer.h index a3cc6c044..76abbe726 100644 --- a/library/include/RemoteServer.h +++ b/library/include/RemoteServer.h @@ -72,6 +72,23 @@ namespace DFHack function_type fptr; }; + template + class VoidServerFunction : public ServerFunctionBase { + public: + typedef command_result (*function_type)(color_ostream &out, const In *input); + + In *in() { return static_cast(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 class ServerMethod : public ServerFunctionBase { public: @@ -92,6 +109,25 @@ namespace DFHack function_type fptr; }; + template + class VoidServerMethod : public ServerFunctionBase { + public: + typedef command_result (Svc::*function_type)(color_ostream &out, const In *input); + + In *in() { return static_cast(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(owner)->*fptr)(stream, in()); + } + + private: + function_type fptr; + }; + class Plugin; class DFHACK_EXPORT RPCService { @@ -120,6 +156,15 @@ namespace DFHack functions.push_back(new ServerFunction(this, name, fptr)); } + template + void addFunction( + const char *name, + command_result (*fptr)(color_ostream &out, const In *input) + ) { + assert(!owner); + functions.push_back(new VoidServerFunction(this, name, fptr)); + } + protected: ServerConnection *connection() { return owner; } @@ -131,6 +176,15 @@ namespace DFHack assert(!owner); functions.push_back(new ServerMethod(this, name, fptr)); } + + template + void addMethod( + const char *name, + command_result (Svc::*fptr)(color_ostream &out, const In *input) + ) { + assert(!owner); + functions.push_back(new VoidServerMethod(this, name, fptr)); + } }; class CoreService : public RPCService { @@ -141,8 +195,7 @@ namespace DFHack const dfproto::CoreBindRequest *in, dfproto::CoreBindReply *out); command_result RunCommand(color_ostream &stream, - const dfproto::CoreRunCommandRequest *in, - CoreVoidReply*); + const dfproto::CoreRunCommandRequest *in); }; class DFHACK_EXPORT ServerConnection { diff --git a/library/proto/CoreProtocol.proto b/library/proto/CoreProtocol.proto index eec5ef7f2..2e2fe2c24 100644 --- a/library/proto/CoreProtocol.proto +++ b/library/proto/CoreProtocol.proto @@ -42,7 +42,7 @@ message CoreErrorNotification { required ErrorCode code = 1; } -message CoreVoidReply {} +message EmptyMessage {} message CoreBindRequest { required string method = 1; diff --git a/package/linux/dfhack b/package/linux/dfhack index d539237ff..3e5a0d803 100755 --- a/package/linux/dfhack +++ b/package/linux/dfhack @@ -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) diff --git a/package/linux/dfhack-run b/package/linux/dfhack-run new file mode 100755 index 000000000..cc69db964 --- /dev/null +++ b/package/linux/dfhack-run @@ -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 "$@" diff --git a/package/linux/egghack b/package/linux/egghack index 1ce583ceb..2265ab29e 100755 --- a/package/linux/egghack +++ b/package/linux/egghack @@ -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! :) diff --git a/plugins/server/CMakeLists.txt b/plugins/server/CMakeLists.txt index 5af8e58fd..6fd7c44d0 100644 --- a/plugins/server/CMakeLists.txt +++ b/plugins/server/CMakeLists.txt @@ -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() From 87f925e72e7c6f923a595cc930728483a3f1422e Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 15 Mar 2012 13:01:23 +0400 Subject: [PATCH 11/12] Add support for exporting functions from plugins, with example in rename. TODO: test by actually calling them remotely. --- library/Core.cpp | 14 ++-- library/PluginManager.cpp | 70 +++++++++++++++----- library/RemoteServer.cpp | 38 +++++++++-- library/include/Core.h | 1 + library/include/PluginManager.h | 23 +++++-- library/include/RemoteClient.h | 11 ++-- library/include/RemoteServer.h | 3 +- library/proto/CoreProtocol.proto | 1 + plugins/CMakeLists.txt | 17 ++++- plugins/Plugins.cmake | 10 ++- plugins/proto/.gitignore | 3 + plugins/proto/rename.proto | 17 +++++ plugins/rename.cpp | 107 ++++++++++++++++++++++++------- 13 files changed, 248 insertions(+), 67 deletions(-) create mode 100644 plugins/proto/.gitignore create mode 100644 plugins/proto/rename.proto diff --git a/library/Core.cpp b/library/Core.cpp index 6f04f5edb..793a63680 100644 --- a/library/Core.cpp +++ b/library/Core.cpp @@ -165,7 +165,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) { @@ -272,7 +272,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 @@ -284,7 +284,7 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue } else { - plug->load(); + plug->load(con); } } } @@ -299,7 +299,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 @@ -311,7 +311,7 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue } else { - plug->reload(); + plug->reload(con); } } } @@ -326,7 +326,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 @@ -338,7 +338,7 @@ static void runInteractiveCommand(Core *core, PluginManager *plug_mgr, int &clue } else { - plug->unload(); + plug->unload(con); } } } diff --git a/library/PluginManager.cpp b/library/PluginManager.cpp index d36ba6659..f7af9c954 100644 --- a/library/PluginManager.cpp +++ b/library/PluginManager.cpp @@ -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 & parameters, bool interactive_) +command_result Plugin::invoke(color_ostream &out, const std::string & command, std::vector & 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 & parameters, bool interactive) +command_result PluginManager::InvokeCommand(color_ostream &out, const std::string & command, std::vector & 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; diff --git a/library/RemoteServer.cpp b/library/RemoteServer.cpp index 302080780..9d55da120 100644 --- a/library/RemoteServer.cpp +++ b/library/RemoteServer.cpp @@ -75,7 +75,7 @@ command_result CoreService::BindMethod(color_ostream &stream, const dfproto::CoreBindRequest *in, dfproto::CoreBindReply *out) { - ServerFunctionBase *fn = connection()->findFunction(in->plugin(), in->method()); + ServerFunctionBase *fn = connection()->findFunction(stream, in->plugin(), in->method()); if (!fn) { @@ -104,7 +104,7 @@ command_result CoreService::RunCommand(color_ostream &stream, for (int i = 0; i < in->arguments_size(); i++) args.push_back(in->arguments(i)); - return Core::getInstance().plug_mgr->InvokeCommand(stream, cmd, args, false); + return Core::getInstance().plug_mgr->InvokeCommand(stream, cmd, args); } RPCService::RPCService() @@ -115,6 +115,9 @@ RPCService::RPCService() RPCService::~RPCService() { + if (holder) + holder->detach_connection(this); + for (size_t i = 0; i < functions.size(); i++) delete functions[i]; } @@ -163,12 +166,37 @@ ServerConnection::~ServerConnection() delete core_service; } -ServerFunctionBase *ServerConnection::findFunction(const std::string &plugin, const std::string &name) +ServerFunctionBase *ServerConnection::findFunction(color_ostream &out, const std::string &plugin, const std::string &name) { + RPCService *svc; + if (plugin.empty()) - return core_service->getFunction(name); + svc = core_service; else - return NULL; // todo: add plugin api support + { + 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; + } + + plugin_services[plugin] = svc; + } + } + + return svc->getFunction(name); } void ServerConnection::connection_ostream::flush_proxy() diff --git a/library/include/Core.h b/library/include/Core.h index 1705bcc16..740c35558 100644 --- a/library/include/Core.h +++ b/library/include/Core.h @@ -198,6 +198,7 @@ namespace DFHack std::map misc_data_map; friend class CoreService; + friend class ServerConnection; ServerMain *server; }; diff --git a/library/include/PluginManager.h b/library/include/PluginManager.h index e617814cf..86f7a4b2f 100644 --- a/library/include/PluginManager.h +++ b/library/include/PluginManager.h @@ -48,6 +48,7 @@ namespace DFHack class Core; class PluginManager; class virtual_identity; + class RPCService; enum state_change_event { @@ -106,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 & 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 & 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]; @@ -132,6 +139,7 @@ namespace DFHack private: RefLock * access; std::vector commands; + std::vector services; std::string filename; std::string name; DFLibrary * plugin_lib; @@ -142,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 { @@ -158,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 & parameters, bool interactive = true ); - bool CanInvokeHotkey(std::string &command, df::viewscreen *top); + command_result InvokeCommand(color_ostream &out, const std::string & command, std::vector & parameters); + bool CanInvokeHotkey(const std::string &command, df::viewscreen *top); Plugin* operator[] (std::size_t index) { if(index >= all_plugins.size()) diff --git a/library/include/RemoteClient.h b/library/include/RemoteClient.h index 6fd762080..1b468694d 100644 --- a/library/include/RemoteClient.h +++ b/library/include/RemoteClient.h @@ -37,11 +37,12 @@ namespace DFHack enum command_result { - CR_WOULD_BREAK = -2, - CR_NOT_IMPLEMENTED = -1, - CR_OK = 0, - CR_FAILURE = 1, - CR_WRONG_USAGE = 2 + 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 { diff --git a/library/include/RemoteServer.h b/library/include/RemoteServer.h index 76abbe726..26363d582 100644 --- a/library/include/RemoteServer.h +++ b/library/include/RemoteServer.h @@ -132,6 +132,7 @@ namespace DFHack class DFHACK_EXPORT RPCService { friend class ServerConnection; + friend class Plugin; std::vector functions; std::map lookup; @@ -225,7 +226,7 @@ namespace DFHack ServerConnection(CActiveSocket *socket); ~ServerConnection(); - ServerFunctionBase *findFunction(const std::string &plugin, const std::string &name); + ServerFunctionBase *findFunction(color_ostream &out, const std::string &plugin, const std::string &name); }; class DFHACK_EXPORT ServerMain { diff --git a/library/proto/CoreProtocol.proto b/library/proto/CoreProtocol.proto index 2e2fe2c24..3ebe17a28 100644 --- a/library/proto/CoreProtocol.proto +++ b/library/proto/CoreProtocol.proto @@ -37,6 +37,7 @@ message CoreErrorNotification { CR_OK = 0; CR_FAILURE = 1; CR_WRONG_USAGE = 2; + CR_NOT_FOUND = 3; }; required ErrorCode code = 1; diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index aba5feaa6..fe00fbafb 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -45,6 +45,21 @@ 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}/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 +85,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) diff --git a/plugins/Plugins.cmake b/plugins/Plugins.cmake index 8d68827a4..17c7320aa 100644 --- a/plugins/Plugins.cmake +++ b/plugins/Plugins.cmake @@ -8,6 +8,7 @@ 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}) @@ -58,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}) diff --git a/plugins/proto/.gitignore b/plugins/proto/.gitignore new file mode 100644 index 000000000..befabf79d --- /dev/null +++ b/plugins/proto/.gitignore @@ -0,0 +1,3 @@ +*.pb.cc +*.pb.cc.rule +*.pb.h diff --git a/plugins/proto/rename.proto b/plugins/proto/rename.proto new file mode 100644 index 000000000..a139ceaa0 --- /dev/null +++ b/plugins/proto/rename.proto @@ -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; +} diff --git a/plugins/rename.cpp b/plugins/rename.cpp index 1a1a8cc30..ae6edc652 100644 --- a/plugins/rename.cpp +++ b/plugins/rename.cpp @@ -17,6 +17,9 @@ #include "df/assumed_identity.h" #include "df/language_name.h" +#include "RemoteServer.h" +#include "rename.pb.h" + #include 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 ¶meters) { CoreSuspender suspend; @@ -124,29 +207,7 @@ static command_result rename(color_ostream &out, vector ¶meters) 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") { From 605ee9669a5cd4d2f69ca8adcbe96c9e62671b87 Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Thu, 15 Mar 2012 14:06:50 +0400 Subject: [PATCH 12/12] Plugin-exported functions now actually work. --- library/CMakeLists.txt | 18 ++++++++++-------- library/RemoteClient.cpp | 4 ++-- library/RemoteServer.cpp | 1 + library/include/RemoteClient.h | 4 ++-- plugins/CMakeLists.txt | 3 ++- 5 files changed, 17 insertions(+), 13 deletions(-) diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 0bda587a7..f062be875 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -216,10 +216,11 @@ ENDIF() ADD_LIBRARY(dfhack SHARED ${PROJECT_SOURCES}) ADD_DEPENDENCIES(dfhack generate_headers) -ADD_EXECUTABLE(dfhack-run dfhack-run.cpp - RemoteClient.cpp ColorText.cpp MiscUtils.cpp - ${PROJECT_PROTO_SRCS}) -ADD_DEPENDENCIES(dfhack-run dfhack) +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" ) @@ -231,10 +232,10 @@ endif() IF(WIN32) SET_TARGET_PROPERTIES(dfhack PROPERTIES COMPILE_FLAGS "/FI\"Export.h\"" ) - SET_TARGET_PROPERTIES(dfhack-run PROPERTIES COMPILE_FLAGS "/FI\"Export.h\"" ) + 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-run PROPERTIES COMPILE_FLAGS "-include Export.h" ) + SET_TARGET_PROPERTIES(dfhack-client PROPERTIES COMPILE_FLAGS "-include Export.h" ) ENDIF() #effectively disables debug builds... @@ -243,7 +244,8 @@ SET_TARGET_PROPERTIES(dfhack PROPERTIES DEBUG_POSTFIX "-debug" ) TARGET_LINK_LIBRARIES(dfhack protobuf-lite clsocket ${PROJECT_LIBS}) SET_TARGET_PROPERTIES(dfhack PROPERTIES LINK_INTERFACE_LIBRARIES "") -TARGET_LINK_LIBRARIES(dfhack-run protobuf-lite clsocket) +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 @@ -277,7 +279,7 @@ install(FILES xml/symbols.xml install(FILES ../dfhack.init-example DESTINATION ${DFHACK_BINARY_DESTINATION}) -install(TARGETS dfhack-run +install(TARGETS dfhack-run dfhack-client LIBRARY DESTINATION ${DFHACK_LIBRARY_DESTINATION} RUNTIME DESTINATION ${DFHACK_LIBRARY_DESTINATION}) diff --git a/library/RemoteClient.cpp b/library/RemoteClient.cpp index 6f284dd15..f8d3e3e7e 100644 --- a/library/RemoteClient.cpp +++ b/library/RemoteClient.cpp @@ -215,7 +215,7 @@ bool RemoteClient::bind(color_ostream &out, RemoteFunctionBase *function, in->set_output_msg(function->p_out_template->GetTypeName()); } - if (bind_call.execute(out) != CR_OK) + if (bind_call(out) != CR_OK) return false; function->p_client = this; @@ -239,7 +239,7 @@ command_result RemoteClient::run_command(color_ostream &out, const std::string & for (size_t i = 0; i < args.size(); i++) runcmd_call.in()->add_arguments(args[i]); - return runcmd_call.execute(out); + return runcmd_call(out); } void RPCFunctionBase::reset(bool free) diff --git a/library/RemoteServer.cpp b/library/RemoteServer.cpp index 9d55da120..14b64f19e 100644 --- a/library/RemoteServer.cpp +++ b/library/RemoteServer.cpp @@ -192,6 +192,7 @@ ServerFunctionBase *ServerConnection::findFunction(color_ostream &out, const std return NULL; } + svc->finalize(this, &functions); plugin_services[plugin] = svc; } } diff --git a/library/include/RemoteClient.h b/library/include/RemoteClient.h index 1b468694d..79efff977 100644 --- a/library/include/RemoteClient.h +++ b/library/include/RemoteClient.h @@ -171,7 +171,7 @@ namespace DFHack RemoteFunction() : RemoteFunctionBase(&In::default_instance(), &Out::default_instance()) {} - command_result execute(color_ostream &stream) { + command_result operator() (color_ostream &stream) { return RemoteFunctionBase::execute(stream, in(), out()); } command_result operator() (color_ostream &stream, const In *input, Out *output) { @@ -187,7 +187,7 @@ namespace DFHack RemoteFunction() : RemoteFunctionBase(&In::default_instance(), &EmptyMessage::default_instance()) {} - command_result execute(color_ostream &stream) { + command_result operator() (color_ostream &stream) { return RemoteFunctionBase::execute(stream, in(), out()); } command_result operator() (color_ostream &stream, const In *input) { diff --git a/plugins/CMakeLists.txt b/plugins/CMakeLists.txt index fe00fbafb..1574b5a28 100644 --- a/plugins/CMakeLists.txt +++ b/plugins/CMakeLists.txt @@ -53,7 +53,8 @@ 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}/proto/ -I=${CMAKE_CURRENT_SOURCE_DIR}/proto/ + 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}