Implement a program to hack away bug 3708 (unengraveable ghosts).

TODO: Test long-term consequences.
develop
Alexander Gavrilov 2011-04-13 22:04:32 +04:00
parent ebc4d21e66
commit 1d805ca328
3 changed files with 293 additions and 0 deletions

@ -874,6 +874,7 @@
<Offset name="position" description="X,Y,Z." />
<Offset name="flags1" description="First set of flags" />
<Offset name="flags2" description="Second set of flags" />
<Offset name="flags3" description="Third set of flags" />
<Offset name="caste" description="Caste of the creature. Same as sex most of the time." />
<Offset name="sex" description="Sex of the creature." />
<Offset name="id" description="Unique ID of the creature, seems to be used for binary search in the creature vector." />
@ -895,6 +896,7 @@
<Offset name="current_soul" description="Currently active soul?" />
<Offset name="labors" description="Array of labors. Used by DT to enable/disable them." />
<Offset name="happiness" description="Number that says how happy the creature is." />
<Offset name="hist_figure_id" description="For a creature matching a historical figure, it's ID"/>
</Group>
</Group>
@ -1013,6 +1015,39 @@
<Address name="control_mode" description="Current control mode" />
<!--<Address name="control_mode_copy" description="Copy of the control mode in DF memory" />-->
</Group>
<Group name="Legends">
<Group name="figures">
<Address name="vector"/>
<Offset name="profession" description="Figure's profession"/>
<Offset name="race" description="Figure's race"/>
<Offset name="caste" description="Figure's caste; mostly equal to sex"/>
<Offset name="sex" description="Figure's sex (or maybe the other way round)"/>
<Offset name="appeared_year"/>
<Offset name="born_year"/>
<Offset name="born_seconds"/>
<Offset name="died_year"/>
<Offset name="died_seconds"/>
<Offset name="name" description="A standard name record"/>
<Offset name="unit_id" description="ID of the creature matching the figure, or -1"/>
<Offset name="figure_id" description="ID of the figure, ordered"/>
<Offset name="entity_link_vector"/>
<Offset name="histfig_link_vector"/>
</Group>
<Group name="events">
<Address name="vector"/>
<Offset name="year"/>
<Offset name="seconds"/>
<Offset name="event_id"/>
<Group name="hist_figure_died">
<Offset name="figure_id"/>
<Offset name="slayer_id"/>
<Offset name="slayer_race"/>
<Offset name="slayer_caste"/>
<Offset name="slayer_item_id"/>
<Offset name="region_id"/>
</Group>
</Group>
</Group>
</Offsets>
</Base>
@ -2902,6 +2937,12 @@
<Offsets>
<Group name="Creatures">
<Address name="vector" value="0x0940b174" /> VERIFY
<Group name="creature">
<Offset name="flags3" value="0x94"/>
<Group name="advanced">
<Offset name="hist_figure_id" value="0x640"/>
</Group>
</Group>
</Group>
<Group name="Position" valid="true">
<Address name="cursor_xyz" value="0x8c3de60"/>
@ -2921,6 +2962,39 @@
<Offset name="item_ref_vector" value="0x24" />
<Offset name="owner_ref_id_field" value="0x4" />
</Group>
<Group name="Legends">
<Group name="figures">
<Address name="vector" value="0x9451a18"/>
<Offset name="profession" value="0x00"/>
<Offset name="race" value="0x02"/>
<Offset name="caste" value="0x04"/>
<Offset name="sex" value="0x06"/>
<Offset name="appeared_year" value="0x08"/>
<Offset name="born_year" value="0x0C"/>
<Offset name="born_seconds" value="0x10"/>
<Offset name="died_year" value="0x1C"/>
<Offset name="died_seconds" value="0x20"/>
<Offset name="name" value="0x24"/>
<Offset name="unit_id" value="0x70"/>
<Offset name="figure_id" value="0x74"/>
<Offset name="entity_link_vector" value="0x7c"/>
<Offset name="histfig_link_vector" value="0x94"/>
</Group>
<Group name="events">
<Address name="vector" value="0x9451a00"/>
<Offset name="year" value="0x04"/>
<Offset name="seconds" value="0x08"/>
<Offset name="event_id" value="0x14"/>
<Group name="hist_figure_died">
<Offset name="figure_id" value="0x18"/>
<Offset name="slayer_id" value="0x1C"/>
<Offset name="slayer_race" value="0x20"/>
<Offset name="slayer_caste" value="0x24"/>
<Offset name="slayer_item_id" value="0x28"/>
<Offset name="region_id" value="0x48"/>
</Group>
</Group>
</Group>
</Offsets>
</Version>
</DFHack>

@ -82,6 +82,8 @@ DFHACK_TOOL(dfhellhole hellhole.cpp)
DFHACK_TOOL(dfcleanowned cleanowned.cpp)
DFHACK_TOOL(dffixbug-3708 fix-3708.cpp)
# this needs the C bindings
IF(BUILD_DFHACK_C_BINDINGS)
# The C bindings won't be real C bindings until this compiles.

