Deprecated: The behavior of unparenthesized expressions containing both '.' and '+'/'-' will change in PHP 8: '+'/'-' will take a higher precedence in /home/iano/public_html/tpforums-vb5/forum/includes/class_core.php on line 5842

PHP Warning: Use of undefined constant MYSQL_NUM - assumed 'MYSQL_NUM' (this will throw an Error in a future version of PHP) in ..../includes/init.php on line 165

PHP Warning: Use of undefined constant MYSQL_ASSOC - assumed 'MYSQL_ASSOC' (this will throw an Error in a future version of PHP) in ..../includes/init.php on line 165

PHP Warning: Use of undefined constant MYSQL_BOTH - assumed 'MYSQL_BOTH' (this will throw an Error in a future version of PHP) in ..../includes/init.php on line 165

PHP Warning: "continue" targeting switch is equivalent to "break". Did you mean to use "continue 2"? in ..../includes/functions_navigation.php on line 588

PHP Warning: "continue" targeting switch is equivalent to "break". Did you mean to use "continue 2"? in ..../includes/functions_navigation.php on line 612

PHP Warning: Use of undefined constant misc - assumed 'misc' (this will throw an Error in a future version of PHP) in ..../global.php(29) : eval()'d code(6) : eval()'d code on line 1

PHP Warning: Use of undefined constant index - assumed 'index' (this will throw an Error in a future version of PHP) in ..../global.php(29) : eval()'d code(6) : eval()'d code on line 1

PHP Warning: Use of undefined constant misc - assumed 'misc' (this will throw an Error in a future version of PHP) in ..../includes/class_bootstrap.php(1422) : eval()'d code(4) : eval()'d code on line 1

PHP Warning: Use of undefined constant index - assumed 'index' (this will throw an Error in a future version of PHP) in ..../includes/class_bootstrap.php(1422) : eval()'d code(4) : eval()'d code on line 1

PHP Warning: Use of undefined constant onlinestatusphrase - assumed 'onlinestatusphrase' (this will throw an Error in a future version of PHP) in ..../includes/class_core.php(4684) : eval()'d code on line 6

PHP Warning: Use of undefined constant onlinestatusphrase - assumed 'onlinestatusphrase' (this will throw an Error in a future version of PHP) in ..../includes/class_core.php(4684) : eval()'d code on line 6

PHP Warning: Use of undefined constant onlinestatusphrase - assumed 'onlinestatusphrase' (this will throw an Error in a future version of PHP) in ..../includes/class_core.php(4684) : eval()'d code on line 6

PHP Warning: Use of undefined constant onlinestatusphrase - assumed 'onlinestatusphrase' (this will throw an Error in a future version of PHP) in ..../includes/class_core.php(4684) : eval()'d code on line 6

PHP Warning: Use of undefined constant onlinestatusphrase - assumed 'onlinestatusphrase' (this will throw an Error in a future version of PHP) in ..../includes/class_core.php(4684) : eval()'d code on line 6

PHP Warning: Use of undefined constant onlinestatusphrase - assumed 'onlinestatusphrase' (this will throw an Error in a future version of PHP) in ..../includes/class_core.php(4684) : eval()'d code on line 6

PHP Warning: Use of undefined constant onlinestatusphrase - assumed 'onlinestatusphrase' (this will throw an Error in a future version of PHP) in ..../includes/class_core.php(4684) : eval()'d code on line 6

PHP Warning: Use of undefined constant onlinestatusphrase - assumed 'onlinestatusphrase' (this will throw an Error in a future version of PHP) in ..../includes/class_core.php(4684) : eval()'d code on line 6

PHP Warning: Use of undefined constant onlinestatusphrase - assumed 'onlinestatusphrase' (this will throw an Error in a future version of PHP) in ..../includes/class_core.php(4684) : eval()'d code on line 6

PHP Warning: Use of undefined constant onlinestatusphrase - assumed 'onlinestatusphrase' (this will throw an Error in a future version of PHP) in ..../includes/class_core.php(4684) : eval()'d code on line 6
[OTClient] Battle List Tutorial
Page 1 of 3 123 LastLast
Results 1 to 10 of 21

Thread: [OTClient] Battle List Tutorial

  1. #1
    Senior Member
    Join Date
    Jan 2012
    Posts
    417

    [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


    1. How to find Battle List address
    2. Understanding std::unordered_map
    3. 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

    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

      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
    2. 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

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


    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. #2
    Senior Member
    Join Date
    Jan 2012
    Posts
    417
    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

  3. #3
    Another great tutorial, thank you for sharing.

  4. #4
    Thats awesome, thank you very much.

  5. #5
    Junior Member
    Join Date
    Jul 2009
    Posts
    22
    thanks for sharing!

  6. #6
    Junior Member
    Join Date
    Jul 2009
    Posts
    22
    btw, do you know how to check if a creature is a NPC, a monsters or other players?

  7. #7
    Senior Member
    Join Date
    Jan 2012
    Posts
    417
    creature type was introduced in 9.x if I recall correctly. For these 7.x it should not be available

  8. #8
    Senior Member
    Join Date
    Mar 2009
    Location
    Brazil
    Posts
    266
    One of the best thread i've seen all this years.

    Thanks Blequi.

    I hope the flame of sharing will raise from the ashes.

  9. #9
    Senior Member
    Join Date
    Nov 2009
    Posts
    320
    Wow! That's incredible! Thanks a lot
    oi amiguinhos

  10. #10
    Senior Member
    Join Date
    Nov 2009
    Posts
    320
    @Blequi, I made a BL analyzer and worked 100% here, thanks a lot!
    But when I pass to my friend to test, 10~15% of the creatures have the posZ and/or HP offset negative and with weird values!
    Do you know what is the problem?

    #Edit
    When I launch the client/dll from the Delphi runtime, it works perfectly
    But when I launch first the client and later the bot, this strange bug happens sometimes o.O

    #Edit2: hp and posZ offsets are byte but I was reading as 4 bytes and worked at home, but not at my friend pc lol. Solved
    Last edited by Devil; 02-29-2016 at 12:58 AM.
    oi amiguinhos

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •