From 1d805ca3281ce0d16a65a73a8839148f489ae40c Mon Sep 17 00:00:00 2001 From: Alexander Gavrilov Date: Wed, 13 Apr 2011 22:04:32 +0400 Subject: [PATCH] Implement a program to hack away bug 3708 (unengraveable ghosts). TODO: Test long-term consequences. --- Memory.xml | 74 +++++++++++ tools/playground/CMakeLists.txt | 2 + tools/playground/fix-3708.cpp | 217 ++++++++++++++++++++++++++++++++ 3 files changed, 293 insertions(+) create mode 100644 tools/playground/fix-3708.cpp diff --git a/Memory.xml b/Memory.xml index 775eee771..086f9e285 100644 --- a/Memory.xml +++ b/Memory.xml @@ -874,6 +874,7 @@ + @@ -895,6 +896,7 @@ + @@ -1013,6 +1015,39 @@
+ + +
+ + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + @@ -2902,6 +2937,12 @@
VERIFY + + + + + +
@@ -2921,6 +2962,39 @@ + + +
+ + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + diff --git a/tools/playground/CMakeLists.txt b/tools/playground/CMakeLists.txt index 5ff77de1c..6f60d0c16 100644 --- a/tools/playground/CMakeLists.txt +++ b/tools/playground/CMakeLists.txt @@ -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. diff --git a/tools/playground/fix-3708.cpp b/tools/playground/fix-3708.cpp new file mode 100644 index 000000000..1cfe50d9c --- /dev/null +++ b/tools/playground/fix-3708.cpp @@ -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 +#include +#include +#include +#include +#include +using namespace std; + +#define DFHACK_WANT_MISCUTILS +#include + +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 goblins; + std::list 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::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; +}