dfhack/library/DFHackAPI.cpp

694 lines
22 KiB
C++

2009-09-13 18:02:46 -06:00
/*
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 BUILD_DFHACK_LIB
# define BUILD_DFHACK_LIB
#endif
2009-09-13 18:02:46 -06:00
#include "DFCommon.h"
#include "DFVector.h"
#include "DFHackAPI.h"
#include "DFMemInfo.h"
2009-10-21 18:18:49 -06:00
#if defined(_MSC_VER) && _MSC_VER >= 1400
# define fill_char_buf(buf, str) strcpy_s((buf), sizeof(buf) / sizeof((buf)[0]), (str).c_str())
#else
# define fill_char_buf(buf, str) strncpy((buf), (str).c_str(), sizeof(buf) / sizeof((buf)[0]))
#endif
2009-09-13 18:02:46 -06:00
/* TODO: Test these
matgloss other than stone/soil
*/
DFHACKAPI DFHackAPI *CreateDFHackAPI0(const char *path_to_xml)
{
return new DFHackAPIImpl(path_to_xml);
}
2009-09-13 18:02:46 -06:00
// TODO: templating for vectors, simple copy constructor for stl vectors
// TODO: encapsulate access to multidimensional arrays.
DFHackAPIImpl::DFHackAPIImpl(const string path_to_xml)
: block(NULL)
, pm(NULL), p(NULL), dm(NULL), offset_descriptor(NULL)
, p_cons(NULL), p_bld(NULL), p_veg(NULL)
2009-09-13 18:02:46 -06:00
{
xml = path_to_xml;
constructionsInited = false;
2009-10-22 19:39:19 -06:00
creaturesInited = false;
2009-09-13 18:02:46 -06:00
buildingsInited = false;
vegetationInited = false;
pm = NULL;
}
/*-----------------------------------*
* Init the mapblock pointer array *
*-----------------------------------*/
bool DFHackAPIImpl::InitMap()
2009-09-13 18:02:46 -06:00
{
uint32_t map_loc, // location of the X array
temp_loc, // block location
temp_locx, // iterator for the X array
temp_locy, // iterator for the Y array
temp_locz; // iterator for the Z array
uint32_t map_offset = offset_descriptor->getAddress("map_data");
uint32_t x_count_offset = offset_descriptor->getAddress("x_count");
uint32_t y_count_offset = offset_descriptor->getAddress("y_count");
uint32_t z_count_offset = offset_descriptor->getAddress("z_count");
// get the offsets once here
tile_type_offset = offset_descriptor->getOffset("type");
designation_offset = offset_descriptor->getOffset("designation");
occupancy_offset = offset_descriptor->getOffset("occupancy");
// get the map pointer
map_loc = MreadDWord(map_offset);
if (!map_loc)
{
// bad stuffz happend
return false;
}
// get the size
x_block_count = MreadDWord(x_count_offset);
y_block_count = MreadDWord(y_count_offset);
z_block_count = MreadDWord(z_count_offset);
// alloc array for pinters to all blocks
block = new uint32_t[x_block_count*y_block_count*z_block_count];
//read the memory from the map blocks - x -> map slice
for(uint32_t x = 0; x < x_block_count; x++)
{
temp_locx = map_loc + ( 4 * x );
temp_locy = MreadDWord(temp_locx);
// y -> map column
for(uint32_t y = 0; y < y_block_count; y++)
{
temp_locz = MreadDWord(temp_locy);
temp_locy += 4;
// z -> map block (16x16)
for(uint32_t z = 0; z < z_block_count; z++)
{
temp_loc = MreadDWord(temp_locz);
temp_locz += 4;
block[x*y_block_count*z_block_count + y*z_block_count + z] = temp_loc;
}
}
}
return true;
}
bool DFHackAPIImpl::DestroyMap()
{
if (block != NULL)
{
delete [] block;
block = NULL;
}
return true;
}
bool DFHackAPIImpl::isValidBlock(uint32_t x, uint32_t y, uint32_t z)
2009-09-13 18:02:46 -06:00
{
return block[x*y_block_count*z_block_count + y*z_block_count + z] != NULL;
}
// 256 * sizeof(uint16_t)
bool DFHackAPIImpl::ReadTileTypes(uint32_t x, uint32_t y, uint32_t z, uint16_t *buffer)
2009-09-13 18:02:46 -06:00
{
uint32_t addr = block[x*y_block_count*z_block_count + y*z_block_count + z];
if (addr!=NULL)
{
Mread(addr+tile_type_offset, 256 * sizeof(uint16_t), (uint8_t *)buffer);
return true;
}
return false;
}
// 256 * sizeof(uint32_t)
bool DFHackAPIImpl::ReadDesignations(uint32_t x, uint32_t y, uint32_t z, uint32_t *buffer)
2009-09-13 18:02:46 -06:00
{
uint32_t addr = block[x*y_block_count*z_block_count + y*z_block_count + z];
if (addr!=NULL)
{
Mread(addr+designation_offset, 256 * sizeof(uint32_t), (uint8_t *)buffer);
return true;
}
return false;
}
// 256 * sizeof(uint32_t)
bool DFHackAPIImpl::ReadOccupancy(uint32_t x, uint32_t y, uint32_t z, uint32_t *buffer)
2009-09-13 18:02:46 -06:00
{
uint32_t addr = block[x*y_block_count*z_block_count + y*z_block_count + z];
if (addr!=NULL)
{
Mread(addr+occupancy_offset, 256 * sizeof(uint32_t), (uint8_t *)buffer);
return true;
}
return false;
}
// 256 * sizeof(uint16_t)
bool DFHackAPIImpl::WriteTileTypes(uint32_t x, uint32_t y, uint32_t z, uint16_t *buffer)
2009-09-13 18:02:46 -06:00
{
uint32_t addr = block[x*y_block_count*z_block_count + y*z_block_count + z];
if (addr!=NULL)
{
Mwrite(addr+tile_type_offset, 256 * sizeof(uint16_t), (uint8_t *)buffer);
return true;
}
return false;
}
// 256 * sizeof(uint32_t)
bool DFHackAPIImpl::WriteDesignations(uint32_t x, uint32_t y, uint32_t z, uint32_t *buffer)
2009-09-13 18:02:46 -06:00
{
uint32_t addr = block[x*y_block_count*z_block_count + y*z_block_count + z];
if (addr!=NULL)
{
Mwrite(addr+designation_offset, 256 * sizeof(uint32_t), (uint8_t *)buffer);
return true;
}
return false;
}
// 256 * sizeof(uint32_t)
bool DFHackAPIImpl::WriteOccupancy(uint32_t x, uint32_t y, uint32_t z, uint32_t *buffer)
2009-09-13 18:02:46 -06:00
{
uint32_t addr = block[x*y_block_count*z_block_count + y*z_block_count + z];
if (addr!=NULL)
{
Mwrite(addr+occupancy_offset, 256 * sizeof(uint32_t), (uint8_t *)buffer);
return true;
}
return false;
}
//16 of them? IDK... there's probably just 7. Reading more doesn't cause errors as it's an array nested inside a block
// 16 * sizeof(uint8_t)
bool DFHackAPIImpl::ReadRegionOffsets(uint32_t x, uint32_t y, uint32_t z, uint8_t *buffer)
2009-09-13 18:02:46 -06:00
{
uint32_t biome_stuffs = offset_descriptor->getOffset("biome_stuffs");
uint32_t addr = block[x*y_block_count*z_block_count + y*z_block_count + z];
if (addr!=NULL)
{
Mread(addr+biome_stuffs, 16 * sizeof(uint8_t), buffer);
return true;
}
return false;
}
// veins of a block, expects empty vein vector
bool DFHackAPIImpl::ReadVeins(uint32_t x, uint32_t y, uint32_t z, vector <t_vein> & veins)
2009-09-13 18:02:46 -06:00
{
uint32_t addr = block[x*y_block_count*z_block_count + y*z_block_count + z];
int veinvector = offset_descriptor->getOffset("v_vein");
int veinsize = offset_descriptor->getHexValue("v_vein_size");
veins.clear();
if(addr!=NULL && veinvector && veinsize)
{
assert(sizeof(t_vein) == veinsize);
// 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 = dm->readVector(addr + veinvector, 4);
// read all veins
for (uint32_t i = 0; i< p_veins.getSize();i++)
{
t_vein v;
uint32_t temp;
// read the vein pointer from the vector
p_veins.read((uint32_t)i,(uint8_t *)&temp);
// read the vein data (dereference pointer)
Mread(temp, veinsize, (uint8_t *)&v);
// store it in the vector
veins.push_back(v);
}
return true;
}
return false;
}
// getter for map size
void DFHackAPIImpl::getSize(uint32_t& x, uint32_t& y, uint32_t& z)
2009-09-13 18:02:46 -06:00
{
x = x_block_count;
y = y_block_count;
z = z_block_count;
}
bool DFHackAPIImpl::ReadWoodMatgloss(vector<t_matgloss> & woods)
2009-09-18 05:31:56 -06:00
{
int matgloss_address = offset_descriptor->getAddress("matgloss");
// TODO: find flag for autumnal coloring?
DfVector p_matgloss = dm->readVector(matgloss_address, 4);
woods.clear();
t_matgloss mat;
// TODO: use brown?
mat.fore = 7;
mat.back = 0;
mat.bright = 0;
for (uint32_t i = 0; i< p_matgloss.getSize();i++)
{
uint32_t temp;
// read the matgloss pointer from the vector into temp
p_matgloss.read((uint32_t)i,(uint8_t *)&temp);
// read the string pointed at by
2009-10-21 18:18:49 -06:00
fill_char_buf(mat.id, dm->readSTLString(temp)); // reads a C string given an address
2009-09-18 05:31:56 -06:00
woods.push_back(mat);
}
return true;
}
bool DFHackAPIImpl::ReadStoneMatgloss(vector<t_matgloss> & stones)
2009-09-13 18:02:46 -06:00
{
int matgloss_address = offset_descriptor->getAddress("matgloss");
int matgloss_offset = offset_descriptor->getHexValue("matgloss_skip");
int matgloss_colors = offset_descriptor->getOffset("matgloss_stone_color");
DfVector p_matgloss = dm->readVector(matgloss_address + matgloss_offset, 4);
stones.clear();
for (uint32_t i = 0; i< p_matgloss.getSize();i++)
{
uint32_t temp;
// read the matgloss pointer from the vector into temp
p_matgloss.read((uint32_t)i,(uint8_t *)&temp);
// read the string pointed at by
t_matgloss mat;
2009-10-21 18:18:49 -06:00
fill_char_buf(mat.id, dm->readSTLString(temp)); // reads a C string given an address
mat.fore = (uint8_t)MreadWord(temp + matgloss_colors);
mat.back = (uint8_t)MreadWord(temp + matgloss_colors + 2);
mat.bright = (uint8_t)MreadWord(temp + matgloss_colors + 4);
2009-09-13 18:02:46 -06:00
stones.push_back(mat);
}
return true;
}
bool DFHackAPIImpl::ReadMetalMatgloss(vector<t_matgloss> & metals)
2009-09-13 18:02:46 -06:00
{
int matgloss_address = offset_descriptor->getAddress("matgloss");
int matgloss_offset = offset_descriptor->getHexValue("matgloss_skip");
int matgloss_colors = offset_descriptor->getOffset("matgloss_metal_color");
DfVector p_matgloss = dm->readVector(matgloss_address + matgloss_offset*2, 4);
metals.clear();
for (uint32_t i = 0; i< p_matgloss.getSize();i++)
{
uint32_t temp;
// read the matgloss pointer from the vector into temp
p_matgloss.read((uint32_t)i,(uint8_t *)&temp);
// read the string pointed at by
t_matgloss mat;
2009-10-21 18:18:49 -06:00
fill_char_buf(mat.id, dm->readSTLString(temp)); // reads a C string given an address
mat.fore = (uint8_t)MreadWord(temp + matgloss_colors);
mat.back = (uint8_t)MreadWord(temp + matgloss_colors + 2);
mat.bright = (uint8_t)MreadWord(temp + matgloss_colors + 4);
2009-09-13 18:02:46 -06:00
metals.push_back(mat);
}
return true;
}
bool DFHackAPIImpl::ReadPlantMatgloss(vector<t_matgloss> & plants)
2009-09-13 18:02:46 -06:00
{
int matgloss_address = offset_descriptor->getAddress("matgloss");
int matgloss_offset = offset_descriptor->getHexValue("matgloss_skip");
DfVector p_matgloss = dm->readVector(matgloss_address + matgloss_offset*3, 4);
plants.clear();
// TODO: use green?
t_matgloss mat;
mat.fore = 7;
mat.back = 0;
mat.bright = 0;
for (uint32_t i = 0; i< p_matgloss.getSize();i++)
{
uint32_t temp;
// read the matgloss pointer from the vector into temp
p_matgloss.read((uint32_t)i,(uint8_t *)&temp);
// read the string pointed at by
2009-10-21 18:18:49 -06:00
fill_char_buf(mat.id, dm->readSTLString(temp)); // reads a C string given an address
2009-09-13 18:02:46 -06:00
plants.push_back(mat);
}
return true;
}
2009-10-22 19:39:19 -06:00
bool DFHackAPIImpl::ReadCreatureMatgloss(vector<t_matgloss> & creatures)
{
int matgloss_address = offset_descriptor->getAddress("matgloss");
int matgloss_offset = offset_descriptor->getHexValue("matgloss_skip");
DfVector p_matgloss = dm->readVector(matgloss_address + matgloss_offset*6, 4);
2009-10-22 19:39:19 -06:00
creatures.clear();
// TODO: use green?
t_matgloss mat;
mat.fore = 7;
mat.back = 0;
mat.bright = 0;
for (uint32_t i = 0; i< p_matgloss.getSize();i++)
{
uint32_t temp;
// read the matgloss pointer from the vector into temp
p_matgloss.read((uint32_t)i,(uint8_t *)&temp);
// read the string pointed at by
fill_char_buf(mat.id, dm->readSTLString(temp)); // reads a C string given an address
creatures.push_back(mat);
}
return true;
}
2009-09-13 18:02:46 -06:00
//vector<uint16_t> v_geology[eBiomeCount];
bool DFHackAPIImpl::ReadGeology( vector < vector <uint16_t> >& assign )
2009-09-13 18:02:46 -06:00
{
// get needed addresses and offsets
int region_x_offset = offset_descriptor->getAddress("region_x");
int region_y_offset = offset_descriptor->getAddress("region_y");
int region_z_offset = offset_descriptor->getAddress("region_z");
int world_offset = offset_descriptor->getAddress("world");
int world_regions_offset = offset_descriptor->getOffset("w_regions_arr");
int region_size = offset_descriptor->getHexValue("region_size");
int region_geo_index_offset = offset_descriptor->getOffset("region_geo_index_off");
int world_geoblocks_offset = offset_descriptor->getOffset("w_geoblocks");
int world_size_x = offset_descriptor->getOffset("world_size_x");
int world_size_y = offset_descriptor->getOffset("world_size_y");
int geolayer_geoblock_offset = offset_descriptor->getOffset("geolayer_geoblock_offset");
uint32_t regionX, regionY, regionZ;
uint16_t worldSizeX, worldSizeY;
// check if we have 'em all
if(
!(
region_x_offset && region_y_offset && region_z_offset && world_size_x && world_size_y
&& world_offset && world_regions_offset && world_geoblocks_offset && region_size
&& region_geo_index_offset && geolayer_geoblock_offset
)
)
{
// fail if we don't have them
return false;
}
// read position of the region inside DF world
MreadDWord(region_x_offset, regionX);
MreadDWord(region_y_offset, regionY);
MreadDWord(region_z_offset, regionZ);
// get world size
MreadWord(world_offset + world_size_x, worldSizeX);
MreadWord(world_offset + world_size_y, worldSizeY);
// get pointer to first part of 2d array of regions
uint32_t regions = MreadDWord(world_offset + world_regions_offset);
// read the geoblock vector
DfVector geoblocks = dm->readVector(world_offset + world_geoblocks_offset,4);
// iterate over 8 surrounding regions + local region
for(int i = eNorthWest; i< eBiomeCount; i++)
{
// check bounds, fix them if needed
int bioRX = regionX / 16 + (i%3) - 1;
if( bioRX < 0) bioRX = 0;
if( bioRX >= worldSizeX) bioRX = worldSizeX - 1;
int bioRY = regionY / 16 + (i/3) - 1;
if( bioRY < 0) bioRY = 0;
if( bioRY >= worldSizeY) bioRY = worldSizeY - 1;
// get pointer to column of regions
uint32_t geoX;
MreadDWord(regions + bioRX*4, geoX);
// get index into geoblock vector
uint16_t geoindex;
MreadWord(geoX + bioRY*region_size + region_geo_index_offset, geoindex);
// get the geoblock from the geoblock vector using the geoindex
uint32_t geoblock_off;
geoblocks.read(geoindex,(uint8_t *) &geoblock_off);
// get the vector with pointer to layers
DfVector geolayers = dm->readVector(geoblock_off + geolayer_geoblock_offset , 4); // let's hope
// make sure we don't load crap
assert(geolayers.getSize() > 0 && geolayers.getSize() <= 16);
// finally, read the layer matgloss
for(uint32_t j = 0;j< geolayers.getSize();j++)
{
int geol_offset;
// read pointer to a layer
geolayers.read(j, (uint8_t *) & geol_offset);
// read word at pointer + 2, store in our geology vectors
v_geology[i].push_back(MreadWord(geol_offset + 2));
}
}
assign.clear();
// TODO: clean this up
for(int i = 0; i< eBiomeCount;i++)
{
assign.push_back(v_geology[i]);
}
return true;
}
// returns number of buildings, expects v_buildingtypes that will later map t_building.type to its name
uint32_t DFHackAPIImpl::InitReadBuildings(vector <string> &v_buildingtypes)
2009-09-13 18:02:46 -06:00
{
buildingsInited = true;
int buildings = offset_descriptor->getAddress("buildings");
assert(buildings);
p_bld = new DfVector( dm->readVector(buildings,4));
offset_descriptor->copyBuildings(v_buildingtypes);
return p_bld->getSize();
}
// read one building
bool DFHackAPIImpl::ReadBuilding(const uint32_t &index, t_building & building)
2009-09-13 18:02:46 -06:00
{
assert(buildingsInited);
uint32_t temp;
t_building_df40d bld_40d;
// read pointer from vector at position
p_bld->read(index,(uint8_t *)&temp);
//read building from memory
Mread(temp, sizeof(t_building_df40d), (uint8_t *)&bld_40d);
// transform
int32_t type = -1;
offset_descriptor->resolveClassId(temp, type);
building.vtable = bld_40d.vtable;
building.x1 = bld_40d.x1;
building.x2 = bld_40d.x2;
building.y1 = bld_40d.y1;
building.y2 = bld_40d.y2;
building.z = bld_40d.z;
building.material = bld_40d.material;
building.type = type;
return true;
2009-09-13 18:02:46 -06:00
}
void DFHackAPIImpl::FinishReadBuildings()
2009-09-13 18:02:46 -06:00
{
buildingsInited = false;
}
//TODO: maybe do construction reading differently - this could go slow with many of them.
// returns number of constructions, prepares a vector, returns total number of constructions
uint32_t DFHackAPIImpl::InitReadConstructions()
2009-09-13 18:02:46 -06:00
{
constructionsInited = true;
int constructions = offset_descriptor->getAddress("constructions");
assert(constructions);
p_cons = new DfVector(dm->readVector(constructions,4));
return p_cons->getSize();
}
bool DFHackAPIImpl::ReadConstruction(const uint32_t &index, t_construction & construction)
2009-09-13 18:02:46 -06:00
{
assert(constructionsInited);
t_construction_df40d c_40d;
uint32_t temp;
// read pointer from vector at position
p_cons->read((uint32_t)index,(uint8_t *)&temp);
//read construction from memory
Mread(temp, sizeof(t_construction_df40d), (uint8_t *)&c_40d);
// transform
construction.x = c_40d.x;
construction.y = c_40d.y;
construction.z = c_40d.z;
construction.material = c_40d.material;
return true;
2009-09-13 18:02:46 -06:00
}
void DFHackAPIImpl::FinishReadConstructions()
2009-09-13 18:02:46 -06:00
{
constructionsInited = false;
}
uint32_t DFHackAPIImpl::InitReadVegetation()
2009-09-13 18:02:46 -06:00
{
vegetationInited = true;
int vegetation = offset_descriptor->getAddress("vegetation");
2009-10-22 19:39:19 -06:00
tree_offset = offset_descriptor->getOffset("tree_desc_offset");
assert(vegetation && tree_offset);
2009-09-13 18:02:46 -06:00
p_veg = new DfVector(dm->readVector(vegetation,4));
return p_veg->getSize();
}
bool DFHackAPIImpl::ReadVegetation(const uint32_t &index, t_tree_desc & shrubbery)
2009-09-13 18:02:46 -06:00
{
assert(vegetationInited);
uint32_t temp;
// read pointer from vector at position
p_veg->read(index,(uint8_t *)&temp);
//read construction from memory
2009-10-22 19:39:19 -06:00
Mread(temp + tree_offset, sizeof(t_tree_desc), (uint8_t *) &shrubbery);
2009-09-13 18:02:46 -06:00
// FIXME: this is completely wrong. type isn't just tree/shrub but also different kinds of trees. stuff that grows around ponds has its own type ID
if(shrubbery.material.type == 3) shrubbery.material.type = 2;
2009-10-22 19:39:19 -06:00
return true;
2009-09-13 18:02:46 -06:00
}
void DFHackAPIImpl::FinishReadVegetation()
2009-09-13 18:02:46 -06:00
{
vegetationInited = false;
}
2009-10-22 19:39:19 -06:00
uint32_t DFHackAPIImpl::InitReadCreatures()
{
creaturesInited = true;
int creatures = offset_descriptor->getAddress("creatures");
creature_pos_offset = offset_descriptor->getOffset("creature_position");
creature_type_offset = offset_descriptor->getOffset("creature_type");
creature_flags1_offset = offset_descriptor->getOffset("creature_flags1");
creature_flags2_offset = offset_descriptor->getOffset("creature_flags2");
assert(creatures && creature_pos_offset && creature_type_offset && creature_flags1_offset && creature_flags2_offset);
2009-10-22 19:39:19 -06:00
p_cre = new DfVector(dm->readVector(creatures, 4));
return p_cre->getSize();
}
bool DFHackAPIImpl::ReadCreature(const uint32_t &index, t_creature & furball)
{
assert(creaturesInited);
2009-10-22 19:39:19 -06:00
uint32_t temp;
// read pointer from vector at position
p_cre->read(index,(uint8_t *)&temp);
//read creature from memory
Mread(temp + creature_pos_offset, 3 * sizeof(uint16_t), (uint8_t *) &(furball.x)); // xyz really
Mread(temp + creature_type_offset, sizeof(uint32_t), (uint8_t *) &furball.type);
Mread(temp + creature_flags1_offset, sizeof(uint32_t), (uint8_t *) &furball.flags1);
Mread(temp + creature_flags2_offset, sizeof(uint32_t), (uint8_t *) &furball.flags2);
2009-10-22 19:39:19 -06:00
return true;
}
void DFHackAPIImpl::FinishReadCreatures()
{
creaturesInited = false;
}
bool DFHackAPIImpl::Attach()
2009-09-13 18:02:46 -06:00
{
// detach all processes, destroy manager
if(pm != NULL)
delete pm;
// find a process (ProcessManager can find multiple when used properly)
pm = new ProcessManager(xml); // FIXME: handle bad XML better
if(!pm->findProcessess())
return false; // FIXME: throw exceptions to distinguish errors? provide error code?
p = (*pm)[0]; // we just use the first found process
if(!p->attach())
return false; // couldn't attach to process, no go
offset_descriptor = p->getDescriptor();
dm = p->getDataModel();
// process is attached, everything went just fine... hopefully
return true;
}
// TODO: clean inited stuff here
bool DFHackAPIImpl::Detach()
2009-09-13 18:02:46 -06:00
{
p->detach();
if(pm != NULL)
delete pm;
pm = NULL;
p = NULL;
offset_descriptor = NULL;
dm = NULL;
return true;
}
bool DFHackAPIImpl::isAttached()
{
return dm != NULL;
}