Announcement

Collapse
No announcement yet.

[OTClient] Battle List Tutorial

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • [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 (https://github.com/edubart/otclient).

    For this guide, we will use Cheat Engine (http://www.cheatengine.org/) (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 (https://github.com/edubart/otclient) 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
    1. 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


    2. 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
    3. 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
    4. function getCreatureById


      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);
    5. 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.

  • #2
    Understanding std::unordered_map


    Fortunately, I think std::unordered_map is not a difficult structure to understand. We will see through diagrams how it is laid out in memory and how you can read it.

    In the following diagrams, on every place we see an arrow with a label near, it means: "read 32-bits integer from memory at the offset of this field".

    This is the general std::unordered_map representation


    This is the std::unordered_map representation using TKey = uint32 and TValue = CreaturePtr




    How to Code


    The std::unordered_map has a pointer to a buffer of nodes, this buffer has a pointer to the first node and each node has next and previous pointers. Also, each node has key and value fields (not pointers).

    Now, we can test a code to print details about each creature on CE in Medivia (DirectX):

    Code:
    local baseAddress = getAddress("Medivia_D3D.exe")
    
    local gmapAddress = 0x54C3A0
    local knownCreaturesOffset = 0x200
    local battleListAddress = gmapAddress + knownCreaturesOffset
    
    local UnorderedMapOffsets = {
    	Unknown = 0x0,
    	BufferPointer = 0x4,
    	Count = 0x8
    }
    
    local UnorderedMapBufferOffsets = {
    	NodePointer = 0x0
    }
    
    local UnorderedMapNodeOffsets = {
    	NextPointer = 0x0,
    	PreviousPointer = 0x4,
    	Key = 0x8,
    	Value = 0xC
    }
    
    local CreatureOffsets = {
    	PosX = 0xC,
    	PosY = 0x10,
    	PosZ = 0x14,
    	Id = 0x1C,
    	Name = 0x20
    }
    
    -- helper functions
    function readStdString(address)
    	local smallBufferSize = 0x10
    	local lengthAddress = address + smallBufferSize
    	local length = readInteger(lengthAddress)
    	local res = nil
    	if (length < smallBufferSize) then
    		res = readString(address, length)
    	else
    		res = readString(readInteger(address), length)
    	end
    	return res
    end
    
    function readShort(address)
    	local buffer = readBytes(address, 2, true)
    	return buffer[1] + buffer[2] * 256
    end
    -- end of helper functions
    
    local address = baseAddress + battleListAddress
    local count = readInteger(address + UnorderedMapOffsets.Count)
    
    print("Number of creatures: " .. count)
    
    if (count > 0) then
    	local bufferPointer = readInteger(address + UnorderedMapOffsets.BufferPointer)
    
    	local currentNodeAddress = bufferPointer + UnorderedMapBufferOffsets.NodePointer
    
    	local i = 1
    	local stop = false
    
    	while (not stop) do
    		local nodePointer = readInteger(currentNodeAddress)
    		local creaturePointer = readInteger(nodePointer + UnorderedMapNodeOffsets.Value)
    
    		-- printing creature
    
    		local posX = readInteger(creaturePointer + CreatureOffsets.PosX)
    		local posY = readInteger(creaturePointer + CreatureOffsets.PosY)
    		local posZ = readShort(creaturePointer + CreatureOffsets.PosZ)
    		local id = readInteger(creaturePointer + CreatureOffsets.Id)
    		local name = readStdString(creaturePointer + CreatureOffsets.Name)
    
    		local msg = ("x: %i, y: %i, z: %i, id: %i, name: %s"):format(
    			posX,
    			posY,
    			posZ,
    			id,
    			name
    		)
    		print(msg)
    		-- end of print
    
    		i = i + 1
    		if (i <= count) then
    			currentNodeAddress = nodePointer + UnorderedMapNodeOffsets.NextPointer
    		else
    			stop = true
    		end
    	end
    end

    Comment


    • #3
      What great contributions... Thanks a lot! I was having an extremely hard time at finding the battle list on OT Client.

      Comment


      • #4
        Wow, amazing! I'm honestly a bit confused about how its laid out logically, but will read through it a bunch of times and try it out myself on c# on the OGL client. Thank you very much for this, amazing work!

        Comment


        • #5
          Hi, which cheat engine version are you using? Basically my mov ecx, ADDRESS_OF_g_map shows up as mov ecx, SDL_wcslen + 2BAE20 instead of Medivia_D3D.exe + blabla.. Tried different view options such as ticking show module addresses, kernelmode symbols etc, none of the combinations work.

          Comment


          • #6
            I don't know, but I guess it was CE 6.4 back in the time. Not sure, but maybe you are working on a different otclient than Medivia uses, so things might change a bit due compilers and optimization options.

            Comment


            • #7
              Im working on medivia. Okay, switched to the latest Cheat engine, and it works there
              Last edited by normik; 19-09-2016, 01:41 PM.

              Comment


              • #8
                I don't have the time to check if Medivia has released a new version of their client. Which Medivia rendering engine (dx, opengl) and client version (e.g.: 1.0232) are you using? I might take a look at weekend, maybe

                Comment


                • #9
                  I'm also having trouble converting this method to c#. Sorta got it working in c++

                  Comment


                  • #10
                    Nice explanation about the unordered map architeture.
                    Last edited by wolffexz; 30-09-2016, 04:54 AM.

                    Comment


                    • #11
                      I'm trying to convert it to c++, everything is working except name - when i try to read name value from address it gives me another address(?). Did someone got it working in c++?

                      Comment


                      • #12
                        std::string is used for storing the name in OTClient which means that if the string exceeds 15 characters, the field will instead point to a string instead of directly pointing to the string.

                        The structure is something like:



                        union{
                        char data[15];
                        uint32_t pointer;
                        } field;

                        uint32_t max_length;
                        uint32_t length;

                        Comment


                        • #13
                          can I reproduce this on the otclient version used by pxg, i tried to follow the tutorial with lights and also tried it with the change outfit function but things seems kinda different

                          Comment


                          • #14
                            I don't know. However, I have meet a guy who were working on such pxg and have succeeded following the idea of this tutorial

                            Comment


                            • #15
                              Someone had any lucky updating this?

                              Look's like the creatures list offset now is 0x264 and the UnorderedMapOffsets->BufferPointer is 0x14, but it doesn't always works, it's kinda random, it works, but if you open the client again it doesn't work at all, or just get like half of the creatures in the list.

                              Comment

                              Working...
                              X