[OTClient] Battle List Tutorial
Hi, this tutorial is meant to help you understand how to find and iterate through the list of creatures managed by OTClient, mostly based on its source code.
For this guide, we will use Cheat Engine (CE) and Medivia (DirectX version), since Medivia is a custom OTClient.
Outline
- How to find Battle List address
- Understanding std::unordered_map
- How to Code
How to find Battle List address
In CipSoft's client, also known as Tibia.exe, the list of creatures (hereby Battle List) held by the client to store creatures is a static array in memory. This means that searching for character names, you usually find a few addresses, but one of them will be static and can be "recognized" visually when browsing memory region with CE, because each creature object has also pos x, pos y, pos z, hp percent and a lot of other fields. On the other hand, in OTClient, if we search for character names, we won't find static addresses to creature objects. This means that creature objects are kept in pointers.
Someone may argue that OTClient is harder to reverse engineer than Tibia.exe, but both don't seem to have any sort of bot protection code, so this is not entirely true. In my point of view, I think that OTClient talks about the same subject, but with different words. Moreover, OTClient has its source code publicly available and Tibia.exe not, thus with a bit of criativity, we can find whatever we want.
That being said, let's go.
In the first post of my "[OTClient] How to build a Full Light Hack" tutorial, I've shown a way to find a pointer path to Light address. In the second post, we patch addresses responsible to changes to the light address. We will use it in a different way.
In the second post, when we "Find out what writes to this address" in the Light address, CE shows the instruction responsible to this piece of code
Code:
creature->setLight(light);
caused by turning a torch on/off.
Turning a torch on/off, the server sends a "Creature Light change" packet to the OTClient, then the client parses this packet and applies such changes. In order to apply such light changes, the client has to find the correct creature to change its light property (hmmmm...)
At this time, the file https://github.com/edubart/otclient/...lgameparse.cpp contains the code responsible to parse the light change.
The code responsible for it is:
Code:
void ProtocolGame::parseCreatureLight(const InputMessagePtr& msg)
{
uint id = msg->getU32();
Light light;
light.intensity = msg->getU8();
light.color = msg->getU8();
CreaturePtr creature = g_map.getCreatureById(id);
if(creature)
creature->setLight(light);
else
g_logger.traceError("could not get creature");
}
Looking this code, the g_map object instance seems to manage the list of creatures (Battle List). In the last lines of https://github.com/edubart/otclient/...c/client/map.h file, we can see g_map is an instance of Map class.
At the time of writing this post, this is the function getCreatureById of the Map class:
Code:
CreaturePtr Map::getCreatureById(uint32 id)
{
auto it = m_knownCreatures.find(id);
if(it == m_knownCreatures.end())
return nullptr;
return it->second;
}
So, the m_knownCreatures field in the Map class
Code:
std::unordered_map<uint32, CreaturePtr> m_knownCreatures;
seems to be the Battle List and we are going to find its address.
Let's analyze the parseCreatureLight function:
Code:
void ProtocolGame::parseCreatureLight(const InputMessagePtr& msg)
{
uint id = msg->getU32();
Light light;
light.intensity = msg->getU8();
light.color = msg->getU8();
CreaturePtr creature = g_map.getCreatureById(id);
if(creature)
creature->setLight(light);
else
g_logger.traceError("could not get creature");
}
We can see the line
Code:
CreaturePtr creature = g_map.getCreatureById(id);
is the first function above
Code:
creature->setLight(light);
found in the other tutorial. So, in ASM, we are looking for the first CALL instruction above the line
Code:
mov [ecx+LIGHT_OFFSET], ax
Before we proceed, let's understand a little C++ to ASM theory:
everytime we have a C++ class like that
Code:
class Something
{
public:
int someFunction();
int someField;
};
and we use it this way
Code:
Something obj;
.
.
.
obj.someFunction();
in ASM this will be something like that
Code:
.
.
.
mov ecx, ADDRESS_OF_obj
.
.
.
call ADDRESS_OF_someFunction
Under code execution, in the call line, ecx register will hold the address to obj. It is bold here because it is really important.
Now that we know how things work, we follow these steps to get the address
Steps
follow my "[OTClient] How to build a Full Light Hack" tutorial up to Step 3 from the second post to reach a screen like that
http://i.imgur.com/eVDVG7x.png
Here, we are within the parseCreatureLight function.
In the red circle
Code:
creature->setLight(light);
In the green circle, the call
Code:
g_map.getCreatureById(id);
And in the blue circle, the last instruction changing ecx register before the call. It's in the following form
Code:
mov ecx, ADDRESS_OF_g_map
- Now, we know the address of g_map object and we are going to follow getCreatureById function, so right click the call line and click Follow option
http://i.imgur.com/cWWhidG.png
- function getCreatureById
http://i.imgur.com/N1rA19F.png
Code:
CreaturePtr Map::getCreatureById(uint32 id)
{
auto it = m_knownCreatures.find(id);
if(it == m_knownCreatures.end())
return nullptr;
return it->second;
}
In purple circle, ecx is assigned to esi register (g_map address is now in esi)
In blue circle, the ASM code
Code:
lea ecx, [esi + m_knownCreatures_offset]
call ADDRESS_OF_knownCreatures.find
is responsible for the C++ code
Code:
m_knownCreatures.find(id);
After it all, we know that Battle List address is
Code:
battle_list_address = ADDRESS_OF_g_map + m_knownCreatures_offset
and we are done.
In the following post, we are going to understand how std::unordered_map works.