From 56969fb977b3c8098aac183895dfe571aebaba5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Tue, 16 Feb 2010 00:04:15 +0100 Subject: [PATCH 1/3] Added std::string read/write by belal. Extremely volatile. --- library/CMakeLists.txt | 3 +- library/DFCommonInternal.h | 2 +- library/DFDataModel.cpp | 164 ------- library/DFDataModel.h | 60 --- library/DFHackAPI.cpp | 127 +++--- library/DFProcess-linux-SHM.cpp | 60 ++- library/DFProcess-linux-wine.cpp | 584 ++++++++++++++++++++++++ library/DFProcess-linux.cpp | 93 ++-- library/DFProcess-windows-SHM.cpp | 268 ++++++----- library/DFProcess-windows.cpp | 98 +++- library/DFProcess.h | 77 +++- library/DFProcessEnumerator-linux.cpp | 11 + library/DFProcessEnumerator-windows.cpp | 1 + library/DFProcessEnumerator.h | 1 - shmserver/shms-proto.cpp | 26 +- shmserver/shms.h | 9 +- tools/customCreatureNameProf.cpp | 243 ++++++---- tools/dfbauxite.cpp | 2 +- 18 files changed, 1240 insertions(+), 589 deletions(-) delete mode 100644 library/DFDataModel.cpp delete mode 100644 library/DFDataModel.h create mode 100644 library/DFProcess-linux-wine.cpp diff --git a/library/CMakeLists.txt b/library/CMakeLists.txt index 5f2dd3230..12de11fbd 100644 --- a/library/CMakeLists.txt +++ b/library/CMakeLists.txt @@ -2,7 +2,6 @@ SET(PROJECT_HDRS DFCommonInternal.h -DFDataModel.h DFHackAPI.h DFMemInfo.h DFMemInfoManager.h @@ -24,7 +23,6 @@ tinyxml/tinyxml.h ) SET(PROJECT_SRCS -DFDataModel.cpp DFMemInfo.cpp DFMemInfoManager.cpp DFHackAPI.cpp @@ -47,6 +45,7 @@ stdint_win.h SET(PROJECT_SRCS_LINUX DFProcess-linux.cpp DFProcess-linux-SHM.cpp +DFProcess-linux-wine.cpp DFWindow-linux.cpp DFProcessEnumerator-linux.cpp ) diff --git a/library/DFCommonInternal.h b/library/DFCommonInternal.h index 9fd0dd0c0..1a98f8274 100644 --- a/library/DFCommonInternal.h +++ b/library/DFCommonInternal.h @@ -92,7 +92,7 @@ namespace DFHack #endif #include "DFTypes.h" -#include "DFDataModel.h" +//#include "DFDataModel.h" #include "DFProcess.h" #include "DFWindow.h" #include "DFProcessEnumerator.h" diff --git a/library/DFDataModel.cpp b/library/DFDataModel.cpp deleted file mode 100644 index 1e48a0bf5..000000000 --- a/library/DFDataModel.cpp +++ /dev/null @@ -1,164 +0,0 @@ -/* -www.sourceforge.net/projects/dfhack -Copyright (c) 2009 Petr Mrázek (peterix), Kenneth Ferland (Impaler[WrG]), dorf - -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. -*/ - -#include "DFCommonInternal.h" -using namespace DFHack; - -DfVector DMWindows40d::readVector (uint32_t offset, uint32_t item_size) -{ - /* - MSVC++ vector is four pointers long - ptr allocator - ptr start - ptr end - ptr alloc_end - - we don't care about alloc_end because we don't try to add stuff - we also don't care about the allocator thing in front - */ - uint32_t start = g_pProcess->readDWord(offset+4); - uint32_t end = g_pProcess->readDWord(offset+8); - uint32_t size = (end - start) /4; - return DfVector(start,size,item_size); -} - - -size_t DMWindows40d::readSTLString (uint32_t offset, char * buffer, size_t bufcapacity) -{ - /* - MSVC++ string - ptr allocator - union - { - char[16] start; - char * start_ptr -} -Uint32 length -Uint32 capacity -*/ - uint32_t start_offset = offset + 4; - size_t length = g_pProcess->readDWord(offset + 20); - - size_t capacity = g_pProcess->readDWord(offset + 24); - size_t read_real = min(length, bufcapacity-1);// keep space for null termination - - // read data from inside the string structure - if(capacity < 16) - { - g_pProcess->read(start_offset, read_real , (uint8_t *)buffer); - } - else // read data from what the offset + 4 dword points to - { - start_offset = g_pProcess->readDWord(start_offset);// dereference the start offset - g_pProcess->read(start_offset, read_real, (uint8_t *)buffer); - } - - buffer[read_real] = 0; - return read_real; -} - -const string DMWindows40d::readSTLString (uint32_t offset) -{ - /* - MSVC++ string - ptr allocator - union - { - char[16] start; - char * start_ptr - } - Uint32 length - Uint32 capacity - */ - uint32_t start_offset = offset + 4; - uint32_t length = g_pProcess->readDWord(offset + 20); - uint32_t capacity = g_pProcess->readDWord(offset + 24); - char * temp = new char[capacity+1]; - - // read data from inside the string structure - if(capacity < 16) - { - g_pProcess->read(start_offset, capacity, (uint8_t *)temp); - } - else // read data from what the offset + 4 dword points to - { - start_offset = g_pProcess->readDWord(start_offset);// dereference the start offset - g_pProcess->read(start_offset, capacity, (uint8_t *)temp); - } - - temp[length] = 0; - string ret = temp; - delete temp; - return ret; -} - - -DfVector DMLinux40d::readVector (uint32_t offset, uint32_t item_size) -{ - /* - GNU libstdc++ vector is three pointers long - ptr start - ptr end - ptr alloc_end - - we don't care about alloc_end because we don't try to add stuff - */ - uint32_t start = g_pProcess->readDWord(offset); - uint32_t end = g_pProcess->readDWord(offset+4); - uint32_t size = (end - start) /4; - return DfVector(start,size,item_size); -} - -struct _Rep_base -{ - uint32_t _M_length; - uint32_t _M_capacity; - uint32_t _M_refcount; -}; - -size_t DMLinux40d::readSTLString (uint32_t offset, char * buffer, size_t bufcapacity) -{ - _Rep_base header; - offset = g_pProcess->readDWord(offset); - g_pProcess->read(offset - sizeof(_Rep_base),sizeof(_Rep_base),(uint8_t *)&header); - size_t read_real = min((size_t)header._M_length, bufcapacity-1);// keep space for null termination - g_pProcess->read(offset,read_real,(uint8_t * )buffer); - buffer[read_real] = 0; - return read_real; -} - -const string DMLinux40d::readSTLString (uint32_t offset) -{ - _Rep_base header; - - offset = g_pProcess->readDWord(offset); - g_pProcess->read(offset - sizeof(_Rep_base),sizeof(_Rep_base),(uint8_t *)&header); - - // FIXME: use char* everywhere, avoid string - char * temp = new char[header._M_length+1]; - g_pProcess->read(offset,header._M_length+1,(uint8_t * )temp); - string ret(temp); - delete temp; - return ret; -} diff --git a/library/DFDataModel.h b/library/DFDataModel.h deleted file mode 100644 index 479e8bb5e..000000000 --- a/library/DFDataModel.h +++ /dev/null @@ -1,60 +0,0 @@ -/* -www.sourceforge.net/projects/dfhack -Copyright (c) 2009 Petr Mrázek (peterix), Kenneth Ferland (Impaler[WrG]), dorf - -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. -*/ - -#ifndef DATAMODEL_H_INCLUDED -#define DATAMODEL_H_INCLUDED - -namespace DFHack -{ - class DfVector; - - // let's go pure virtual. - class DataModel - { - public: - // read a string - virtual const string readSTLString (uint32_t offset) = 0; - virtual size_t readSTLString (uint32_t offset, char * buffer, size_t bufcapacity) = 0; - // read a vector from memory - //template - virtual DfVector readVector (uint32_t offset, uint32_t item_size) = 0; - }; - - class DMWindows40d : public DataModel - { - virtual const string readSTLString (uint32_t offset); - virtual size_t readSTLString (uint32_t offset, char * buffer, size_t bufcapacity); - // read a vector from memory - virtual DfVector readVector (uint32_t offset, uint32_t item_size); - }; - - class DMLinux40d : public DataModel - { - virtual const string readSTLString (uint32_t offset); - virtual size_t readSTLString (uint32_t offset, char * buffer, size_t bufcapacity); - // read a vector from memory - virtual DfVector readVector (uint32_t offset, uint32_t item_size); - }; -} -#endif // DATAMODEL_H_INCLUDED diff --git a/library/DFHackAPI.cpp b/library/DFHackAPI.cpp index 9a4d70219..bb53bb904 100644 --- a/library/DFHackAPI.cpp +++ b/library/DFHackAPI.cpp @@ -30,7 +30,7 @@ class API::Private public: Private() : block (NULL) - , pm (NULL), p (NULL), dm (NULL), offset_descriptor (NULL) + , pm (NULL), p (NULL), offset_descriptor (NULL) , p_cons (NULL), p_bld (NULL), p_veg (NULL) {} uint32_t * block; @@ -87,7 +87,6 @@ public: ProcessEnumerator* pm; Process* p; - DataModel* dm; memory_info* offset_descriptor; vector v_geology[eBiomeCount]; string xml; @@ -265,7 +264,7 @@ bool API::WriteTileTypes (uint32_t x, uint32_t y, uint32_t z, uint16_t *buffer) bool API::getCurrentCursorCreatures (vector &addresses) { assert (d->cursorWindowInited); - DfVector creUnderCursor = d->dm->readVector (d->current_cursor_creature_offset, 4); + DfVector creUnderCursor = d->p->readVector (d->current_cursor_creature_offset, 4); if (creUnderCursor.getSize() == 0) { return false; @@ -328,7 +327,7 @@ bool API::ReadVeins (uint32_t x, uint32_t y, uint32_t z, vector & veins { // veins are stored as a vector of pointers to veins /*pointer is 4 bytes! we work with a 32bit program here, no matter what architecture we compile khazad for*/ - DfVector p_veins = d->dm->readVector (addr + d->veinvector, 4); + DfVector p_veins = d->p->readVector (addr + d->veinvector, 4); uint32_t size = p_veins.getSize(); veins.reserve (size); @@ -364,7 +363,7 @@ bool API::ReadWoodMatgloss (vector & woods) int matgloss_address = d->offset_descriptor->getAddress ("matgloss"); int matgloss_wood_name_offset = d->offset_descriptor->getOffset("matgloss_wood_name"); // TODO: find flag for autumnal coloring? - DfVector p_matgloss = d->dm->readVector (matgloss_address, 4); + DfVector p_matgloss = d->p->readVector (matgloss_address, 4); woods.clear(); @@ -380,10 +379,10 @@ bool API::ReadWoodMatgloss (vector & woods) uint32_t temp = * (uint32_t *) p_matgloss[i]; // read the string pointed at by /* - fill_char_buf(mat.id, d->dm->readSTLString(temp)); // reads a C string given an address + fill_char_buf(mat.id, d->p->readSTLString(temp)); // reads a C string given an address */ - d->dm->readSTLString (temp, mat.id, 128); - d->dm->readSTLString (temp+matgloss_wood_name_offset, mat.name, 128); + d->p->readSTLString (temp, mat.id, 128); + d->p->readSTLString (temp+matgloss_wood_name_offset, mat.name, 128); woods.push_back (mat); } return true; @@ -397,7 +396,7 @@ bool API::ReadStoneMatgloss (vector & stones) int matgloss_colors = minfo->getOffset ("matgloss_stone_color"); int matgloss_stone_name_offset = minfo->getOffset("matgloss_stone_name"); - DfVector p_matgloss = d->dm->readVector (matgloss_address + matgloss_offset, 4); + DfVector p_matgloss = d->p->readVector (matgloss_address + matgloss_offset, 4); uint32_t size = p_matgloss.getSize(); stones.resize (0); @@ -408,9 +407,9 @@ bool API::ReadStoneMatgloss (vector & stones) uint32_t temp = * (uint32_t *) p_matgloss[i]; // read the string pointed at by t_matgloss mat; - //fill_char_buf(mat.id, d->dm->readSTLString(temp)); // reads a C string given an address - d->dm->readSTLString (temp, mat.id, 128); - d->dm->readSTLString (temp+matgloss_stone_name_offset, mat.name, 128); + //fill_char_buf(mat.id, d->p->readSTLString(temp)); // reads a C string given an address + d->p->readSTLString (temp, mat.id, 128); + d->p->readSTLString (temp+matgloss_stone_name_offset, mat.name, 128); mat.fore = (uint8_t) g_pProcess->readWord (temp + matgloss_colors); mat.back = (uint8_t) g_pProcess->readWord (temp + matgloss_colors + 2); mat.bright = (uint8_t) g_pProcess->readWord (temp + matgloss_colors + 4); @@ -427,7 +426,7 @@ bool API::ReadMetalMatgloss (vector & metals) int matgloss_offset = minfo->getHexValue ("matgloss_skip"); int matgloss_colors = minfo->getOffset ("matgloss_metal_color"); int matgloss_metal_name_offset = minfo->getOffset("matgloss_metal_name"); - DfVector p_matgloss = d->dm->readVector (matgloss_address + matgloss_offset * 3, 4); + DfVector p_matgloss = d->p->readVector (matgloss_address + matgloss_offset * 3, 4); metals.clear(); @@ -437,9 +436,9 @@ bool API::ReadMetalMatgloss (vector & metals) uint32_t temp = * (uint32_t *) p_matgloss[i]; // read the string pointed at by t_matgloss mat; - //fill_char_buf(mat.id, d->dm->readSTLString(temp)); // reads a C string given an address - d->dm->readSTLString (temp, mat.id, 128); - d->dm->readSTLString (temp+matgloss_metal_name_offset, mat.name, 128); + //fill_char_buf(mat.id, d->p->readSTLString(temp)); // reads a C string given an address + d->p->readSTLString (temp, mat.id, 128); + d->p->readSTLString (temp+matgloss_metal_name_offset, mat.name, 128); mat.fore = (uint8_t) g_pProcess->readWord (temp + matgloss_colors); mat.back = (uint8_t) g_pProcess->readWord (temp + matgloss_colors + 2); mat.bright = (uint8_t) g_pProcess->readWord (temp + matgloss_colors + 4); @@ -454,7 +453,7 @@ bool API::ReadPlantMatgloss (vector & plants) int matgloss_address = minfo->getAddress ("matgloss"); int matgloss_offset = minfo->getHexValue ("matgloss_skip"); int matgloss_plant_name_offset = minfo->getOffset("matgloss_plant_name"); - DfVector p_matgloss = d->dm->readVector (matgloss_address + matgloss_offset * 2, 4); + DfVector p_matgloss = d->p->readVector (matgloss_address + matgloss_offset * 2, 4); plants.clear(); @@ -468,9 +467,9 @@ bool API::ReadPlantMatgloss (vector & plants) // read the matgloss pointer from the vector into temp uint32_t temp = * (uint32_t *) p_matgloss[i]; // read the string pointed at by - //fill_char_buf(mat.id, d->dm->readSTLString(temp)); // reads a C string given an address - d->dm->readSTLString (temp, mat.id, 128); - d->dm->readSTLString (temp+matgloss_plant_name_offset, mat.name, 128); + //fill_char_buf(mat.id, d->p->readSTLString(temp)); // reads a C string given an address + d->p->readSTLString (temp, mat.id, 128); + d->p->readSTLString (temp+matgloss_plant_name_offset, mat.name, 128); plants.push_back (mat); } return true; @@ -485,7 +484,7 @@ bool API::ReadPlantMatgloss (vector & plants) int matgloss_plant_drink_offset = minfo->getOffset("matgloss_plant_drink"); int matgloss_plant_food_offset = minfo->getOffset("matgloss_plant_food"); int matgloss_plant_extract_offset = minfo->getOffset("matgloss_plant_extract"); - DfVector p_matgloss = d->dm->readVector (matgloss_address + matgloss_offset * 2, 4); + DfVector p_matgloss = d->p->readVector (matgloss_address + matgloss_offset * 2, 4); plants.clear(); @@ -499,14 +498,14 @@ bool API::ReadPlantMatgloss (vector & plants) // read the matgloss pointer from the vector into temp uint32_t temp = * (uint32_t *) p_matgloss[i]; // read the string pointed at by - //fill_char_buf(mat.id, d->dm->readSTLString(temp)); // reads a C string given an address - d->dm->readSTLString (temp, mat.id, 128); - d->dm->readSTLString (temp+matgloss_plant_name_offset, mat.name, 128); - d->dm->readSTLString (temp+matgloss_plant_drink_offset, mat.drink_name, 128); - d->dm->readSTLString (temp+matgloss_plant_food_offset, mat.food_name, 128); - d->dm->readSTLString (temp+matgloss_plant_extract_offset, mat.extract_name, 128); + //fill_char_buf(mat.id, d->p->readSTLString(temp)); // reads a C string given an address + d->p->readSTLString (temp, mat.id, 128); + d->p->readSTLString (temp+matgloss_plant_name_offset, mat.name, 128); + d->p->readSTLString (temp+matgloss_plant_drink_offset, mat.drink_name, 128); + d->p->readSTLString (temp+matgloss_plant_food_offset, mat.food_name, 128); + d->p->readSTLString (temp+matgloss_plant_extract_offset, mat.extract_name, 128); - //d->dm->readSTLString (temp + //d->p->readSTLString (temp plants.push_back (mat); } return true; @@ -518,7 +517,7 @@ bool API::ReadCreatureMatgloss (vector & creatures) int matgloss_address = minfo->getAddress ("matgloss"); int matgloss_offset = minfo->getHexValue ("matgloss_skip"); int matgloss_creature_name_offset = minfo->getOffset("matgloss_creature_name"); - DfVector p_matgloss = d->dm->readVector (matgloss_address + matgloss_offset * 6, 4); + DfVector p_matgloss = d->p->readVector (matgloss_address + matgloss_offset * 6, 4); creatures.clear(); @@ -532,9 +531,9 @@ bool API::ReadCreatureMatgloss (vector & creatures) // read the matgloss pointer from the vector into temp uint32_t temp = * (uint32_t *) p_matgloss[i]; // read the string pointed at by - //fill_char_buf(mat.id, d->dm->readSTLString(temp)); // reads a C string given an address - d->dm->readSTLString (temp, mat.id, 128); - d->dm->readSTLString (temp+matgloss_creature_name_offset, mat.name, 128); + //fill_char_buf(mat.id, d->p->readSTLString(temp)); // reads a C string given an address + d->p->readSTLString (temp, mat.id, 128); + d->p->readSTLString (temp+matgloss_creature_name_offset, mat.name, 128); creatures.push_back (mat); } return true; @@ -587,7 +586,7 @@ bool API::ReadGeology (vector < vector >& assign) uint32_t regions = g_pProcess->readDWord (world_offset + world_regions_offset); // read the geoblock vector - DfVector geoblocks = d->dm->readVector (world_offset + world_geoblocks_offset, 4); + DfVector geoblocks = d->p->readVector (world_offset + world_geoblocks_offset, 4); // iterate over 8 surrounding regions + local region for (int i = eNorthWest; i < eBiomeCount; i++) @@ -613,7 +612,7 @@ bool API::ReadGeology (vector < vector >& assign) uint32_t geoblock_off = * (uint32_t *) geoblocks[geoindex]; // get the vector with pointer to layers - DfVector geolayers = d->dm->readVector (geoblock_off + geolayer_geoblock_offset , 4); // let's hope + DfVector geolayers = d->p->readVector (geoblock_off + geolayer_geoblock_offset , 4); // let's hope // make sure we don't load crap assert (geolayers.getSize() > 0 && geolayers.getSize() <= 16); @@ -645,7 +644,7 @@ bool API::InitReadBuildings ( uint32_t& numbuildings ) if(buildings) { d->buildingsInited = true; - d->p_bld = new DfVector (d->dm->readVector (buildings, 4)); + d->p_bld = new DfVector (d->p->readVector (buildings, 4)); return true; } else @@ -703,7 +702,7 @@ bool API::InitReadConstructions(uint32_t & numconstructions) int constructions = d->offset_descriptor->getAddress ("constructions"); if(constructions) { - d->p_cons = new DfVector (d->dm->readVector (constructions, 4)); + d->p_cons = new DfVector (d->p->readVector (constructions, 4)); d->constructionsInited = true; numconstructions = d->p_cons->getSize(); return true; @@ -753,7 +752,7 @@ bool API::InitReadVegetation(uint32_t & numplants) if(vegetation && d->tree_offset) { d->vegetationInited = true; - d->p_veg = new DfVector (d->dm->readVector (vegetation, 4)); + d->p_veg = new DfVector (d->p->readVector (vegetation, 4)); numplants = d->p_veg->getSize(); return true; } @@ -840,7 +839,7 @@ bool API::InitReadCreatures( uint32_t &numcreatures ) // && d->creature_likes_offset ) { - d->p_cre = new DfVector (d->dm->readVector (creatures, 4)); + d->p_cre = new DfVector (d->p->readVector (creatures, 4)); //InitReadNameTables(); d->creaturesInited = true; numcreatures = d->p_cre->getSize(); @@ -925,11 +924,11 @@ bool API::ReadCreature (const int32_t &index, t_creature & furball) g_pProcess->readDWord (temp + d->creature_flags1_offset, furball.flags1.whole); g_pProcess->readDWord (temp + d->creature_flags2_offset, furball.flags2.whole); // normal names - d->dm->readSTLString (temp + d->creature_first_name_offset, furball.first_name, 128); - d->dm->readSTLString (temp + d->creature_nick_name_offset, furball.nick_name, 128); + d->p->readSTLString (temp + d->creature_first_name_offset, furball.first_name, 128); + d->p->readSTLString (temp + d->creature_nick_name_offset, furball.nick_name, 128); // custom profession - d->dm->readSTLString (temp + d->creature_nick_name_offset, furball.nick_name, 128); - fill_char_buf (furball.custom_profession, d->dm->readSTLString (temp + d->creature_custom_profession_offset)); + d->p->readSTLString (temp + d->creature_nick_name_offset, furball.nick_name, 128); + fill_char_buf (furball.custom_profession, d->p->readSTLString (temp + d->creature_custom_profession_offset)); // crazy composited names g_pProcess->read (temp + d->creature_last_name_offset, sizeof (t_lastname), (uint8_t *) &furball.last_name); g_pProcess->read (temp + d->creature_squad_name_offset, sizeof (t_squadname), (uint8_t *) &furball.squad_name); @@ -941,7 +940,7 @@ bool API::ReadCreature (const int32_t &index, t_creature & furball) // traits g_pProcess->read (temp + d->creature_traits_offset, sizeof (uint16_t) * NUM_CREATURE_TRAITS, (uint8_t *) &furball.traits); // learned skills - DfVector skills (d->dm->readVector (temp + d->creature_skills_offset, 4)); + DfVector skills (d->p->readVector (temp + d->creature_skills_offset, 4)); furball.numSkills = skills.getSize(); for (uint32_t i = 0; i < furball.numSkills;i++) { @@ -963,7 +962,7 @@ bool API::ReadCreature (const int32_t &index, t_creature & furball) } //likes - DfVector likes(d->dm->readVector(temp+d->creature_likes_offset,4)); + DfVector likes(d->p->readVector(temp+d->creature_likes_offset,4)); furball.numLikes = likes.getSize(); for(uint32_t i = 0;i > & nameTable) if(genericAddress && transAddress && word_table_offset) { - DfVector genericVec (d->dm->readVector (genericAddress, 4)); - DfVector transVec (d->dm->readVector (transAddress, 4)); + DfVector genericVec (d->p->readVector (genericAddress, 4)); + DfVector transVec (d->p->readVector (transAddress, 4)); for (uint32_t i = 0;i < genericVec.getSize();i++) { uint32_t genericNamePtr = * (uint32_t *) genericVec.at (i); - string genericName = d->dm->readSTLString (genericNamePtr); + string genericName = d->p->readSTLString (genericNamePtr); nameTable["GENERIC"].push_back (genericName); } for (uint32_t i = 0; i < transVec.getSize();i++) { uint32_t transPtr = * (uint32_t *) transVec.at (i); - string transName = d->dm->readSTLString (transPtr); - DfVector trans_names_vec (d->dm->readVector (transPtr + word_table_offset, 4)); + string transName = d->p->readSTLString (transPtr); + DfVector trans_names_vec (d->p->readVector (transPtr + word_table_offset, 4)); for (uint32_t j = 0;j < trans_names_vec.getSize();j++) { uint32_t transNamePtr = * (uint32_t *) trans_names_vec.at (j); - string name = d->dm->readSTLString (transNamePtr); + string name = d->p->readSTLString (transNamePtr); nameTable[transName].push_back (name); } } @@ -1113,7 +1112,6 @@ bool API::Attach() return false; // couldn't attach to process, no go } d->offset_descriptor = d->p->getDescriptor(); - d->dm = d->p->getDataModel(); // process is attached, everything went just fine... hopefully return true; } @@ -1132,13 +1130,12 @@ bool API::Detach() d->pm = NULL; d->p = NULL; d->offset_descriptor = NULL; - d->dm = NULL; return true; } bool API::isAttached() { - return d->dm != NULL; + return d->p != NULL; } bool API::Suspend() @@ -1290,7 +1287,7 @@ bool API::InitReadItems(uint32_t & numitems) if(items && d->item_material_offset) { - d->p_itm = new DfVector (d->dm->readVector (items, 4)); + d->p_itm = new DfVector (d->p->readVector (items, 4)); d->itemsInited = true; numitems = d->p_itm->getSize(); return true; @@ -1375,15 +1372,15 @@ bool API::ReadItemTypes(vector< vector< t_itemType > > & itemTypes) int matgloss_skip = minfo->getHexValue("matgloss_skip"); int item_type_name_offset = minfo->getOffset("item_type_name"); for(int i = 8;i<20;i++){ - DfVector p_temp = d->dm->readVector(matgloss_address + i*matgloss_skip,4); + DfVector p_temp = d->p->readVector(matgloss_address + i*matgloss_skip,4); vector< t_itemType > typesForVec; for(uint32_t j =0; jdm->readSTLString(temp+4,currType.id,128); - d->dm->readSTLString(temp+item_type_name_offset,currType.name,128); + d->p->readSTLString(temp+4,currType.id,128); + d->p->readSTLString(temp+item_type_name_offset,currType.name,128); //stringsForVec.push_back(string(name)); typesForVec.push_back(currType); } @@ -1400,44 +1397,44 @@ bool API::ReadAllMatgloss(vector< vector< string > > & all) int matgloss_address = minfo->getAddress("matgloss"); int matgloss_skip = minfo->getHexValue("matgloss_skip"); for(int i = 0;i<7;i++){ - DfVector p_temp = d->dm->readVector(matgloss_address + i*matgloss_skip,4); + DfVector p_temp = d->p->readVector(matgloss_address + i*matgloss_skip,4); vector< string > stringsForVec; for(uint32_t j =0; jdm->readSTLString(temp); + string tempStr = d->p->readSTLString(temp); stringsForVec.push_back(tempStr); } all.push_back(stringsForVec); } for(int i = 7;i<22;i++){ - DfVector p_temp = d->dm->readVector(matgloss_address + i*matgloss_skip,4); + DfVector p_temp = d->p->readVector(matgloss_address + i*matgloss_skip,4); vector< string > stringsForVec; for(uint32_t j =0; jdm->readSTLString(temp+4); + string tempStr = d->p->readSTLString(temp+4); stringsForVec.push_back(tempStr); } all.push_back(stringsForVec); } for(int i = 22;i<25;i++){ - DfVector p_temp = d->dm->readVector(matgloss_address + i*matgloss_skip,4); + DfVector p_temp = d->p->readVector(matgloss_address + i*matgloss_skip,4); vector< string > stringsForVec; for(uint32_t j =0; jdm->readSTLString(temp); + string tempStr = d->p->readSTLString(temp); stringsForVec.push_back(tempStr); } all.push_back(stringsForVec); } - DfVector p_temp = d->dm->readVector(0x01604104,4); + DfVector p_temp = d->p->readVector(0x01604104,4); vector< string > stringsForVec; for(uint32_t j =0; jdm->readSTLString(temp); + string tempStr = d->p->readSTLString(temp); stringsForVec.push_back(tempStr); } all.push_back(stringsForVec); diff --git a/library/DFProcess-linux-SHM.cpp b/library/DFProcess-linux-SHM.cpp index 1f1650f9d..3c729f790 100644 --- a/library/DFProcess-linux-SHM.cpp +++ b/library/DFProcess-linux-SHM.cpp @@ -40,7 +40,6 @@ class SHMProcess::Private public: Private() { - my_datamodel = NULL; my_descriptor = NULL; my_pid = 0; my_shm = 0; @@ -51,7 +50,6 @@ class SHMProcess::Private identified = false; }; ~Private(){}; - DataModel* my_datamodel; memory_info * my_descriptor; DFWindow * my_window; pid_t my_pid; @@ -229,7 +227,6 @@ bool SHMProcess::Private::validate(char * exe_file, uint32_t pid, vector my_datamodel) - { - delete d->my_datamodel; - } if(d->my_window) { delete d->my_window; @@ -262,12 +255,6 @@ SHMProcess::~SHMProcess() delete d; } - -DataModel *SHMProcess::getDataModel() -{ - return d->my_datamodel; -} - memory_info * SHMProcess::getDescriptor() { return d->my_descriptor; @@ -603,3 +590,50 @@ const std::string SHMProcess::readCString (uint32_t offset) return temp; } +DfVector SHMProcess::readVector (uint32_t offset, uint32_t item_size) +{ + /* + GNU libstdc++ vector is three pointers long + ptr start + ptr end + ptr alloc_end + + we don't care about alloc_end because we don't try to add stuff + */ + uint32_t start = g_pProcess->readDWord(offset); + uint32_t end = g_pProcess->readDWord(offset+4); + uint32_t size = (end - start) /4; + return DfVector(start,size,item_size); +} + +const std::string SHMProcess::readSTLString(uint32_t offset) +{ + ((shm_read_small *)d->my_shm)->address = offset; + full_barrier + ((shm_read_small *)d->my_shm)->pingpong = DFPP_READ_STL_STRING; + d->waitWhile(DFPP_READ_STL_STRING); + //int length = ((shm_retval *)d->my_shm)->value; + return(string( (char *)d->my_shm+SHM_HEADER)); +} + +size_t SHMProcess::readSTLString (uint32_t offset, char * buffer, size_t bufcapacity) +{ + ((shm_read_small *)d->my_shm)->address = offset; + full_barrier + ((shm_read_small *)d->my_shm)->pingpong = DFPP_READ_STL_STRING; + d->waitWhile(DFPP_READ_STL_STRING); + size_t length = ((shm_retval *)d->my_shm)->value; + size_t fit = min(bufcapacity - 1, length); + strncpy(buffer,(char *)d->my_shm+SHM_HEADER,fit); + buffer[fit] = 0; + return fit; +} + +void SHMProcess::writeSTLString(const uint32_t address, const std::string writeString) +{ + ((shm_write_small *)d->my_shm)->address = address; + strncpy(d->my_shm+SHM_HEADER,writeString.c_str(),writeString.length()+1); // length + 1 for the null terminator + full_barrier + ((shm_write_small *)d->my_shm)->pingpong = DFPP_WRITE_STL_STRING; + d->waitWhile(DFPP_WRITE_STL_STRING); +} \ No newline at end of file diff --git a/library/DFProcess-linux-wine.cpp b/library/DFProcess-linux-wine.cpp new file mode 100644 index 000000000..24cc71cc8 --- /dev/null +++ b/library/DFProcess-linux-wine.cpp @@ -0,0 +1,584 @@ +/* +www.sourceforge.net/projects/dfhack +Copyright (c) 2009 Petr Mrázek (peterix), Kenneth Ferland (Impaler[WrG]), dorf + +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. +*/ +#include "DFCommonInternal.h" +#include +#include +using namespace DFHack; + +class WineProcess::Private +{ + public: + Private() + { + my_descriptor = NULL; + my_handle = NULL; + my_window = NULL; + my_pid = 0; + attached = false; + suspended = false; + memFileHandle = 0; + }; + ~Private(){}; + DFWindow* my_window; + memory_info * my_descriptor; + ProcessHandle my_handle; + uint32_t my_pid; + string memFile; + int memFileHandle; + bool attached; + bool suspended; + bool identified; + bool validate(char * exe_file, uint32_t pid, char * mem_file, vector & known_versions); +}; + +WineProcess::WineProcess(uint32_t pid, vector & known_versions) +: d(new Private()) +{ + char dir_name [256]; + char exe_link_name [256]; + char mem_name [256]; + char cwd_name [256]; + char cmdline_name [256]; + char target_name[1024]; + int target_result; + + d->identified = false; + + sprintf(dir_name,"/proc/%d/", pid); + sprintf(exe_link_name,"/proc/%d/exe", pid); + sprintf(mem_name,"/proc/%d/mem", pid); + sprintf(cwd_name,"/proc/%d/cwd", pid); + sprintf(cmdline_name,"/proc/%d/cmdline", pid); + + // resolve /proc/PID/exe link + target_result = readlink(exe_link_name, target_name, sizeof(target_name)-1); + if (target_result == -1) + { + return; + } + // make sure we have a null terminated string... + target_name[target_result] = 0; + + // is this the regular linux DF? + if (strstr(target_name, "dwarfort.exe") != NULL) + { + // create linux process, add it to the vector + d->identified = d->validate(target_name,pid,mem_name,known_versions ); + d->my_window = new DFWindow(this); + return; + } + + // FIXME: this fails when the wine process isn't started from the 'current working directory'. strip path data from cmdline + // is this windows version of Df running in wine? + if(strstr(target_name, "wine-preloader")!= NULL) + { + // get working directory + target_result = readlink(cwd_name, target_name, sizeof(target_name)-1); + target_name[target_result] = 0; + + // got path to executable, do the same for its name + ifstream ifs ( cmdline_name , ifstream::in ); + string cmdline; + getline(ifs,cmdline); + if (cmdline.find("dwarfort-w.exe") != string::npos || cmdline.find("dwarfort.exe") != string::npos || cmdline.find("Dwarf Fortress.exe") != string::npos) + { + char exe_link[1024]; + // put executable name and path together + sprintf(exe_link,"%s/%s",target_name,cmdline.c_str()); + + // create wine process, add it to the vector + d->identified = d->validate(exe_link,pid,mem_name,known_versions); + d->my_window = new DFWindow(this); + return; + } + } +} + +bool WineProcess::isSuspended() +{ + return d->suspended; +} +bool WineProcess::isAttached() +{ + return d->attached; +} + +bool WineProcess::isIdentified() +{ + return d->identified; +} + +bool WineProcess::Private::validate(char * exe_file,uint32_t pid, char * memFile, vector & known_versions) +{ + md5wrapper md5; + // get hash of the running DF process + string hash = md5.getHashFromFile(exe_file); + vector::iterator it; + + // iterate over the list of memory locations + for ( it=known_versions.begin() ; it < known_versions.end(); it++ ) + { + // are the md5 hashes the same? + if(memory_info::OS_WINDOWS == (*it).getOS() && hash == (*it).getString("md5")) + { + memory_info * m = &*it; + my_descriptor = m; + my_handle = my_pid = pid; + // tell WineProcess about the /proc/PID/mem file + this->memFile = memFile; + identified = true; + return true; + } + } + return false; +} + +WineProcess::~WineProcess() +{ + if(d->attached) + { + detach(); + } + if(d->my_window) + delete d->my_window; + delete d; +} + +memory_info * WineProcess::getDescriptor() +{ + return d->my_descriptor; +} + +DFWindow * WineProcess::getWindow() +{ + return d->my_window; +} + +int WineProcess::getPID() +{ + return d->my_pid; +} + +//FIXME: implement +bool WineProcess::getThreadIDs(vector & threads ) +{ + return false; +} + +//FIXME: cross-reference with ELF segment entries? +void WineProcess::getMemRanges( vector & ranges ) +{ + char buffer[1024]; + char permissions[5]; // r/-, w/-, x/-, p/s, 0 + + sprintf(buffer, "/proc/%lu/maps", d->my_pid); + FILE *mapFile = ::fopen(buffer, "r"); + uint64_t offset, device1, device2, node; + + while (fgets(buffer, 1024, mapFile)) + { + t_memrange temp; + temp.name[0] = 0; + sscanf(buffer, "%llx-%llx %s %llx %2llu:%2llu %llu %s", + &temp.start, + &temp.end, + (char*)&permissions, + &offset, &device1, &device2, &node, + (char*)&temp.name); + temp.read = permissions[0] == 'r'; + temp.write = permissions[1] == 'w'; + temp.execute = permissions[2] == 'x'; + ranges.push_back(temp); + } +} + +bool WineProcess::asyncSuspend() +{ + return suspend(); +} + +bool WineProcess::suspend() +{ + int status; + if(!d->attached) + return false; + if(d->suspended) + return true; + if (kill(d->my_handle, SIGSTOP) == -1) + { + // no, we got an error + perror("kill SIGSTOP error"); + return false; + } + while(true) + { + // we wait on the pid + pid_t w = waitpid(d->my_handle, &status, 0); + if (w == -1) + { + // child died + perror("DF exited during suspend call"); + return false; + } + // stopped -> let's continue + if (WIFSTOPPED(status)) + { + break; + } + } + d->suspended = true; + return true; +} + +bool WineProcess::forceresume() +{ + return resume(); +} + +bool WineProcess::resume() +{ + if(!d->attached) + return false; + if(!d->suspended) + return true; + if (ptrace(PTRACE_CONT, d->my_handle, NULL, NULL) == -1) + { + // no, we got an error + perror("ptrace resume error"); + return false; + } + d->suspended = false; + return true; +} + + +bool WineProcess::attach() +{ + int status; + if(g_pProcess != NULL) + { + return false; + } + // can we attach? + if (ptrace(PTRACE_ATTACH , d->my_handle, NULL, NULL) == -1) + { + // no, we got an error + perror("ptrace attach error"); + cerr << "attach failed on pid " << d->my_handle << endl; + return false; + } + while(true) + { + // we wait on the pid + pid_t w = waitpid(d->my_handle, &status, 0); + if (w == -1) + { + // child died + perror("wait inside attach()"); + return false; + } + // stopped -> let's continue + if (WIFSTOPPED(status)) + { + break; + } + } + d->suspended = true; + + int proc_pid_mem = open(d->memFile.c_str(),O_RDONLY); + if(proc_pid_mem == -1) + { + ptrace(PTRACE_DETACH, d->my_handle, NULL, NULL); + cerr << "couldn't open /proc/" << d->my_handle << "/mem" << endl; + perror("open(memFile.c_str(),O_RDONLY)"); + return false; + } + else + { + d->attached = true; + g_pProcess = this; + + d->memFileHandle = proc_pid_mem; + return true; // we are attached + } +} + +bool WineProcess::detach() +{ + if(!d->attached) return false; + if(!d->suspended) suspend(); + int result = 0; + // close /proc/PID/mem + result = close(d->memFileHandle); + if(result == -1) + { + cerr << "couldn't close /proc/"<< d->my_handle <<"/mem" << endl; + perror("mem file close"); + return false; + } + else + { + // detach + result = ptrace(PTRACE_DETACH, d->my_handle, NULL, NULL); + if(result == -1) + { + cerr << "couldn't detach from process pid" << d->my_handle << endl; + perror("ptrace detach"); + return false; + } + else + { + d->attached = false; + g_pProcess = NULL; + return true; + } + } +} + + +// danger: uses recursion! +void WineProcess::read (const uint32_t offset, const uint32_t size, uint8_t *target) +{ + if(size == 0) return; + + ssize_t result; + result = pread(d->memFileHandle, target,size,offset); + if(result != size) + { + if(result == -1) + { + cerr << "pread failed: can't read " << size << " bytes at addres " << offset << endl; + cerr << "errno: " << errno << endl; + errno = 0; + } + else + { + read(offset + result, size - result, target + result); + } + } +} + +uint8_t WineProcess::readByte (const uint32_t offset) +{ + uint8_t val; + read(offset, 1, &val); + return val; +} + +void WineProcess::readByte (const uint32_t offset, uint8_t &val ) +{ + read(offset, 1, &val); +} + +uint16_t WineProcess::readWord (const uint32_t offset) +{ + uint16_t val; + read(offset, 2, (uint8_t *) &val); + return val; +} + +void WineProcess::readWord (const uint32_t offset, uint16_t &val) +{ + read(offset, 2, (uint8_t *) &val); +} + +uint32_t WineProcess::readDWord (const uint32_t offset) +{ + uint32_t val; + read(offset, 4, (uint8_t *) &val); + return val; +} +void WineProcess::readDWord (const uint32_t offset, uint32_t &val) +{ + read(offset, 4, (uint8_t *) &val); +} + +/* + * WRITING + */ + +void WineProcess::writeDWord (uint32_t offset, uint32_t data) +{ + ptrace(PTRACE_POKEDATA,d->my_handle, offset, data); +} + +// using these is expensive. +void WineProcess::writeWord (uint32_t offset, uint16_t data) +{ + uint32_t orig = readDWord(offset); + orig &= 0xFFFF0000; + orig |= data; + /* + orig |= 0x0000FFFF; + orig &= data; + */ + ptrace(PTRACE_POKEDATA,d->my_handle, offset, orig); +} + +void WineProcess::writeByte (uint32_t offset, uint8_t data) +{ + uint32_t orig = readDWord(offset); + orig &= 0xFFFFFF00; + orig |= data; + /* + orig |= 0x000000FF; + orig &= data; + */ + ptrace(PTRACE_POKEDATA,d->my_handle, offset, orig); +} + +// blah. I hate the kernel devs for crippling /proc/PID/mem. THIS IS RIDICULOUS +void WineProcess::write (uint32_t offset, uint32_t size, uint8_t *source) +{ + uint32_t indexptr = 0; + while (size > 0) + { + // default: we push 4 bytes + if(size >= 4) + { + writeDWord(offset, *(uint32_t *) (source + indexptr)); + offset +=4; + indexptr +=4; + size -=4; + } + // last is either three or 2 bytes + else if(size >= 2) + { + writeWord(offset, *(uint16_t *) (source + indexptr)); + offset +=2; + indexptr +=2; + size -=2; + } + // finishing move + else if(size == 1) + { + writeByte(offset, *(uint8_t *) (source + indexptr)); + return; + } + } +} + +const std::string WineProcess::readCString (uint32_t offset) +{ + std::string temp; + char temp_c[256]; + int counter = 0; + char r; + do + { + r = readByte(offset+counter); + temp_c[counter] = r; + counter++; + } while (r && counter < 255); + temp_c[counter] = 0; + temp = temp_c; + return temp; +} + +DfVector WineProcess::readVector (uint32_t offset, uint32_t item_size) +{ + /* + MSVC++ vector is four pointers long + ptr allocator + ptr start + ptr end + ptr alloc_end + + we don't care about alloc_end because we don't try to add stuff + we also don't care about the allocator thing in front + */ + uint32_t start = g_pProcess->readDWord(offset+4); + uint32_t end = g_pProcess->readDWord(offset+8); + uint32_t size = (end - start) /4; + return DfVector(start,size,item_size); +} + +size_t WineProcess::readSTLString (uint32_t offset, char * buffer, size_t bufcapacity) +{ + /* + MSVC++ string + ptr allocator + union + { + char[16] start; + char * start_ptr + } + Uint32 length + Uint32 capacity + */ + uint32_t start_offset = offset + 4; + size_t length = g_pProcess->readDWord(offset + 20); + + size_t capacity = g_pProcess->readDWord(offset + 24); + size_t read_real = min(length, bufcapacity-1);// keep space for null termination + + // read data from inside the string structure + if(capacity < 16) + { + g_pProcess->read(start_offset, read_real , (uint8_t *)buffer); + } + else // read data from what the offset + 4 dword points to + { + start_offset = g_pProcess->readDWord(start_offset);// dereference the start offset + g_pProcess->read(start_offset, read_real, (uint8_t *)buffer); + } + + buffer[read_real] = 0; + return read_real; +} + +const string WineProcess::readSTLString (uint32_t offset) +{ + /* + MSVC++ string + ptr allocator + union + { + char[16] start; + char * start_ptr + } + Uint32 length + Uint32 capacity + */ + uint32_t start_offset = offset + 4; + uint32_t length = g_pProcess->readDWord(offset + 20); + uint32_t capacity = g_pProcess->readDWord(offset + 24); + char * temp = new char[capacity+1]; + + // read data from inside the string structure + if(capacity < 16) + { + g_pProcess->read(start_offset, capacity, (uint8_t *)temp); + } + else // read data from what the offset + 4 dword points to + { + start_offset = g_pProcess->readDWord(start_offset);// dereference the start offset + g_pProcess->read(start_offset, capacity, (uint8_t *)temp); + } + + temp[length] = 0; + string ret = temp; + delete temp; + return ret; +} \ No newline at end of file diff --git a/library/DFProcess-linux.cpp b/library/DFProcess-linux.cpp index 31d4c72de..5bda864a6 100644 --- a/library/DFProcess-linux.cpp +++ b/library/DFProcess-linux.cpp @@ -31,7 +31,6 @@ class NormalProcess::Private public: Private() { - my_datamodel = NULL; my_descriptor = NULL; my_handle = NULL; my_window = NULL; @@ -41,7 +40,6 @@ class NormalProcess::Private memFileHandle = 0; }; ~Private(){}; - DataModel* my_datamodel; DFWindow* my_window; memory_info * my_descriptor; ProcessHandle my_handle; @@ -90,31 +88,6 @@ NormalProcess::NormalProcess(uint32_t pid, vector & known_versions d->my_window = new DFWindow(this); return; } - - // FIXME: this fails when the wine process isn't started from the 'current working directory'. strip path data from cmdline - // is this windows version of Df running in wine? - if(strstr(target_name, "wine-preloader")!= NULL) - { - // get working directory - target_result = readlink(cwd_name, target_name, sizeof(target_name)-1); - target_name[target_result] = 0; - - // got path to executable, do the same for its name - ifstream ifs ( cmdline_name , ifstream::in ); - string cmdline; - getline(ifs,cmdline); - if (cmdline.find("dwarfort-w.exe") != string::npos || cmdline.find("dwarfort.exe") != string::npos || cmdline.find("Dwarf Fortress.exe") != string::npos) - { - char exe_link[1024]; - // put executable name and path together - sprintf(exe_link,"%s/%s",target_name,cmdline.c_str()); - - // create wine process, add it to the vector - d->identified = d->validate(exe_link,pid,mem_name,known_versions); - d->my_window = new DFWindow(this); - return; - } - } } bool NormalProcess::isSuspended() @@ -144,16 +117,8 @@ bool NormalProcess::Private::validate(char * exe_file,uint32_t pid, char * memFi if(hash == (*it).getString("md5")) // are the md5 hashes the same? { memory_info * m = &*it; - // df can run under wine on Linux - if(memory_info::OS_WINDOWS == (*it).getOS()) + if (memory_info::OS_LINUX == (*it).getOS()) { - my_datamodel =new DMWindows40d(); - my_descriptor = m; - my_handle = my_pid = pid; - } - else if (memory_info::OS_LINUX == (*it).getOS()) - { - my_datamodel =new DMLinux40d(); my_descriptor = m; my_handle = my_pid = pid; } @@ -178,19 +143,11 @@ NormalProcess::~NormalProcess() detach(); } // destroy data model. this is assigned by processmanager - if(d->my_datamodel) - delete d->my_datamodel; if(d->my_window) delete d->my_window; delete d; } - -DataModel *NormalProcess::getDataModel() -{ - return d->my_datamodel; -} - memory_info * NormalProcess::getDescriptor() { return d->my_descriptor; @@ -522,3 +479,51 @@ const std::string NormalProcess::readCString (uint32_t offset) return temp; } +DfVector NormalProcess::readVector (uint32_t offset, uint32_t item_size) +{ + /* + GNU libstdc++ vector is three pointers long + ptr start + ptr end + ptr alloc_end + + we don't care about alloc_end because we don't try to add stuff + */ + uint32_t start = g_pProcess->readDWord(offset); + uint32_t end = g_pProcess->readDWord(offset+4); + uint32_t size = (end - start) /4; + return DfVector(start,size,item_size); +} + +struct _Rep_base +{ + uint32_t _M_length; + uint32_t _M_capacity; + uint32_t _M_refcount; +}; + +size_t NormalProcess::readSTLString (uint32_t offset, char * buffer, size_t bufcapacity) +{ + _Rep_base header; + offset = g_pProcess->readDWord(offset); + g_pProcess->read(offset - sizeof(_Rep_base),sizeof(_Rep_base),(uint8_t *)&header); + size_t read_real = min((size_t)header._M_length, bufcapacity-1);// keep space for null termination + g_pProcess->read(offset,read_real,(uint8_t * )buffer); + buffer[read_real] = 0; + return read_real; +} + +const string NormalProcess::readSTLString (uint32_t offset) +{ + _Rep_base header; + + offset = g_pProcess->readDWord(offset); + g_pProcess->read(offset - sizeof(_Rep_base),sizeof(_Rep_base),(uint8_t *)&header); + + // FIXME: use char* everywhere, avoid string + char * temp = new char[header._M_length+1]; + g_pProcess->read(offset,header._M_length+1,(uint8_t * )temp); + string ret(temp); + delete temp; + return ret; +} \ No newline at end of file diff --git a/library/DFProcess-windows-SHM.cpp b/library/DFProcess-windows-SHM.cpp index fe58802ea..bccca900c 100644 --- a/library/DFProcess-windows-SHM.cpp +++ b/library/DFProcess-windows-SHM.cpp @@ -31,7 +31,6 @@ class SHMProcess::Private public: Private() { - my_datamodel = NULL; my_descriptor = NULL; my_pid = 0; my_shm = 0; @@ -43,7 +42,6 @@ class SHMProcess::Private DFCLMutex = 0; }; ~Private(){}; - DataModel* my_datamodel; memory_info * my_descriptor; DFWindow * my_window; uint32_t my_pid; @@ -153,122 +151,119 @@ SHMProcess::SHMProcess(vector & known_versions) char exe_link_name [256]; char target_name[1024]; int target_result; - do + // get server and client mutex + d->DFSVMutex = OpenMutex(SYNCHRONIZE,false, "DFSVMutex"); + if(d->DFSVMutex == 0) { - // get server and client mutex - d->DFSVMutex = OpenMutex(SYNCHRONIZE,false, "DFSVMutex"); - if(d->DFSVMutex == 0) - { - break; - } - d->DFCLMutex = OpenMutex(SYNCHRONIZE,false, "DFCLMutex"); - if(d->DFCLMutex == 0) - { - break; - } - if(!attach()) - { - break; - } - - // All seems to be OK so far. Attached and connected to something that looks like DF + return; + } + d->DFCLMutex = OpenMutex(SYNCHRONIZE,false, "DFCLMutex"); + if(d->DFCLMutex == 0) + { + return; + } + if(!attach()) + { + return; + } + + // All seems to be OK so far. Attached and connected to something that looks like DF + + // Test bridge version, will also detect when we connect to something that doesn't respond + bool bridgeOK; + if(!d->DF_TestBridgeVersion(bridgeOK)) + { + fprintf(stderr,"DF terminated during reading\n"); + UnmapViewOfFile(d->my_shm); + ReleaseMutex(d->DFCLMutex); + CloseHandle(d->DFSVMutex); + d->DFSVMutex = 0; + CloseHandle(d->DFCLMutex); + d->DFCLMutex = 0; + return; + } + if(!bridgeOK) + { + fprintf(stderr,"SHM bridge version mismatch\n"); + ((shm_cmd *)d->my_shm)->pingpong = DFPP_RUNNING; + UnmapViewOfFile(d->my_shm); + ReleaseMutex(d->DFCLMutex); + CloseHandle(d->DFSVMutex); + d->DFSVMutex = 0; + CloseHandle(d->DFCLMutex); + d->DFCLMutex = 0; + return; + } + /* + * get the PID from DF + */ + if(d->DF_GetPID(d->my_pid)) + { + // try to identify the DF version + do // glorified goto + { + IMAGE_NT_HEADERS32 pe_header; + IMAGE_SECTION_HEADER sections[16]; + HMODULE hmod = NULL; + DWORD junk; + HANDLE hProcess; + bool found = false; + d->identified = false; + // open process, we only need the process open + hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, d->my_pid ); + if (NULL == hProcess) + break; + + // try getting the first module of the process + if(EnumProcessModules(hProcess, &hmod, 1 * sizeof(HMODULE), &junk) == 0) + { + CloseHandle(hProcess); + cout << "EnumProcessModules fail'd" << endl; + break; + } + // got base ;) + uint32_t base = (uint32_t)hmod; + + // read from this process + uint32_t pe_offset = readDWord(base+0x3C); + read(base + pe_offset , sizeof(pe_header), (uint8_t *)&pe_header); + read(base + pe_offset+ sizeof(pe_header), sizeof(sections) , (uint8_t *)§ions ); + + // iterate over the list of memory locations + vector::iterator it; + for ( it=known_versions.begin() ; it < known_versions.end(); it++ ) + { + uint32_t pe_timestamp = (*it).getHexValue("pe_timestamp"); + if (pe_timestamp == pe_header.FileHeader.TimeDateStamp) + { + memory_info *m = new memory_info(*it); + m->RebaseAll(base); + d->my_descriptor = m; + d->identified = true; + cerr << "identified " << m->getVersion() << endl; + break; + } + } + CloseHandle(hProcess); + } while (0); // glorified goto end - // Test bridge version, will also detect when we connect to something that doesn't respond - bool bridgeOK; - if(!d->DF_TestBridgeVersion(bridgeOK)) + if(d->identified) { - fprintf(stderr,"DF terminated during reading\n"); - UnmapViewOfFile(d->my_shm); - ReleaseMutex(d->DFCLMutex); - CloseHandle(d->DFSVMutex); - d->DFSVMutex = 0; - CloseHandle(d->DFCLMutex); - d->DFCLMutex = 0; - break; + d->my_window = new DFWindow(this); } - if(!bridgeOK) + else { - fprintf(stderr,"SHM bridge version mismatch\n"); ((shm_cmd *)d->my_shm)->pingpong = DFPP_RUNNING; UnmapViewOfFile(d->my_shm); + d->my_shm = 0; ReleaseMutex(d->DFCLMutex); CloseHandle(d->DFSVMutex); d->DFSVMutex = 0; CloseHandle(d->DFCLMutex); d->DFCLMutex = 0; - break; + return; } - /* - * get the PID from DF - */ - if(d->DF_GetPID(d->my_pid)) - { - // try to identify the DF version - do // glorified goto - { - IMAGE_NT_HEADERS32 pe_header; - IMAGE_SECTION_HEADER sections[16]; - HMODULE hmod = NULL; - DWORD junk; - HANDLE hProcess; - bool found = false; - d->identified = false; - // open process, we only need the process open - hProcess = OpenProcess( PROCESS_ALL_ACCESS, FALSE, d->my_pid ); - if (NULL == hProcess) - break; - - // try getting the first module of the process - if(EnumProcessModules(hProcess, &hmod, 1 * sizeof(HMODULE), &junk) == 0) - { - CloseHandle(hProcess); - cout << "EnumProcessModules fail'd" << endl; - break; - } - // got base ;) - uint32_t base = (uint32_t)hmod; - - // read from this process - uint32_t pe_offset = readDWord(base+0x3C); - read(base + pe_offset , sizeof(pe_header), (uint8_t *)&pe_header); - read(base + pe_offset+ sizeof(pe_header), sizeof(sections) , (uint8_t *)§ions ); - - // iterate over the list of memory locations - vector::iterator it; - for ( it=known_versions.begin() ; it < known_versions.end(); it++ ) - { - uint32_t pe_timestamp = (*it).getHexValue("pe_timestamp"); - if (pe_timestamp == pe_header.FileHeader.TimeDateStamp) - { - memory_info *m = new memory_info(*it); - m->RebaseAll(base); - d->my_datamodel = new DMWindows40d(); - d->my_descriptor = m; - d->identified = true; - cerr << "identified " << m->getVersion() << endl; - break; - } - } - CloseHandle(hProcess); - } while (0); // glorified goto end - - if(d->identified) - { - d->my_window = new DFWindow(this); - } - else - { - ((shm_cmd *)d->my_shm)->pingpong = DFPP_RUNNING; - UnmapViewOfFile(d->my_shm); - ReleaseMutex(d->DFCLMutex); - CloseHandle(d->DFSVMutex); - d->DFSVMutex = 0; - CloseHandle(d->DFCLMutex); - d->DFCLMutex = 0; - break; - } - } - } while (0); + } full_barrier // at this point, DF is attached and suspended, make it run detach(); @@ -295,10 +290,6 @@ SHMProcess::~SHMProcess() detach(); } // destroy data model. this is assigned by processmanager - if(d->my_datamodel) - { - delete d->my_datamodel; - } if(d->my_descriptor) { delete d->my_descriptor; @@ -319,12 +310,6 @@ SHMProcess::~SHMProcess() delete d; } - -DataModel *SHMProcess::getDataModel() -{ - return d->my_datamodel; -} - memory_info * SHMProcess::getDescriptor() { return d->my_descriptor; @@ -686,3 +671,56 @@ const std::string SHMProcess::readCString (uint32_t offset) return temp; } +DfVector SHMProcess::readVector (uint32_t offset, uint32_t item_size) +{ + /* + MSVC++ vector is four pointers long + ptr allocator + ptr start + ptr end + ptr alloc_end + + we don't care about alloc_end because we don't try to add stuff + we also don't care about the allocator thing in front + */ + uint32_t start = g_pProcess->readDWord(offset+4); + uint32_t end = g_pProcess->readDWord(offset+8); + uint32_t size = (end - start) /4; + return DfVector(start,size,item_size); +} + +const std::string SHMProcess::readSTLString(uint32_t offset) +{ + //offset -= 4; //msvc std::string pointers are 8 bytes ahead of their data, not 4 + ((shm_read_small *)d->my_shm)->address = offset; + full_barrier + ((shm_read_small *)d->my_shm)->pingpong = DFPP_READ_STL_STRING; + d->waitWhile(DFPP_READ_STL_STRING); + int length = ((shm_retval *)d->my_shm)->value; +// char temp_c[256]; +// strncpy(temp_c, d->my_shm+SHM_HEADER,length+1); // length + 1 for the null terminator + return(string(d->my_shm+SHM_HEADER)); +} + +size_t SHMProcess::readSTLString (uint32_t offset, char * buffer, size_t bufcapacity) +{ + //offset -= 4; //msvc std::string pointers are 8 bytes ahead of their data, not 4 + ((shm_read_small *)d->my_shm)->address = offset; + full_barrier + ((shm_read_small *)d->my_shm)->pingpong = DFPP_READ_STL_STRING; + d->waitWhile(DFPP_READ_STL_STRING); + size_t length = ((shm_retval *)d->my_shm)->value; + size_t real = min(length, bufcapacity - 1); + strncpy(buffer, d->my_shm+SHM_HEADER,real); // length + 1 for the null terminator + buffer[real] = 0; + return real; +} + +void SHMProcess::writeSTLString(const uint32_t address, const std::string writeString) +{ + ((shm_write_small *)d->my_shm)->address = address/*-4*/; + strncpy(d->my_shm+SHM_HEADER,writeString.c_str(),writeString.length()+1); // length + 1 for the null terminator + full_barrier + ((shm_write_small *)d->my_shm)->pingpong = DFPP_WRITE_STL_STRING; + d->waitWhile(DFPP_WRITE_STL_STRING); +} \ No newline at end of file diff --git a/library/DFProcess-windows.cpp b/library/DFProcess-windows.cpp index 87c934c96..f1780f474 100644 --- a/library/DFProcess-windows.cpp +++ b/library/DFProcess-windows.cpp @@ -29,7 +29,6 @@ class NormalProcess::Private public: Private() { - my_datamodel = NULL; my_descriptor = NULL; my_handle = NULL; my_main_thread = NULL; @@ -39,7 +38,6 @@ class NormalProcess::Private suspended = false; }; ~Private(){}; - DataModel* my_datamodel; memory_info * my_descriptor; DFWindow * my_window; ProcessHandle my_handle; @@ -109,7 +107,6 @@ NormalProcess::NormalProcess(uint32_t pid, vector & known_versions // keep track of created memory_info object so we can destroy it later d->my_descriptor = m; // process is responsible for destroying its data model - d->my_datamodel = new DMWindows40d(); d->my_pid = pid; d->my_handle = hProcess; d->identified = true; @@ -142,8 +139,6 @@ NormalProcess::~NormalProcess() { detach(); } - // destroy data model. this is assigned by processmanager - delete d->my_datamodel; // destroy our rebased copy of the memory descriptor delete d->my_descriptor; if(d->my_handle != NULL) @@ -161,13 +156,6 @@ NormalProcess::~NormalProcess() delete d; } - -DataModel *NormalProcess::getDataModel() -{ - return d->my_datamodel; -} - - memory_info * NormalProcess::getDescriptor() { return d->my_descriptor; @@ -390,3 +378,89 @@ const string NormalProcess::readCString (const uint32_t offset) return temp; } +DfVector NormalProcess::readVector (uint32_t offset, uint32_t item_size) +{ + /* + MSVC++ vector is four pointers long + ptr allocator + ptr start + ptr end + ptr alloc_end + + we don't care about alloc_end because we don't try to add stuff + we also don't care about the allocator thing in front + */ + uint32_t start = g_pProcess->readDWord(offset+4); + uint32_t end = g_pProcess->readDWord(offset+8); + uint32_t size = (end - start) /4; + return DfVector(start,size,item_size); +} + +size_t NormalProcess::readSTLString (uint32_t offset, char * buffer, size_t bufcapacity) +{ + /* + MSVC++ string + ptr allocator + union + { + char[16] start; + char * start_ptr +} +Uint32 length +Uint32 capacity +*/ + uint32_t start_offset = offset + 4; + size_t length = g_pProcess->readDWord(offset + 20); + + size_t capacity = g_pProcess->readDWord(offset + 24); + size_t read_real = min(length, bufcapacity-1);// keep space for null termination + + // read data from inside the string structure + if(capacity < 16) + { + g_pProcess->read(start_offset, read_real , (uint8_t *)buffer); + } + else // read data from what the offset + 4 dword points to + { + start_offset = g_pProcess->readDWord(start_offset);// dereference the start offset + g_pProcess->read(start_offset, read_real, (uint8_t *)buffer); + } + + buffer[read_real] = 0; + return read_real; +} + +const string NormalProcess::readSTLString (uint32_t offset) +{ + /* + MSVC++ string + ptr allocator + union + { + char[16] start; + char * start_ptr + } + Uint32 length + Uint32 capacity + */ + uint32_t start_offset = offset + 4; + uint32_t length = g_pProcess->readDWord(offset + 20); + uint32_t capacity = g_pProcess->readDWord(offset + 24); + char * temp = new char[capacity+1]; + + // read data from inside the string structure + if(capacity < 16) + { + g_pProcess->read(start_offset, capacity, (uint8_t *)temp); + } + else // read data from what the offset + 4 dword points to + { + start_offset = g_pProcess->readDWord(start_offset);// dereference the start offset + g_pProcess->read(start_offset, capacity, (uint8_t *)temp); + } + + temp[length] = 0; + string ret = temp; + delete temp; + return ret; +} \ No newline at end of file diff --git a/library/DFProcess.h b/library/DFProcess.h index 4ce46d50e..19acbbecd 100644 --- a/library/DFProcess.h +++ b/library/DFProcess.h @@ -30,9 +30,9 @@ distribution. namespace DFHack { class memory_info; - class DataModel; class Process; class DFWindow; + class DfVector; // structure describing a memory range struct DFHACK_EXPORT t_memrange @@ -90,6 +90,13 @@ namespace DFHack virtual void writeByte(const uint32_t address, const uint8_t value) = 0; virtual void write(uint32_t address, uint32_t length, uint8_t* buffer) = 0; + // read a string + virtual const string readSTLString (uint32_t offset) = 0; + virtual size_t readSTLString (uint32_t offset, char * buffer, size_t bufcapacity) = 0; + virtual void writeSTLString(const uint32_t address, const std::string writeString) = 0; + // read a vector from memory + virtual DfVector readVector (uint32_t offset, uint32_t item_size) = 0; + virtual const std::string readCString (uint32_t offset) = 0; virtual bool isSuspended() = 0; @@ -103,8 +110,6 @@ namespace DFHack // get the flattened Memory.xml entry of this process virtual memory_info *getDescriptor() = 0; - // get the DataModel for reading stl containers (depends on the version of stl DF was compiled with) - virtual DataModel *getDataModel() = 0; // get the DF's window (first that can be found ~_~) virtual DFWindow * getWindow() = 0; // get the DF Process ID @@ -142,6 +147,12 @@ namespace DFHack void writeByte(const uint32_t address, const uint8_t value); void write(uint32_t address, uint32_t length, uint8_t* buffer); + const string readSTLString (uint32_t offset); + size_t readSTLString (uint32_t offset, char * buffer, size_t bufcapacity); + void writeSTLString(const uint32_t address, const std::string writeString){}; + // read a vector from memory + DfVector readVector (uint32_t offset, uint32_t item_size); + const std::string readCString (uint32_t offset); bool isSuspended(); @@ -151,7 +162,6 @@ namespace DFHack bool getThreadIDs(vector & threads ); void getMemRanges( vector & ranges ); memory_info *getDescriptor(); - DataModel *getDataModel(); DFWindow * getWindow(); int getPID(); }; @@ -183,11 +193,68 @@ namespace DFHack void readByte(const uint32_t address, uint8_t & value); void read( uint32_t address, uint32_t length, uint8_t* buffer); + void writeDWord(const uint32_t address, const uint32_t value); + void writeWord(const uint32_t address, const uint16_t value); + void writeByte(const uint32_t address, const uint8_t value); + void write(uint32_t address, uint32_t length, uint8_t* buffer); + + const string readSTLString (uint32_t offset); + size_t readSTLString (uint32_t offset, char * buffer, size_t bufcapacity); + void writeSTLString(const uint32_t address, const std::string writeString); + // read a vector from memory + DfVector readVector (uint32_t offset, uint32_t item_size); + + const std::string readCString (uint32_t offset); + + bool isSuspended(); + bool isAttached(); + bool isIdentified(); + + bool getThreadIDs(vector & threads ); + void getMemRanges( vector & ranges ); + memory_info *getDescriptor(); + DFWindow * getWindow(); + int getPID(); + }; + +#ifdef LINUX_BUILD + class DFHACK_EXPORT WineProcess : virtual public Process + { + friend class ProcessEnumerator; + class Private; + private: + Private * const d; + + public: + WineProcess(uint32_t pid, vector & known_versions); + ~WineProcess(); + bool attach(); + bool detach(); + + bool suspend(); + bool asyncSuspend(); + bool resume(); + bool forceresume(); + + uint32_t readDWord(const uint32_t address); + void readDWord(const uint32_t address, uint32_t & value); + uint16_t readWord(const uint32_t address); + void readWord(const uint32_t address, uint16_t & value); + uint8_t readByte(const uint32_t address); + void readByte(const uint32_t address, uint8_t & value); + void read( uint32_t address, uint32_t length, uint8_t* buffer); + void writeDWord(const uint32_t address, const uint32_t value); void writeWord(const uint32_t address, const uint16_t value); void writeByte(const uint32_t address, const uint8_t value); void write(uint32_t address, uint32_t length, uint8_t* buffer); + const string readSTLString (uint32_t offset); + size_t readSTLString (uint32_t offset, char * buffer, size_t bufcapacity); + void writeSTLString(const uint32_t address, const std::string writeString){}; + // read a vector from memory + DfVector readVector (uint32_t offset, uint32_t item_size); + const std::string readCString (uint32_t offset); bool isSuspended(); @@ -197,9 +264,9 @@ namespace DFHack bool getThreadIDs(vector & threads ); void getMemRanges( vector & ranges ); memory_info *getDescriptor(); - DataModel *getDataModel(); DFWindow * getWindow(); int getPID(); }; +#endif } #endif diff --git a/library/DFProcessEnumerator-linux.cpp b/library/DFProcessEnumerator-linux.cpp index c9e20da70..321d151f0 100644 --- a/library/DFProcessEnumerator-linux.cpp +++ b/library/DFProcessEnumerator-linux.cpp @@ -76,11 +76,22 @@ bool ProcessEnumerator::findProcessess() if(p2->isIdentified()) { d->processes.push_back(p2); + continue; } else { delete p2; } + Process *p3 = new WineProcess(atoi(dir_entry_p->d_name),d->meminfo->meminfo); + if(p3->isIdentified()) + { + d->processes.push_back(p3); + continue; + } + else + { + delete p3; + } } closedir(dir_p); // return value depends on if we found some DF processes diff --git a/library/DFProcessEnumerator-windows.cpp b/library/DFProcessEnumerator-windows.cpp index 94ffd5b7e..ac11d2ea2 100644 --- a/library/DFProcessEnumerator-windows.cpp +++ b/library/DFProcessEnumerator-windows.cpp @@ -103,6 +103,7 @@ bool ProcessEnumerator::findProcessess() else { delete q; + q = 0; } } if(d->processes.size()) diff --git a/library/DFProcessEnumerator.h b/library/DFProcessEnumerator.h index 601ac7f41..eeeca9041 100644 --- a/library/DFProcessEnumerator.h +++ b/library/DFProcessEnumerator.h @@ -32,7 +32,6 @@ class TiXmlElement; namespace DFHack { class memory_info; - class DataModel; class Process; /* diff --git a/shmserver/shms-proto.cpp b/shmserver/shms-proto.cpp index 8ea698524..25b5810c9 100644 --- a/shmserver/shms-proto.cpp +++ b/shmserver/shms-proto.cpp @@ -29,6 +29,7 @@ distribution. #include "../library/integers.h" #include #include +#include //#include #include "shms.h" // various crud @@ -45,6 +46,7 @@ void SHM_Act (void) uint32_t numwaits = 0; uint32_t length; uint32_t address; + std::string * myStringPtr; check_again: // goto target!!! SCHED_YIELD // yield the CPU, valid only on single-core CPUs if(numwaits == 10000) @@ -69,6 +71,7 @@ void SHM_Act (void) case DFPP_RET_DWORD: case DFPP_RET_WORD: case DFPP_RET_BYTE: + case DFPP_RET_STRING: case DFPP_SUSPENDED: case DFPP_RET_PID: case DFPP_SV_ERROR: @@ -159,21 +162,22 @@ void SHM_Act (void) //MessageBox(0,"Broke out of loop properly","FUN", MB_OK); break; - // client requests contents of STL string at address - /*case DFPP_READ_STL_STRING: - char * real = *(char **)((shm_read_small *)shm)->address; - strncpy(shm + SHM_HEADER,real,1024*1024-1); + case DFPP_READ_STL_STRING: + myStringPtr = (std::string *) ((shm_read_small *)shm)->address; + ((shm_retval *)shm)->value = myStringPtr->length(); + strncpy(shm+SHM_HEADER,myStringPtr->c_str(),myStringPtr->length()+1);// length + 1 for the null terminator full_barrier ((shm_retval *)shm)->pingpong = DFPP_RET_STRING; goto check_again; -*/ - // client requests contents of a C string at address, max length (0 means zero terminated) -/* case DFPP_READ_C_STRING: - break; - // sv -> cl length + string contents - // client wants to set STL string at address to something + case DFPP_WRITE_STL_STRING: - break;*/ + myStringPtr = (std::string *) ((shm_write *)shm)->address; + myStringPtr->assign((const char *) (shm + SHM_HEADER)); + full_barrier + ((shm_cmd *)shm)->pingpong = DFPP_SUSPENDED; + goto check_again; + + default: ((shm_retval *)shm)->value = DFEE_INVALID_COMMAND; full_barrier diff --git a/shmserver/shms.h b/shmserver/shms.h index 01f59a9d6..6da1322cb 100644 --- a/shmserver/shms.h +++ b/shmserver/shms.h @@ -1,7 +1,7 @@ #ifndef DFCONNECT_H #define DFCONNECT_H -#define PINGPONG_VERSION 1 +#define PINGPONG_VERSION 2 #define SHM_KEY 123466 #define SHM_HEADER 1024 #define SHM_BODY 1024*1024 @@ -139,6 +139,13 @@ typedef struct uint32_t value; } shm_retval; +typedef struct +{ + volatile uint32_t pingpong; + uint32_t length; +} shm_retstr; + + void SHM_Act (void); bool isValidSHM(); uint32_t getPID(); diff --git a/tools/customCreatureNameProf.cpp b/tools/customCreatureNameProf.cpp index 5f16626fe..9171d0eaf 100644 --- a/tools/customCreatureNameProf.cpp +++ b/tools/customCreatureNameProf.cpp @@ -10,13 +10,15 @@ using namespace std; #include #include #include +#include template void print_bits ( T val, std::ostream& out ) { T n_bits = sizeof ( val ) * CHAR_BIT; - - for ( unsigned i = 0; i < n_bits; ++i ) { + + for ( unsigned i = 0; i < n_bits; ++i ) + { out<< !!( val & 1 ) << " "; val >>= 1; } @@ -28,26 +30,30 @@ vector creaturestypes; void printDwarves(DFHack::API & DF) { int dwarfCounter = 0; - for(uint32_t i = 0; i < numCreatures; i++) + for (uint32_t i = 0; i < numCreatures; i++) { DFHack::t_creature temp; DF.ReadCreature(i, temp); string type = creaturestypes[temp.type].id; - if(type == "DWARF" && !temp.flags1.bits.dead && !temp.flags2.bits.killed){ + if (type == "DWARF" && !temp.flags1.bits.dead && !temp.flags2.bits.killed) + { cout << i << ":"; - if(temp.nick_name[0]) + if (temp.nick_name[0]) { cout << temp.nick_name; } - else{ + else + { cout << temp.first_name; } string transName = DF.TranslateName(temp.last_name,names,creaturestypes[temp.type].id); cout << " " << temp.custom_profession; //transName; - if(dwarfCounter%3 != 2){ + if (dwarfCounter%3 != 2) + { cout << '\t'; } - else{ + else + { cout << endl; } dwarfCounter++; @@ -59,29 +65,29 @@ bool getDwarfSelection(DFHack::API & DF, DFHack::t_creature & toChange,string & { static string lastText; bool dwarfSuccess = false; - - while(!dwarfSuccess) + + while (!dwarfSuccess) { string input; cout << "\nSelect Dwarf to Change or q to Quit" << endl; DF.Resume(); getline (cin, input); DF.Suspend(); - if(input == "q") + if (input == "q") { return false; } - else if(input == "r") + else if (input == "r") { printDwarves(DF); } - else if(!input.empty()) + else if (!input.empty()) { int num; stringstream(input) >> num;//= atol(input.c_str()); dwarfSuccess = DF.ReadCreature(num,toChange); string type = creaturestypes[toChange.type].id; - if(type != "DWARF") + if (type != "DWARF") { dwarfSuccess = false; } @@ -92,21 +98,24 @@ bool getDwarfSelection(DFHack::API & DF, DFHack::t_creature & toChange,string & } } bool changeType = false; - while(!changeType) + while (!changeType) { string input; cout << "\n(n)ickname or (p)rofession?" << endl; getline(cin, input); - if(input == "q"){ + if (input == "q") + { return false; } - if(input == "n"){ + if (input == "n") + { commandString = "pzyn"; eraseAmount = string(toChange.nick_name).length(); changeType = true; isName = true; } - else if(input == "p"){ + else if (input == "p") + { commandString = "pzyp"; eraseAmount = string(toChange.custom_profession).length(); changeType = true; @@ -114,18 +123,21 @@ bool getDwarfSelection(DFHack::API & DF, DFHack::t_creature & toChange,string & } } bool changeValue = false; - while(!changeValue) + while (!changeValue) { string input; cout << "value to change to?" << endl; getline(cin, input); - if(input == "q"){ + if (input == "q") + { return false; } - if(!lastText.empty() && input.empty()){ + if (!lastText.empty() && input.empty()) + { changeValue = true; } - else if( !input.empty()){ + else if ( !input.empty()) + { lastText = input; changeValue = true; } @@ -140,9 +152,9 @@ bool waitTillChanged(DFHack::API &DF, int creatureToCheck, string changeValue, b DFHack::t_creature testCre; DF.ReadCreature(creatureToCheck,testCre); int tryCount = 0; - if(isName) + if (isName) { - while(testCre.nick_name != changeValue && tryCount <50) + while (testCre.nick_name != changeValue && tryCount <50) { DF.Resume(); w->TypeSpecial(DFHack::WAIT,1,100); @@ -153,7 +165,7 @@ bool waitTillChanged(DFHack::API &DF, int creatureToCheck, string changeValue, b } else { - while(testCre.custom_profession != changeValue && tryCount < 50) + while (testCre.custom_profession != changeValue && tryCount < 50) { DF.Resume(); w->TypeSpecial(DFHack::WAIT,1,100); @@ -162,13 +174,16 @@ bool waitTillChanged(DFHack::API &DF, int creatureToCheck, string changeValue, b tryCount++; } } - if(tryCount >= 50){ + if (tryCount >= 50) + { cerr << "Something went wrong, make sure that DF is at the correct screen"; return false; } DF.Resume(); return true; } + + bool waitTillScreenState(DFHack::API &DF, string screenState,bool EqualTo=true) { DFHack::DFWindow * w = DF.getWindow(); @@ -176,7 +191,7 @@ bool waitTillScreenState(DFHack::API &DF, string screenState,bool EqualTo=true) DF.Suspend(); DF.ReadViewScreen(current); int tryCount = 0; - while(((EqualTo && objecttypes[current.type] != screenState) || (!EqualTo && objecttypes[current.type] == screenState)) && tryCount < 50) + while (((EqualTo && objecttypes[current.type] != screenState) || (!EqualTo && objecttypes[current.type] == screenState)) && tryCount < 50) { DF.Resume(); w->TypeSpecial(DFHack::WAIT,1,100); @@ -184,7 +199,7 @@ bool waitTillScreenState(DFHack::API &DF, string screenState,bool EqualTo=true) DF.ReadViewScreen(current); tryCount++; } - if(tryCount >= 50){ + if (tryCount >= 50) { cerr << "Something went wrong, DF at " << objecttypes[current.type] << endl; return false; } @@ -192,6 +207,7 @@ bool waitTillScreenState(DFHack::API &DF, string screenState,bool EqualTo=true) return true; } + bool waitTillCursorState(DFHack::API &DF, bool On) { DFHack::DFWindow * w = DF.getWindow(); @@ -199,7 +215,7 @@ bool waitTillCursorState(DFHack::API &DF, bool On) int tryCount = 0; DF.Suspend(); bool cursorResult = DF.getCursorCoords(x,y,z); - while(tryCount < 50 && On && !cursorResult || !On && cursorResult) + while (tryCount < 50 && On && !cursorResult || !On && cursorResult) { DF.Resume(); w->TypeSpecial(DFHack::WAIT,1,100); @@ -207,7 +223,7 @@ bool waitTillCursorState(DFHack::API &DF, bool On) DF.Suspend(); cursorResult = DF.getCursorCoords(x,y,z); } - if(tryCount >= 50) + if (tryCount >= 50) { cerr << "Something went wrong, cursor at x: " << x << " y: " << y << " z: " << z << endl; return false; @@ -215,13 +231,15 @@ bool waitTillCursorState(DFHack::API &DF, bool On) DF.Resume(); return true; } + + bool waitTillMenuState(DFHack::API &DF, uint32_t menuState,bool EqualTo=true) { int tryCount = 0; DFHack::DFWindow * w = DF.getWindow(); DF.Suspend(); uint32_t testState = DF.ReadMenuState(); - while(tryCount < 50 && ((EqualTo && menuState != testState) || (!EqualTo && menuState == testState))) + while (tryCount < 50 && ((EqualTo && menuState != testState) || (!EqualTo && menuState == testState))) { DF.Resume(); w->TypeSpecial(DFHack::WAIT,1,100); @@ -229,7 +247,7 @@ bool waitTillMenuState(DFHack::API &DF, uint32_t menuState,bool EqualTo=true) DF.Suspend(); testState = DF.ReadMenuState(); } - if(tryCount >= 50) + if (tryCount >= 50) { cerr << "Something went wrong, menuState: "<TypeSpecial(DFHack::F9); // cancel out of text input in names // DF.TypeSpecial(DFHack::ENTER); // cancel out of text input in hotkeys w->TypeSpecial(DFHack::SPACE); // should move up a level - if(!waitTillScreenState(DF,objecttypes[current.type],false)) return false; // wait until screen changes from current + if (!waitTillScreenState(DF,objecttypes[current.type],false)) return false; // wait until screen changes from current DF.ReadViewScreen(current); } - if(DF.ReadMenuState() != 0){// if menu state != 0 then there is a menu, so escape it - w->TypeSpecial(DFHack::F9);w->TypeSpecial(DFHack::ENTER); // exit out of any text prompts + if (DF.ReadMenuState() != 0) {// if menu state != 0 then there is a menu, so escape it + w->TypeSpecial(DFHack::F9); + w->TypeSpecial(DFHack::ENTER); // exit out of any text prompts w->TypeSpecial(DFHack::SPACE); // go back to base state - if(!waitTillMenuState(DF,0))return false; + if (!waitTillMenuState(DF,0))return false; } DF.Resume(); return true; } + bool setCursorToCreature(DFHack::API &DF) { DFHack::DFWindow * w = DF.getWindow(); @@ -266,125 +288,158 @@ bool setCursorToCreature(DFHack::API &DF) DF.Suspend(); DF.getCursorCoords(x,y,z); DF.Resume(); - if(x == -30000){ + if (x == -30000) { w->TypeStr("v"); - if(!waitTillCursorState(DF,true)) return false; + if (!waitTillCursorState(DF,true)) return false; } - else{ // reset the cursor to be the creature cursor + else { // reset the cursor to be the creature cursor w->TypeSpecial(DFHack::SPACE); - if(!waitTillCursorState(DF,false)) return false; + if (!waitTillCursorState(DF,false)) return false; w->TypeStr("v"); - if(!waitTillCursorState(DF,true)) return false; + if (!waitTillCursorState(DF,true)) return false; } return true; } + + int main (void) -{ +{ DFHack::API DF("Memory.xml"); - if(!DF.Attach()) + if (!DF.Attach()) { cerr << "DF not found" << endl; return 1; } DF.Suspend(); - if(!DF.getClassIDMapping(objecttypes)) + if (!DF.getClassIDMapping(objecttypes)) { cerr << "Can't get type info" << endl; return 1; } - + DFHack::memory_info mem = DF.getMemoryInfo(); - - if(!DF.ReadCreatureMatgloss(creaturestypes)) + + if (!DF.ReadCreatureMatgloss(creaturestypes)) { cerr << "Can't get the creature types." << endl; - return 1; + return 1; } - + DF.InitReadNameTables(names); DF.InitReadCreatures(numCreatures); DF.InitViewAndCursor(); - printDwarves(DF); + DFHack::Process * p = DF.getProcess(); + DFHack::DFWindow * w = DF.getWindow(); + DFHack::t_creature toChange; string changeString,commandString; int eraseAmount; int toChangeNum; bool isName; - DFHack::DFWindow * w = DF.getWindow(); - while(getDwarfSelection(DF,toChange,changeString,commandString,eraseAmount,toChangeNum,isName)) + bool useKeys = true; + string input2; + + // use key event emulation or direct writing? + cout << "\nUse \n1:Key simulation\n2:Direct Writing" << endl; + getline(cin,input2); + if (input2 == "1") + { + useKeys = true; + } + else { + useKeys = false; + } + printDwarves(DF); + + while (getDwarfSelection(DF,toChange,changeString,commandString,eraseAmount,toChangeNum,isName)) { // limit length, DF doesn't accept input after this point - if(changeString.size() > 39) + if (changeString.size() > 39) { changeString.resize(39); } - start: +start: bool completed = false; - if(moveToBaseWindow(DF) && setCursorToCreature(DF)) - { - DF.Suspend(); - DF.setCursorCoords(toChange.x, toChange.y,toChange.z); - vector underCursor; - while(!DF.getCurrentCursorCreatures(underCursor)) + if (useKeys) { + if (moveToBaseWindow(DF) && setCursorToCreature(DF)) { - DF.Resume(); - w->TypeSpecial(DFHack::WAIT,1,100); DF.Suspend(); DF.setCursorCoords(toChange.x, toChange.y,toChange.z); - DF.ReadCreature(toChangeNum,toChange); - } - //CurrentCursorCreatures gives the creatures in the order that you see them with the 'k' cursor. - //The 'v' cursor displays them in the order of last, then first,second,third and so on - //Pretty weird, but it works - //The only place that seems to display which creature is currently selected is on the stack, whose location is likely not static, so not usable - if(underCursor[underCursor.size()-1] != toChange.origin) - { - for(int i = 0;i underCursor; + while (!DF.getCurrentCursorCreatures(underCursor)) { DF.Resume(); - w->TypeStr("v",100); - if(underCursor[i] == toChange.origin) + w->TypeSpecial(DFHack::WAIT,1,100); + DF.Suspend(); + DF.setCursorCoords(toChange.x, toChange.y,toChange.z); + DF.ReadCreature(toChangeNum,toChange); + } + //CurrentCursorCreatures gives the creatures in the order that you see them with the 'k' cursor. + //The 'v' cursor displays them in the order of last, then first,second,third and so on + //Pretty weird, but it works + //The only place that seems to display which creature is currently selected is on the stack, whose location is likely not static, so not usable + if (underCursor[underCursor.size()-1] != toChange.origin) + { + for (int i = 0;iTypeStr("v",100); + if (underCursor[i] == toChange.origin) + { + break; + } } } - } - DF.Resume(); - w->TypeStr(commandString.c_str()); - if(waitTillScreenState(DF,"viewscreen_customize_unit")) - { DF.Resume(); - w->TypeSpecial(DFHack::BACK_SPACE,eraseAmount); - if(waitTillChanged(DF,toChangeNum,"",isName)) + w->TypeStr(commandString.c_str()); + if (waitTillScreenState(DF,"viewscreen_customize_unit")) { DF.Resume(); - w->TypeStr(changeString.c_str()); - if(waitTillChanged(DF,toChangeNum,changeString,isName)) + w->TypeSpecial(DFHack::BACK_SPACE,eraseAmount); + if (waitTillChanged(DF,toChangeNum,"",isName)) { DF.Resume(); - w->TypeSpecial(DFHack::ENTER); - w->TypeSpecial(DFHack::SPACE); // should take you to unit screen if everything worked - if(waitTillScreenState(DF,"viewscreen_unit")) + w->TypeStr(changeString.c_str()); + if (waitTillChanged(DF,toChangeNum,changeString,isName)) { DF.Resume(); - w->TypeSpecial(DFHack::SPACE); - if(waitTillScreenState(DF,"viewscreen_dwarfmode")) + w->TypeSpecial(DFHack::ENTER); + w->TypeSpecial(DFHack::SPACE); // should take you to unit screen if everything worked + if (waitTillScreenState(DF,"viewscreen_unit")) { DF.Resume(); w->TypeSpecial(DFHack::SPACE); - if(waitTillCursorState(DF,false)) + if (waitTillScreenState(DF,"viewscreen_dwarfmode")) { - completed = true; + DF.Resume(); + w->TypeSpecial(DFHack::SPACE); + if (waitTillCursorState(DF,false)) + { + completed = true; + } } } } } } } + if (!completed) { + cerr << "Something went wrong, please reset DF to its original state, then press any key to continue" << endl; + goto start; + } } - if(!completed){ - cerr << "Something went wrong, please reset DF to its original state, then press any key to continue" << endl; - goto start; + else + { + // will only work with the shm probably should check for it, but I don't know how, + // I have the writeString function do nothing for normal mode + if (commandString == "pzyn") // change nickname + { + p->writeSTLString(toChange.origin+mem.getOffset("creature_nick_name"),changeString); + } + else + { + p->writeSTLString(toChange.origin+mem.getOffset("creature_custom_profession"),changeString); + } } DF.Suspend(); printDwarves(DF); diff --git a/tools/dfbauxite.cpp b/tools/dfbauxite.cpp index 79bacaeba..a08c01441 100644 --- a/tools/dfbauxite.cpp +++ b/tools/dfbauxite.cpp @@ -91,7 +91,7 @@ int main () return EXIT_FAILURE; } - items_vector = new DFHack::DfVector (proc->getDataModel()->readVector (items, 4)); + items_vector = new DFHack::DfVector (proc->readVector (items, 4)); for(uint32_t i = 0; i < items_vector->getSize(); i++) { // get pointer to object From 054fec0afc43146028a9154c37e2e0eb8f56f302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Tue, 16 Feb 2010 07:21:38 +0100 Subject: [PATCH 2/3] Updated README and COMPILE, split tools into tools and examples. Tools are now useful, and with a working user interaction where applicable. Examples are a collection of benchmarks, tests and simple unfinished ideas. --- CMakeLists.txt | 1 + COMPILE | 64 +++++++-- README | 126 +++++++++--------- examples/CMakeLists.txt | 63 +++++++++ {tools => examples}/attachtest.cpp | 0 {tools => examples}/buildingsdump.cpp | 0 {tools => examples}/creaturedump.cpp | 0 {tools => examples}/dfitemdump.cpp | 0 {tools => examples}/digger.cpp | 0 {tools => examples}/expbench.cpp | 0 {tools => examples}/materialtest.cpp | 0 {tools => examples}/position.cpp | 0 .../renamer.cpp | 0 {tools => examples}/suspendtest.cpp | 0 library/DFHackAPI.cpp | 2 +- tools/CMakeLists.txt | 62 +-------- 16 files changed, 190 insertions(+), 128 deletions(-) create mode 100644 examples/CMakeLists.txt rename {tools => examples}/attachtest.cpp (100%) rename {tools => examples}/buildingsdump.cpp (100%) rename {tools => examples}/creaturedump.cpp (100%) rename {tools => examples}/dfitemdump.cpp (100%) rename {tools => examples}/digger.cpp (100%) rename {tools => examples}/expbench.cpp (100%) rename {tools => examples}/materialtest.cpp (100%) rename {tools => examples}/position.cpp (100%) rename tools/customCreatureNameProf.cpp => examples/renamer.cpp (100%) rename {tools => examples}/suspendtest.cpp (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index ea6b29e8a..81a20e745 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -29,4 +29,5 @@ include_directories (${CMAKE_SOURCE_DIR}/shmserver/) add_subdirectory (library) add_subdirectory (tools) +add_subdirectory (examples) add_subdirectory (shmserver) \ No newline at end of file diff --git a/COMPILE b/COMPILE index 1cdbee429..9e1a2fae0 100644 --- a/COMPILE +++ b/COMPILE @@ -4,6 +4,7 @@ Here's how you build dfhack! First, there is one dependency, regardless of the OS you use: cmake - it's the build system + Building on Linux: -------------------- @@ -30,8 +31,7 @@ library to $CMAKE_INSTALL_PREFIX/lib executables to $CMAKE_INSTALL_PREFIX/bin The Memory.xml file to /usr/share/dfhack -A special library for adding a shared memory interface to DF is also compiled (libdfconnect.so) -It can be put into DF/libs and preloaded using LD_PRELOAD +See the section on the shared memory hook library (SHM). Building on Windows: -------------------- @@ -51,9 +51,6 @@ You'll have to add C:\MinGW\ to your PATH variable. cmake .. -G"MinGW Makefiles" -DCMAKE_BUILD_TYPE:string=Release mingw32-make -A special library for adding a shared memory interface to DF is also compiled (SDL.dll) -You can use it with DF by renaming the original SDL.dll to SDLreal.dll and dropping in the newly compiled library. - * Using MSVC open up cmd and navigate to the dfhack\build folder, run cmake: @@ -64,22 +61,73 @@ This will generate MSVC solution and project files. Note that: you are working i Files added to projects will end up there! (and that's wrong). Any changes to the build system should be done by changing cmake configs and running cmake on them! -A special library for adding a shared memory interface to DF is also compiled (SDL.dll) -You can use it with DF by renaming the original SDL.dll to SDLreal.dll and dropping in the newly compiled library. - * Using some other compiler: I'm afraid you are on your own. dfhack wasn't tested with any other compiler. Try using a different cmake generator that's intended for your tools. + +Building the shared memory hook library (SHM) +--------------------------------------------- + +Unlike the rest of DFHack, The SHM needs special treatment when it comes to compilation. +Because it shares the memory space with DF itself, it has to be built with the same tools as DF +and use the same C and C++/STL libraries. + +For DF 40d15 - 40d17 on Windows, use MSVC 2008. You can get the Express edition for free from Microsoft. + +Windows dependencies can be determined by a tool like depends.exe (google it). Both the fake SDL.dll +and DF have to use the same version of the C runtime (MSVCRT). +The SHM can't be debugged, because debug builds in MSVC use a different CRT! + +Linux dependencies can be determined by setting the LD_DEBUG variable and running ./df: +export LD_DEBUG=versions +./df + +Example of (a part of a) relevant output from a working SHM installation: + 24472: checking for version `GLIBC_2.0' in file /opt/lib32/lib/libpthread.so.0 [0] required by file ./dwarfort.exe [0] + 24472: checking for version `GCC_3.0' in file ./libs/libgcc_s.so.1 [0] required by file ./dwarfort.exe [0] + 24472: checking for version `GLIBC_2.0' in file ./libs/libgcc_s.so.1 [0] required by file ./dwarfort.exe [0] + 24472: checking for version `GLIBC_2.1' in file /opt/lib32/lib/libm.so.6 [0] required by file ./dwarfort.exe [0] + 24472: checking for version `GLIBC_2.0' in file /opt/lib32/lib/libm.so.6 [0] required by file ./dwarfort.exe [0] + 24472: checking for version `GLIBC_2.1.3' in file /opt/lib32/lib/libc.so.6 [0] required by file ./dwarfort.exe [0] + 24472: checking for version `GLIBC_2.3.4' in file /opt/lib32/lib/libc.so.6 [0] required by file ./dwarfort.exe [0] + 24472: checking for version `GLIBC_2.4' in file /opt/lib32/lib/libc.so.6 [0] required by file ./dwarfort.exe [0] + 24472: checking for version `GLIBC_2.0' in file /opt/lib32/lib/libc.so.6 [0] required by file ./dwarfort.exe [0] + 24472: checking for version `GLIBCXX_3.4.9' in file ./libs/libstdc++.so.6 [0] required by file ./dwarfort.exe [0] + 24472: checking for version `CXXABI_1.3' in file ./libs/libstdc++.so.6 [0] required by file ./dwarfort.exe [0] + 24472: checking for version `GLIBCXX_3.4' in file ./libs/libstdc++.so.6 [0] required by file ./dwarfort.exe [0] + 24472: checking for version `CXXABI_1.3' in file ./libs/libstdc++.so.6 [0] required by file ./libs/libdfconnect.so [0] + 24472: checking for version `GLIBCXX_3.4' in file ./libs/libstdc++.so.6 [0] required by file ./libs/libdfconnect.so [0] + 24472: checking for version `GLIBC_2.1.3' in file /opt/lib32/lib/libc.so.6 [0] required by file ./libs/libdfconnect.so [0] + 24472: checking for version `GLIBC_2.2' in file /opt/lib32/lib/libc.so.6 [0] required by file ./libs/libdfconnect.so [0] + 24472: checking for version `GLIBC_2.3.4' in file /opt/lib32/lib/libc.so.6 [0] required by file ./libs/libdfconnect.so [0] + 24472: checking for version `GLIBC_2.0' in file /opt/lib32/lib/libc.so.6 [0] required by file ./libs/libdfconnect.so [0] + +libdfconnect is the SHM. Both are compiled against the same C++ library and share the same CXXABI version. + +Precompiled SHM libraries are provided in binary releases. + +* Checking strings support + +Strings are one of the important C++ types and a great indicator that the SHM works. Tools like Dwarf Therapist depend +on string support. Reading of strings can be checked by running any of the tools that deal with materials. + +String writing is best tested with a fresh throw-away fort and dfrenamer. Embark, give one dwarf a very long name using dfrenamer +and save/exit. If DF crashes during the save sequence, your SHM is not compatible with DF and the throw-away fort is lost. + + Build targets ------------- + dfhack has a few build targets. If you're only after the library run 'make dfhack'. 'make' will build everything. 'make expbench' will build the expbench throughput testing program and the library. + Build types ----------- + cmake allows you to pick a build type by changing this variable: CMAKE_BUILD_TYPE cmake .. -DCMAKE_BUILD_TYPE:string=BUILD_TYPE diff --git a/README b/README index f8ae168a4..15ef9b694 100644 --- a/README +++ b/README @@ -1,66 +1,84 @@ Introduction ------------ -DFHack is a Dwarf Fortress memory access library and a set of basic tools using this library. -The library is a work in progress, so things might change as more tools are written for it. -It is an attempt to unite the various ways tools access DF memory and allow for easier development -of new tools. +DFHack is a Dwarf Fortress memory access library and a set of basic tools using +this library. The library is a work in progress, so things might change as more +tools are written for it. + +It is an attempt to unite the various ways tools access DF memory and allow for +easier development of new tools. + Getting DFHack ---------------- + You can get the code at DFHack's sourceforge site: https://sourceforge.net/projects/dfhack/ * subversion access: svn co https://dfhack.svn.sourceforge.net/svnroot/dfhack/trunk dfhack +It is also available on github: + http://github.com/peterix/dfhack + + Compatibility ------------- + DFHack works on Windows XP, Vista, 7 or any modern Linux distribution. -Windows 2000 is currently *not supported* due to missing OS functionality. If you know how -to easily suspend processes, you can fix it :) +Windows 2000 is currently *not supported* due to missing OS functionality. +If you know how to easily suspend processes, you can fix it :) OSX is also not supported due to lack of developers with a Mac. Currently supported Dwarf Fortress versions: * Windows 40d - 40d9 - 40d16 + 40d9 - 40d17 * Linux - 40d9 - 40d16 + 40d9 - 40d17 + Using the library ----------------- -The library is compilable under Linux with GCC and under Windows with MinGW32 and MSVC compilers. -It is using the cmake build system. See COMPILE for details. -DFHack is using the zlib/libpng license. This makes it easy to link to it, use it in-source -or add your own extensions. Contributing back to the dfhack repository is welcome and the right -thing to do :) +The library is compilable under Linux with GCC and under Windows with MinGW32 +and MSVC compilers. It is using the cmake build system. See COMPILE for details. + +DFHack is using the zlib/libpng license. This makes it easy to link to it, use +it in-source or add your own extensions. Contributing back to the dfhack +repository is welcome and the right thing to do :) + +At the time of writing there's no API reference or documentation. The code does +have a lot of comments though. -At the time of writing there's no API reference or documentation. The code does have a lot -of comments though. Using DFHack Tools ------------------ -The project comes with a special extra library you should add to your DF installation. -It's used to boost the transfer speed between DF and DFHack, and provide data consistency -and synchronization. DFHack will work without the library, but at suboptimal speeds -and the consistency of data written back to DF is questionable. -!!!!Please not that on Windows this currently only works with DF 40d15 and 40d16!!!! -On Linux, it works with the whole range of supported DF versions. +The project comes with a special extra library you should add to your DF +installation. It's used to boost the transfer speed between DF and DFHack, and +provide data consistency and synchronization. DFHack will work without the +library, but at suboptimal speeds and the consistency of data written back +to DF is questionable. + +!!! on Windows this currently only works with DF 40d15, 40d16 and 40d17 !!! + On Linux, it works with the whole range of supported DF versions. + +!!! use the pre-compiled library intended for your version of DF !!! + You can find them in the 'precompiled' folder. ** Installing on Windows - - Open your DF folder, locate SDL.dll and rename it to SDLreal.dll (making a backup of it is a good idea) - - Copy the DFHack SDL.dll into your DF folder. - - Restart DF if it is already running + - Open your DF folder, locate SDL.dll and rename it to SDLreal.dll (making + a backup of it is a good idea) + - Copy the right DFHack SDL.dll into your DF folder. + - Restart DF if it is running ** Unistalling on Windows - Open your DF folder, locate SDL.dll and delete it - - Rename SDLreal.dll to SDl.dll + - Rename SDLreal.dll to SDL.dll - Restart DF if it is running @@ -94,56 +112,40 @@ export LD_PRELOAD="./libs/libdfconnect.so" # Hack DF! - Delete libdfconnect.so and the dfhacked startup script - Go back to using the df startup script + Tools ----- -All the DFHack tools are terminal programs. This might seem strange to Windows users, -but these are meant mostly as examples for developers. Still, they can be useful and -are cross-platform just like the library itself. - -If the tool writes back to DF's memory, make sure you are using the shared memory interface -mentioned in the previous section! - -* expbench - just exports the map 1000 times over. Meant to test how fast the - DFHack<->DF interface is. This can take 3-30+ seconds, depending on - your system and the size of the map. -* reveal - plain old reveal tool. Demonstrates writing map data back to DF. +All the DFHack tools are terminal programs. This might seem strange to Windows +users, but these are meant mostly as examples for developers. Still, they can +be useful and are cross-platform just like the library itself. -* prospector - scans the map for minerals. by default it only scans only visible veins. - You can make it show hidden things with '-a' and base rock and soil layers - with '-b'. These can be combined ('-ab') +If the tool writes back to DF's memory, make sure you are using the shared +memory interface mentioned in the previous section! -* cleanmap - cleans mud, vomit, snow and all kinds of bloody mess from the map. It will - clean your irrigated farms too, so consider yourself warned. +* reveal - plain old reveal tool. It reveals all the map blocks already + initialized by DF. -* dfattachtest - Benchmark for the Attach, Detach, Suspend and Resume functions +* prospector - scans the map for minerals. by default it only scans only visible + veins. You can make it show hidden things with '-a' and base rock + and soil layers with '-b'. These can be combined ('-ab') -* dfbuildingsdump - exports some basic data about buildings of a given type on the game map +* cleanmap - cleans mud, vomit, snow and all kinds of mess from the map. + It will clean your irrigated farms too, so consider yourself + warned. -* dfcreaturedump - exports some basic data about all the dwarves +* dfincremental - incremental search utility. -* dfcustomCreatureNameProf - allws you to change nicknames and custom professions of your dwarves +* bauxite - converts all mechanisms into bauxite mechanisms. -* dfincremental - incremental search utility - linux only right now - -* dfitemdump - lists items under the current DF cursor (40d16 only) - -* dfmaterialtest - tries to load all the material types from DF, shows the first entry of each type - -* dfposition - shows you the current view coordinates and window size in tiles - (may not be implemented for older DF versions) - -* dfsuspend - tests the behavior of Suspend and Resume functions +* itemdesignator - Allows mass-designating items by type and material - dump, + forbid, melt and set on fire ;) + Memory offset definitions ------------------------- -The file with memory offset definitions used by dfhack can be found in the output folder. -It uses XML as the base format. - -It allows entry versioning and basing newer entries on older ones. These newer versions -only have to specify the values that are different or a single 'rebase' value that is then -used as an offset for all the addresses or object identifiers. -This makes it possible to track the small d## releases pretty quickly. +The file with memory offset definitions used by dfhack can be found in the +output folder. ~ EOF ~ diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 000000000..e7c21d9fb --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,63 @@ +# don't use this file directly. use the one in the root folder of the project + +# this is required to ensure we use the right configuration for the system. +IF(UNIX) +add_definitions(-DLINUX_BUILD) +ENDIF(UNIX) + +# attachtest - 100x attach/detach, suspend/resume +ADD_EXECUTABLE(dfattachtest attachtest.cpp) +TARGET_LINK_LIBRARIES(dfattachtest dfhack) + +# a benchmark program, reads the map 1000x +ADD_EXECUTABLE(dfexpbench expbench.cpp) +TARGET_LINK_LIBRARIES(dfexpbench dfhack) + +# creaturedump - basic creature dump - a test of the creature related exports +ADD_EXECUTABLE(dfcreaturedump creaturedump.cpp) +TARGET_LINK_LIBRARIES(dfcreaturedump dfhack) + +# buildingsdump - dump buildings and their raw data filtered by type +ADD_EXECUTABLE(dfbuildingsdump buildingsdump.cpp) +TARGET_LINK_LIBRARIES(dfbuildingsdump dfhack) + +# materialtest - just list the first material of each type +ADD_EXECUTABLE(dfmaterialtest materialtest.cpp) +TARGET_LINK_LIBRARIES(dfmaterialtest dfhack) + +# position - check the DF window and cursor parameters +ADD_EXECUTABLE(dfposition position.cpp) +TARGET_LINK_LIBRARIES(dfposition dfhack) + +# suspendtest - test if suspend works. df should stop responding when suspended by dfhack +ADD_EXECUTABLE(dfsuspend suspendtest.cpp) +TARGET_LINK_LIBRARIES(dfsuspend dfhack) + +# itemdump - dump the item under the cursor +ADD_EXECUTABLE(dfitemdump dfitemdump.cpp) +TARGET_LINK_LIBRARIES(dfitemdump dfhack) + +# digger - designate for digging by tile class +# Author: mizipzor +ADD_EXECUTABLE(dfdigger digger.cpp) +TARGET_LINK_LIBRARIES(dfdigger dfhack) + +# renamer - change the custom names and professions of creatures, sends keys to df directly +ADD_EXECUTABLE(dfrenamer renamer.cpp) +TARGET_LINK_LIBRARIES(dfrenamer dfhack) + +IF(UNIX) +install(TARGETS +dfattachtest +dfbuildingsdump +dfcreaturedump +dfitemdump +dfdigger +dfexpbench +dfmaterialtest +dfposition +dfrenamer +dfsuspend +RUNTIME DESTINATION bin +) +ENDIF(UNIX) \ No newline at end of file diff --git a/tools/attachtest.cpp b/examples/attachtest.cpp similarity index 100% rename from tools/attachtest.cpp rename to examples/attachtest.cpp diff --git a/tools/buildingsdump.cpp b/examples/buildingsdump.cpp similarity index 100% rename from tools/buildingsdump.cpp rename to examples/buildingsdump.cpp diff --git a/tools/creaturedump.cpp b/examples/creaturedump.cpp similarity index 100% rename from tools/creaturedump.cpp rename to examples/creaturedump.cpp diff --git a/tools/dfitemdump.cpp b/examples/dfitemdump.cpp similarity index 100% rename from tools/dfitemdump.cpp rename to examples/dfitemdump.cpp diff --git a/tools/digger.cpp b/examples/digger.cpp similarity index 100% rename from tools/digger.cpp rename to examples/digger.cpp diff --git a/tools/expbench.cpp b/examples/expbench.cpp similarity index 100% rename from tools/expbench.cpp rename to examples/expbench.cpp diff --git a/tools/materialtest.cpp b/examples/materialtest.cpp similarity index 100% rename from tools/materialtest.cpp rename to examples/materialtest.cpp diff --git a/tools/position.cpp b/examples/position.cpp similarity index 100% rename from tools/position.cpp rename to examples/position.cpp diff --git a/tools/customCreatureNameProf.cpp b/examples/renamer.cpp similarity index 100% rename from tools/customCreatureNameProf.cpp rename to examples/renamer.cpp diff --git a/tools/suspendtest.cpp b/examples/suspendtest.cpp similarity index 100% rename from tools/suspendtest.cpp rename to examples/suspendtest.cpp diff --git a/library/DFHackAPI.cpp b/library/DFHackAPI.cpp index bb53bb904..4047fc35b 100644 --- a/library/DFHackAPI.cpp +++ b/library/DFHackAPI.cpp @@ -1301,7 +1301,7 @@ bool API::InitReadItems(uint32_t & numitems) } bool API::ReadItem (const uint32_t &index, t_item & item) { - assert (d->buildingsInited && d->itemsInited); //should change to the generic init rather than buildings + assert (d->itemsInited); //should change to the generic init rather than buildings t_item_df40d item_40d; // read pointer from vector at position diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt index d99127df4..6a6fa9f95 100644 --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -5,10 +5,6 @@ IF(UNIX) add_definitions(-DLINUX_BUILD) ENDIF(UNIX) -# a benchmark program, reads the map 1000x -ADD_EXECUTABLE(dfexpbench expbench.cpp) -TARGET_LINK_LIBRARIES(dfexpbench dfhack) - # a reveal clone ADD_EXECUTABLE(dfreveal reveal.cpp) TARGET_LINK_LIBRARIES(dfreveal dfhack) @@ -21,75 +17,27 @@ TARGET_LINK_LIBRARIES(dfprospector dfhack) ADD_EXECUTABLE(dfcleanmap cleanmap.cpp) TARGET_LINK_LIBRARIES(dfcleanmap dfhack) -# creaturedump - basic creature dump - a test of the creature related exports -ADD_EXECUTABLE(dfcreaturedump creaturedump.cpp) -TARGET_LINK_LIBRARIES(dfcreaturedump dfhack) - -# buildingsdump - dump buildings and their raw data filtered by type -ADD_EXECUTABLE(dfbuildingsdump buildingsdump.cpp) -TARGET_LINK_LIBRARIES(dfbuildingsdump dfhack) - - -# attachtest - 100x attach/detach, 100x reads, 100x writes -ADD_EXECUTABLE(dfattachtest attachtest.cpp) -TARGET_LINK_LIBRARIES(dfattachtest dfhack) - -# materialtest - just list the first material of each type -ADD_EXECUTABLE(dfmaterialtest materialtest.cpp) -TARGET_LINK_LIBRARIES(dfmaterialtest dfhack) - -# materialtest - just list the first material of each type -ADD_EXECUTABLE(dfposition position.cpp) -TARGET_LINK_LIBRARIES(dfposition dfhack) - -#currently only stable on linux -IF(UNIX) - # incremental - incremental memory search tool, a foreshadowing of the future direction of dfhack - ADD_EXECUTABLE(dfincremental incrementalsearch.cpp) - TARGET_LINK_LIBRARIES(dfincremental dfhack) -ENDIF(UNIX) - -# suspendtest - test if suspend works. df should stop responding when suspended by dfhack -ADD_EXECUTABLE(dfsuspend suspendtest.cpp) -TARGET_LINK_LIBRARIES(dfsuspend dfhack) - -# itemdump - dump the item under the cursor -ADD_EXECUTABLE(dfitemdump dfitemdump.cpp) -TARGET_LINK_LIBRARIES(dfitemdump dfhack) +# incrementalsearch - a bit like cheat engine, only DF-specific and very basic +ADD_EXECUTABLE(dfincremental incrementalsearch.cpp) +TARGET_LINK_LIBRARIES(dfincremental dfhack) # bauxite - turn all mechanisms into bauxite mechanisms # Author: Alex Legg ADD_EXECUTABLE(dfbauxite dfbauxite.cpp) TARGET_LINK_LIBRARIES(dfbauxite dfhack) -# digger - designate for digging by tile class -# Author: mizipzor -ADD_EXECUTABLE(dfdigger digger.cpp) -TARGET_LINK_LIBRARIES(dfdigger dfhack) - # itemdesignator - change some item designations (dump, forbid, on-fire) for all items of a given type and material ADD_EXECUTABLE(dfitemdesignator itemdesignator.cpp) TARGET_LINK_LIBRARIES(dfitemdesignator dfhack) -# customCtreatureNameProf - change the custom names and professions of creatures, sends keys to df directly -ADD_EXECUTABLE(dfcustomCreatureNameProf customCreatureNameProf.cpp) -TARGET_LINK_LIBRARIES(dfcustomCreatureNameProf dfhack) - IF(UNIX) install(TARGETS -dfexpbench -dfreveal dfreveal dfprospector dfcleanmap -dfcreaturedump -dfattachtest -dfmaterialtest -dfbuildingsdump -dfposition dfincremental -dfitemdump -dfsuspend +dfbauxite +dfitemdesignator RUNTIME DESTINATION bin ) ENDIF(UNIX) \ No newline at end of file From 518dd0be280c7c9a536de7d8cb827c8b8146bacd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Mr=C3=A1zek?= Date: Tue, 16 Feb 2010 07:35:48 +0100 Subject: [PATCH 3/3] Added current precompiled SHM libs --- precompiled/SDL.dll | Bin 0 -> 13824 bytes precompiled/libdfconnect.so | Bin 0 -> 12403 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 precompiled/SDL.dll create mode 100755 precompiled/libdfconnect.so diff --git a/precompiled/SDL.dll b/precompiled/SDL.dll new file mode 100644 index 0000000000000000000000000000000000000000..a30235e161d462ac18581e85cfbcfdf07c22d189 GIT binary patch literal 13824 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