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 85

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
Calling methods on the tibia GUI structure
Results 1 to 5 of 5

Thread: Calling methods on the tibia GUI structure

  1. #1
    Senior Member
    Join Date
    Jun 2007
    Posts
    247

    Calling methods on the tibia GUI structure

    This post is going to be brief on details, but I'll point to my github project which I hope may find greater use. I'd be particularly interested in hearing from anyone willing to replicate the work on the windows client. The following was performed on the Tibia-1079 client.

    Let's take the example of creating an auto login as motivation. The technique I will describe is to locate the UI element in memory and call the button_press() and button_release() functions. This technique generalises to all elements, and the button_release() functions make for very interesting break points. By breaking on the button_release() function of map element, I was able to quickly identify the below function addresses.

    Code:
            int32_t (*set_target)(int32_t, int32_t, int32_t, int32_t, int32_t) = (void *)0x080e8930;
            int32_t (*astar)(void) = (void *)0x080837e0;
            int32_t (*local_z)(void) = (void *)0x08185a70;
    
            // The below will walk to (14, 6) relative to the top left corner of the map (0, 0)
                    int32_t x = 14;
                    int32_t y = 6;
                    int32_t z = local_z();
    
                    to_global(&x, &y, &z);
                    set_target(x, y, z);
                    astar();
    The Tibia GUI structure has been known for a while, however I am going to further explain how to call the GUI methods.

    First we need to understand the top level structure of the top level GUIComponent class.

    Code:
    typedef struct GUIComponent GUIComponent;
    struct GUIComponent {
            void *vtable;
            int32_t unknown1;
            int32_t unknown2;
            GUIComponent *parent;
            GUIComponent *sibling;
            int32_t x;
            int32_t y;
            int32_t width;
            int32_t height;
    };
    This is an abstract class from which all gui components inherit, and therefore the compiler has created a vtable for this type. GUIComponent defines a large number of virtual functions, and the vtable for a child of GUIComponent contains the function pointers to the child's implementations.

    Using edb I found two global pointers.

    Code:
            GUIComponent **root = (GUIComponent **)0x83d4e84;
            GUIComponent **dialog = (GUIComponent **)0x83d4e80;
    I've also partially determined the structure of the vtable

    Code:
            struct {
                    void *unknown1[9];
                    GUIComponent *(*get_children)(GUIComponent *w);
                    void *unknown2[1];
                    GUIComponent *(*get_sibling)(GUIComponent *w);
                    void *unknown3[1];
                    GUIComponent *(*get_parent)(GUIComponent *w);
                    void *unknown4[9];
                    int32_t (*button_press)(GUIComponent *w, int32_t x, int32_t y, int32_t button);
                    int32_t (*button_release)(GUIComponent *w, int32_t x, int32_t y, int32_t button);
                    void *unknown5[4];
                    int32_t (*key_press)(GUIComponent *w, int32_t code);
                    int32_t (*key_release)(GUIComponent *w, int32_t code);
                    void *unknown[0];
            } *vtable;
    And this is everything we need to know to call button press on the root element.

    Code:
            GUIComponent **root = (GUIComponent **)0x83d4e84;
            (*root)->vtable.button_press(*root, x, y, 0);
            (*root)->vtable.button_release(*root, x, y, 0);
    If we know the x y position of the element we want to click we are set. But this is less than ideal, we would rather not need to know the screen coordinates of the element. We may even want to interact with an off screen part of the game.

    So now we need to interpret the structure of the GUI tree, and to do this we need to understand GUIComposite, a GUIComponent with children.

    Code:
    typedef struct GUIComposite GUIComposite;
    struct GUIComposite {
            GUIComponent component;
            GUIComponent *children;
    };
    GUIComposit adds a single element and no new virtual members. If there were new virtual methods, a second vtable pointer would come before children;

    Code:
    typedef struct GUIComposite GUIComposite;
    struct GUIComposite {
            GUIComponent component;
    //      void *vtable;
            GUIComponent *children;
    };
    GUIComponents form linked lists of siblings. Additionally, the GUIComposite may have children. The resulting structure looks as below.

    Code:
    MainWindow
     - TitleWindow
       - GUIFrameWindow
          - GUIComposite
            - GUITextButton - GUITextButton - GUITextButton - GUITextButton - GUITextButton - GUITextButton
    The row of buttons corresponds to the buttons down the left hand side of the logged out screen. To traverse the tree structure though, we need to determine whether or not a GUIComponent is a GUIComposite. In c++, this would be done by making a dynamic cast.

    Code:
            GUIComponent *component = ...;
            GUIComposite *composite = dynamic_cast<GUIComposite>(component);
            if (composite != NULL) {
                     // was a composite
            }
    The linux client was compiled with gcc, which created the c __dynamic_cast method

    Code:
            // the -1 means "no offset hint", which is always valid but potentially inefficient
            void *__dynamic_cast(void *ptr, rtti::type_info src_type, rtti::type_info dst_type, -1);
    If we know where to find the rtti::type_info, we can call the underlying __dynamic_cast method. Well we can actually do better than that, my rtti_extractor will extract the rtti::type_info addresses for all dynamic types.

    We can also extract the class names and inheritance hierarchy, from which I've generated an svg.

    Most importantly, we can determine if a GUIComponent has children, which allows us to traverse the tree. login.c is a proof of concept with uses the aforementioned machinery to traverse the GUI tree and find the relevant gui components. Then we can call the function pointers which perform the login sequence. Because we found the elements in memory, there is no need to know their x, y positions.

    Code:
    // Find account / password entry components.
    GUIPasswordEdit *account  = (GUIPasswordEdit *)find_gui_component(*current, is_password, NULL);
    GUIPasswordEdit *password = (GUIPasswordEdit *)find_gui_component(*current, is_password, account);
    GUIButton *ok = (GUIButton *)find_gui_component(*current, is_button, "Ok");
    
    // We don't know which password edit is which, but we know account is above password.
    // Sort on y offset.
    if (account->edit.element.component.y > password->edit.element.component.y) {
            GUIPasswordEdit *tmp = account;
            account = password;
            password = tmp;
    }
    
    gui_type((GUIComponent *)account, "account");
    gui_type((GUIComponent *)password, "password");
    
    gui_click((GUIComponent *)ok, 1, 1, 0);
    For most parts of the client, this is exactly the level of control we want. Calling the button_press and button_release functions of a button will do exactly what the client would do if you clicked it. (Not exactly true, the client would normally store the last x,y click position of the cursor, but this is irrelevant).

    But for more complicated elements like the map we want control at a lower level. Instead of specifying cursor positions we would rather specify map coordinates. We know that the button_release function on the MapWindow must be converting the screen coordinates to x, y to map coordinates and then using them to move the character, and to determine how it does this the button_release function makes a splendid starting point.

    There are a couple of ways to find the vtable of a GUI component type. The easiest way I can think of is to use the print_tree method in my proof of concept to print out a detailed view of the GUI, including type names and importantly the address of the vtable.

    The other method is to look up the address of the rtti::type_info in rtti.h. If you search for (rtti_address - 4), you will find a reference from (vtable_address - 4).

    Once you have the vtable address, the button_release function pointer is at (vtable_address + 96). You can look at the vtable structure above to work out the offsets of the other functions. You can now set a break point here and start tracing how the map responds to button releases.


    If you've read this far, you may be interested in trying the proof of concept. It has a second file xpending.c which defines XPending, a function the Tibia client calls on each iteration of the event loop. This makes a fantastic entry point for our code, which we will inject by using LD_PRELOAD.

    To run the proof of concept on a linux system, it should be as simple as.

    Code:
    make
    LD_PRELOAD=/path/to/tibia-hook.so ./Tibia
    It will attempt to log in using the incorrect credentials "account" and "password", which you may change to your own.

    If you would be interested in further reverse engineering the GUI structure or working on a windows port, contact me via my github.
    Last edited by *DEAD*; 07-03-2015 at 02:09 AM.

  2. #2
    Senior Member
    Join Date
    Jun 2007
    Posts
    247
    For interests sake I've included a detailed print out of the start screen's GUI tree

    Code:
    0x9fae708 (11CMainWindow) {
    	vtable   = 0x82f88c8
    	unknown1 = 0x0
    	unknown2 = 0x0
    	parent   = (nil)
    	sibling  = (nil)
    	x        = 0x0
    	y        = 0x0
    	width    = 0x280
    	height   = 0x1e0
    	children = 0x9fae760
    	0x9fae760 (12CTitleWindow) {
    		vtable   = 0x82f8988
    		unknown1 = 0x0
    		unknown2 = 0x0
    		parent   = 0x9fae708
    		sibling  = (nil)
    		x        = 0x0
    		y        = 0x0
    		width    = 0x280
    		height   = 0x1e0
    		children = 0x9faefc8
    		0x9faefc8 (15CGUIFrameWindow) {
    			vtable   = 0x82eca68
    			unknown1 = 0x9faf038
    			unknown2 = 0x9
    			parent   = 0x9fae760
    			sibling  = (nil)
    			x        = 0x19
    			y        = 0xfe
    			width    = 0x75
    			height   = 0xc9
    			children = 0x9fbfab8
    			0x9fbfab8 (13CGUIComposite) {
    				vtable   = 0x82eeb28
    				unknown1 = 0x0
    				unknown2 = 0x0
    				parent   = 0x9faefc8
    				sibling  = (nil)
    				x        = 0xf
    				y        = 0xf
    				width    = 0x56
    				height   = 0xaa
    				children = 0x9faea40
    				0x9faea40 (14CGUITextButton) {
    					vtable   = 0x82ed388
    					unknown1 = 0x0
    					unknown2 = 0x0
    					parent   = 0x9fbfab8
    					sibling  = 0x9fae9b8
    					x        = 0x0
    					y        = 0x3c
    					width    = 0x56
    					height   = 0x14
    					label    = Access Account
    				}
    				0x9fae9b8 (14CGUITextButton) {
    					vtable   = 0x82ed388
    					unknown1 = 0x0
    					unknown2 = 0x0
    					parent   = 0x9fbfab8
    					sibling  = 0x9fae930
    					x        = 0x0
    					y        = 0x96
    					width    = 0x56
    					height   = 0x14
    					label    = Exit Game
    				}
    				0x9fae930 (14CGUITextButton) {
    					vtable   = 0x82ed388
    					unknown1 = 0x0
    					unknown2 = 0x0
    					parent   = 0x9fbfab8
    					sibling  = 0x9fae8a8
    					x        = 0x0
    					y        = 0x78
    					width    = 0x56
    					height   = 0x14
    					label    = Info
    				}
    				0x9fae8a8 (14CGUITextButton) {
    					vtable   = 0x82ed388
    					unknown1 = 0x0
    					unknown2 = 0x0
    					parent   = 0x9fbfab8
    					sibling  = 0x9fae820
    					x        = 0x0
    					y        = 0x5a
    					width    = 0x56
    					height   = 0x14
    					label    = Options
    				}
    				0x9fae820 (14CGUITextButton) {
    					vtable   = 0x82ed388
    					unknown1 = 0x0
    					unknown2 = 0x0
    					parent   = 0x9fbfab8
    					sibling  = 0x9fae798
    					x        = 0x0
    					y        = 0x1e
    					width    = 0x56
    					height   = 0x14
    					label    = Get Premium
    				}
    				0x9fae798 (14CGUITextButton) {
    					vtable   = 0x82ed388
    					unknown1 = 0x0
    					unknown2 = 0x0
    					parent   = 0x9fbfab8
    					sibling  = (nil)
    					x        = 0x0
    					y        = 0x0
    					width    = 0x56
    					height   = 0x14
    					label    = Enter Game
    				}
    			}
    		}
    	}
    }

  3. #3
    Looks nice Snowak also posted a similar break-down many years ago.

    There's a few more quirks and things you don't seem to have spotted yet, but based on what you've got so far I'd say you'll find them pretty easily.

  4. #4
    Senior Member
    Join Date
    Jun 2007
    Posts
    247
    I remember, he and I were both active years ago. Are you involved with the development of xenobot? I see they must have figured this all out as well.

  5. #5
    Administrator
    Join Date
    Mar 2007
    Posts
    1,723
    Nice write-up.

    Quote Originally Posted by *DEAD* View Post
    I remember, he and I were both active years ago. Are you involved with the development of xenobot? I see they must have figured this all out as well.
    He is the sole developer of XenoBot.

Posting Permissions

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