@ -0,0 +1,217 @@
/* Fixes bug 3708 (Ghosts that can't be engraved on a slab).
Cause of the bug:
In order to be engraved on a slab, the creature must be
a historical figure, i.e. be in the historical figure list
of the Legends mode. It seems that caravan guards are not
added to that list until they do something notable, e.g.
kill a goblin. Unfortunately, their own death doesn't
trigger this sometimes.
Solution:
Steal a historical figure entry from a dead goblin, by
replacing the IDs in the structures; also overwrite his
name, race and profession to make the menus make slightly
more sense.
Downsides:
- Obviously, this is an ugly hack.
- The Legends mode still lists the guard as belonging to
the goblin civilization, and killed by whoever killed the
original goblin. There might be other inconsistencies.
Positive sides:
- Avoids messing with tricky creature control code,
by allowing the ghost to be removed naturally.
*/
#include <iostream>
#include <climits>
#include <string.h>
#include <vector>
#include <list>
#include <stdio.h>
using namespace std;
#define DFHACK_WANT_MISCUTILS
#include <DFHack.h>
enum likeType
{
FAIL = 0,
MATERIAL = 1,
ITEM = 2,
FOOD = 3
};
DFHack::Materials * Materials;
DFHack::VersionInfo *mem;
DFHack::Creatures * Creatures = NULL;
void printCreature(DFHack::Context * DF, const DFHack::t_creature & creature)
{
cout << "Address: " << hex << creature.origin << dec << ", creature race: " << Materials->raceEx[creature.race].rawname
<< ", position: " << creature.x << "x " << creature.y << "y "<< creature.z << "z" << endl
<< "Name: " << creature.name.first_name;
if (creature.name.nickname[0])
cout << " `" << creature.name.nickname << "'";
DFHack::Translation * Tran = DF->getTranslation();
cout << " " << Tran->TranslateName(creature.name,false)
<< " (" << Tran->TranslateName(creature.name,true) << ")" << endl;
cout << "Profession: " << mem->getProfession(creature.profession);
if(creature.custom_profession[0])
cout << ", custom: " << creature.custom_profession;
uint32_t dayoflife = creature.birth_year*12*28 + creature.birth_time/1200;
cout << endl
<< "Born on the year " << creature.birth_year
<< ", month " << (creature.birth_time/1200/28)
<< ", day " << ((creature.birth_time/1200) % 28 + 1)
<< ", " << dayoflife << " days lived." << endl << endl;
}
int main (int numargs, char ** args)
{
DFHack::World * World;
DFHack::ContextManager DFMgr("Memory.xml");
DFHack::Context* DF;
try
{
DF = DFMgr.getSingleContext();
DF->Attach();
}
catch (exception& e)
{
cerr << e.what() << endl;
#ifndef LINUX_BUILD
cin.ignore();
#endif
return 1;
}
Creatures = DF->getCreatures();
Materials = DF->getMaterials();
World = DF->getWorld();
DFHack::Translation * Tran = DF->getTranslation();
uint32_t numCreatures;
if(!Creatures->Start(numCreatures))
{
cerr << "Can't get creatures" << endl;
#ifndef LINUX_BUILD
cin.ignore();
#endif
return 1;
}
Materials->ReadCreatureTypes();
Materials->ReadCreatureTypesEx();
mem = DF->getMemoryInfo();
DFHack::Process *p = DF->getProcess();
if(!Tran->Start())
{
cerr << "Can't get name tables" << endl;
return 1;
}
DFHack::OffsetGroup *ogc = mem->getGroup("Creatures")->getGroup("creature");
uint32_t o_flags3 = ogc->getOffset("flags3");
uint32_t o_c_hfid = ogc->getGroup("advanced")->getOffset("hist_figure_id");
std::list<uint32_t> goblins;
std::list<uint32_t> ghosts;
for(uint32_t i = 0; i < numCreatures; i++)
{
DFHack::t_creature temp;
Creatures->ReadCreature(i,temp);
int32_t hfid = p->readDWord(temp.origin + o_c_hfid);
if (hfid > 0) {
if (temp.flags1.bits.dead) {
std::string name = Materials->raceEx[temp.race].rawname;
if (name == "GOBLIN")
goblins.push_back(i);
}
} else {
uint32_t flags3 = p->readDWord(temp.origin + o_flags3);
if (!(flags3 & 0x1000))
continue;
ghosts.push_back(i);
}
}
if (goblins.size() >= ghosts.size() && ghosts.size() > 0)
{
DFHack::OffsetGroup *ogf = mem->getGroup("Legends")->getGroup("figures");
uint32_t f_vector = p->readDWord(ogf->getAddress("vector"));
uint32_t f_id = ogf->getOffset("figure_id");
uint32_t f_unit = ogf->getOffset("unit_id");
uint32_t f_name = ogf->getOffset("name");
uint32_t f_race = ogf->getOffset("race");
uint32_t f_profession = ogf->getOffset("profession");
for (std::list<uint32_t>::iterator it = ghosts.begin(); it != ghosts.end(); ++it)
{
int i = *it;
DFHack::t_creature ghost;
Creatures->ReadCreature(i,ghost);
printCreature(DF,ghost);
int igoblin = goblins.front();
goblins.pop_front();
DFHack::t_creature goblin;
Creatures->ReadCreature(igoblin,goblin);
printCreature(DF,goblin);
int32_t hfid = p->readDWord(goblin.origin + o_c_hfid);
uint32_t fptr = p->readDWord(f_vector + 4*hfid);
if (p->readDWord(fptr + f_id) != hfid ||
p->readDWord(fptr + f_unit) != goblin.id ||
p->readWord(fptr + f_race) != goblin.race)
{
cout << "Data structure inconsistency detected, aborting.";
break;
}
if (1) {
p->writeDWord(goblin.origin + o_c_hfid, -1);
p->writeDWord(ghost.origin + o_c_hfid, hfid);
p->writeDWord(fptr + f_unit, ghost.id);
p->writeWord(fptr + f_race, ghost.race);
p->writeWord(fptr + f_profession, ghost.profession);
Creatures->CopyNameTo(ghost, fptr + f_name);
cout << "Pair succesfully patched." << endl << endl;
}
}
}
else
{
cout << "No suitable ghosts, or not enough goblins." << endl;
}
Creatures->Finish();
DF->Detach();
#ifndef LINUX_BUILD
cout << "Done. Press any key to continue" << endl;
cin.ignore();
#endif
return 0;
}