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

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
Inside of Context Menus
Page 1 of 2 12 LastLast
Results 1 to 10 of 12

Thread: Inside of Context Menus

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

    Inside of Context Menus

    Hi, I got bored again and I've decided to analyze and learn about how to customize context menus in the CipSoft standalone Windows client (also known as Tibia.exe). This tutorial is designed for intermediate cheaters, so if you have basic knowledge about cheating and you want to have an easier journey than me to learn about context menus, follow my findings reading on:

    In the following, we'll use Tibia 10.91, OllyDbg v1.10 and Cheat Engine 6.4.

    I would like to arrange everything in a single post, but, currently, this forum allows a max of four images per post, so you know, I need many of them to explain things and this is the reason I usually expand a tutorial throughout many posts.

    Outline



    1. Choosing a Context Menu and How to find Dialog pointer
    2. How to insert your own context menu items
    3. How to handle the action responsible for the user click on your added context menu items
    4. How to Code


    Choosing a Context Menu and How to find Dialog pointer



    In the client, there are different types of modal dialogs, triggered by different situations:

    • When the user hits the "Enter game" in the start screen;
    • When the user right clicks a message in the chatbox;
    • When the user clicks in the close window button, to exit, logout or simply by miss click;
    • When the user right clicks an item in the backpack;
    • When the user right clicks a player in the screen and so on.


    These are all examples of what Tibia calls modal dialogs. However, we are interested in a specific type of modal dialogs: Context Menus.




    In the following, this tutorial will use the context menu when the user right clicks his own character

    Now, we have a main goal: customize SCM (Self Context Menu).

    Usually, I'll make a statement and won't prove it, but you're welcome to verify whether it's true or not (it's a good exercise, because, in the future, most likely you'll need update things yourself - because I'm not your bitch to update it for you - due CipSoft changes in the client). Whenever I make such statement, I'll try to remember to decorate it with CIY (meaning Check It Yourself).

    CIY: In Tibia.exe, all dialogs are assigned to a global field (in the static memory) reserved for the dialogs.

    The process to find such dialog pointer address is easy:

    1. Open a Tibia client and log in;
    2. Open CE (Cheat Engine) and attach to your Tibia instance;
    3. Close all modal dialogs and do a 4-bytes search for 0;
    4. Right click your character to popup SCM. Once the SCM appears in the screen, select "Changed value" in CE and search again;
    5. Repeat the last two steps until you find a static address in the Tibia.exe module memory which looks like a pointer (well, you are NOT a beginner and you know what a pointer is). Hint: It's near GUI pointer


    So, we got Dialog Pointer address:


  2. #2
    Senior Member
    Join Date
    Jan 2012
    Posts
    417
    How to insert your own context menu items



    Now, it's time to have fun.

    We want to insert our own context menu items in the SCM, but to achieve that, we need to find the function responsible for context menu creation and the one responsible for adding buttons with text ("Look", "Use", "Browse Field", etc like the image above).

    One way to find the function responsible for context menu creation is placing a hardware breakpoint on write in the Dialog Pointer address when all dialogs are closed (Dialog Pointer is 0) and we open the SCM (Dialog Pointer != 0):

    1. Open a Tibia client and log in;
    2. Open OllyDbg, attach Olly to your Tibia instance and run (F9) when paused;
    3. Switch view to Tibia module (Right-click ollydbg central panel -> View -> Module 'Tibia') and let Olly analyze code (Ctrl + A);
    4. Close all modal dialogs;
    5. Right click in the Hex Dump (left-bottom panel) -> Go to -> Expression (Ctrl + G) and type the Dialog Pointer address found in the post above;
    6. Now that we are in the Dialog Pointer address, right click in the Hex Dump -> Breakpoint -> Hardware on write -> Dword;
    7. Right click your character to trigger SCM: Breakpoint has been triggered;
    8. Remove the hardware breakpoint in Olly: Debug (in the main menu at top) -> Hardware breakpoints -> Delete.


    We get a screen like that:


    CIY: The image shown above shows the function responsible for all modal dialogs creation.

    As we can see in the image, scrolling up a bit, we are inside of a switch-case. Jumping to the switch base and placing a breakpoint, we can trigger other dialogs and get the breakpoint hit.

    CIY: The green circle function shown above is responsible for SCM menu creation, passing its pointer to the blue circle function. The blue circle function is responsible to fill context menu with items (text buttons and separators).

    The blue circle function has a signature similar to the following (C++), not sure about the return type, because I didn't hook it:
    Code:
    typedef void (__thiscall* FillContextMenuWithItems)(void *_this, int cursorX, int cursorY, int clientWidth, int clientHeight);
    1. void *_this: Context Menu 'this' pointer passed in ecx register;
    2. int cursorX: cursor x position in the right-click time;
    3. int cursorY: cursor y position in the right-click time;
    4. int clientWidth: client width (client here means the main window content object), this is taken from the GUI pointer in the right-click time;
    5. int clientHeight: client height (taken from the GUI pointer) in the right-click time;


    Let's analyze FillContextMenuWithItems function, so select the call line and hit Enter on your keyboard:

    CIY:

    • In the beginning, the function does a lot of sanity check;
    • Scrolling down, the important part is responsible to format item text, find Player id, etc;
    • There is a key function responsible to insert each item as we can see below



    CIY: The green circle function above is responsible for adding text buttons, as well as separators. Its signature is the following:
    Code:
    typedef void (__thiscall* AddContextMenuItem)(void *_this, int eventId, const char *text, const char *shortcut);
    1. void *_this: Context Menu 'this' pointer passed in ecx register;
    2. int eventId: Each context menu item button has an id bound;
    3. const char *text: button text;
    4. const char *shortcut: button shortcut;


    Let's analyze AddContextMenuItem in the image below to understand how separators are added:

    CIY:
    • In the beginning, the function does a lot of sanity check like asserting button text is not null and has a length <= 0x32. Also asserts that if shortcut is set (can be null), it must have a length <= 0x14;
    • Scrolling down to the end of the function, the important part is responsible for the following things:
      • In the green box, there is a comparison checking
        Code:
        CMP BYTE PTR DS: [EBX + 30], 0
        At this point, EBX holds the pointer to the Context Menu. So, this is equivalent to: If the field in Context Menu at offset 0x30 is 0, then do something.

        Actually, whenever this field is set to true (1), execution will jump to the light blue box and execute a function (append Separator), and after that, set field back to 0;
      • In the dark blue circle, the function creates the text button object;
      • In the purple circle, the function assigns all data (eventId, text, shortcut) to the text button objected created in the dark blue circle and appends it to the Context Menu;
      • Note: So, if the Context Menu's field at offset 0x30 is 1, this function inserts a separator before the text button.



    We're all set to insert our own context menu items: Choose a text button you want to place your context menu item near (like the Set Outfit shown in the FillContextMenuWithItems function) and hook its AddContextMenuItem call. However, remember to set
    Code:
    *(((unsigned char *)_this) + 0x30) = 1
    before the original function to prepend a separator
    Last edited by Blequi; 03-26-2016 at 11:35 AM.

  3. #3
    Senior Member
    Join Date
    Jan 2012
    Posts
    417
    How to find the function responsible to handle user click on context menu items
    Part 1



    In the second section, we were interested to know the function responsible for context menu creation to get the function responsible to add context menu items. To do this, we had set a breakpoint in the Dialog Pointer address when no dialog was shown (Dialog Pointer = 0) and opening SCM to hit our breakpoint.

    This time, we'll use the reverse way: we'll open the SCM (Dialog Pointer != 0) and we are going to click any item in the list, let's say 'Set Outfit' button. Doing this way, the client sends a request outfit packet and closes the context menu (hitting the breakpoint) and hopefully, we can find the click handler function.

    Let's go:

    1. Open a Tibia client and log in;
    2. Open and attach Olly to your Tibia instance and run (F9) when paused;
    3. Switch view to Tibia module (Right-click ollydbg central panel -> View -> Module 'Tibia') and let Olly analyze code (Ctrl + A);
    4. Close all modal dialogs;
    5. Open SCM;
    6. Right click in the Hex Dump (left-bottom panel) -> Go to -> Expression (Ctrl + G) and type the Dialog Pointer address found in the first post;
    7. Now that we are in the Dialog Pointer address, right click in the Hex Dump -> Breakpoint -> Hardware on write -> Dword;
    8. Click 'Set Outfit' button (SCM will close and Breakpoint will hit);
    9. Remove the hardware breakpoint in Olly: Debug (in the main menu at top) -> Hardware breakpoints -> Delete.


    CIY: Once the breakpoint is hit, we are inside the function responsible to handle clicks in the client. As we can see in the image below, we are in the middle of a switch and navigating back to the switch base, we get this


    This switch (cases 0..C) is an enum telling the action state, described in TibiaAPI sources:

    Code:
        public enum ActionState : byte
        {
            None = 0,
            LeftClick = 1, // // left-click to walk or to use the client interface
            Left = LeftClick,    // walk etc
            RightClick = 2, // right-click to use an object such as a torch or an apple
            Right = RightClick,   // use
            InspectObject = 3, // left-click + right-click to see or inspect an object
            Inspect = InspectObject,
            MoveObject = 6, // dragging an object to move it to a new location
            Drag = MoveObject,
            UseObject = 7, // using an object such as a rope, a shovel, a fishing rod, or a rune
            Using = UseObject,   // in-use fishing shooting rune
            SelectHotkeyObject = 8, // selecting an object to bind to a hotkey from the "Hotkey Options" window
            TradeObject = 9, // using "Trade with..." on an object to select a player with whom to trade
            Trade = TradeObject,
            ClientHelp = 10, // // client mouse over tooltip help
            Help = ClientHelp,   // client help
            OpenDialogWindow = 11, // opening a dialog window such as the "Options" window, "Select Outfit" window, or "Move Objects" window
            PopupMenu = 12 // showing a popup menu with options such as "Invite to Party", "Set Outfit", "Copy Name", or "Set Mark"
        }
    Before continuing, set a breakpoint (F2) in the base of case B, C, because it's our target condition. Probably our client has been disconnected and we'll need to reload it, open SCM and click 'Set Outfit' button again.

    We get a screen like this


    Let's analyze what's happenning here:

    CIY:

    • In the base of case B, C: Context Menu is assigned to ecx register and if it's not null, jumps to the box in the bottom (as is our condition);
    • According to Snowak's tutorial (Tibia GUI structure) and *DEAD*'s tutorial (Calling methods on the tibia GUI structure), the first field of the GUI components points to the virtual table (a table of functions). Then, the instruction
      Code:
      MOV EAX, DWORD PTR DS: [ECX]
      is assigning the vtable to EAX;

    • Code:
      00C49323  |> 8B01           MOV EAX,DWORD PTR DS:[ECX] ; Assigns Context Menu vtable pointer to EAX
      00C49325  |. FF75 08        PUSH DWORD PTR SS:[EBP+8] ; Pushes the second parameter of this function on the stack
      00C49328  |. 2B71 18        SUB ESI,DWORD PTR DS:[ECX+18] ; ESI holds cursor Y, DWORD PTR DS:[ECX+18] is Context Menu Y position, so it's Y offset relative to the Context Menu top border, because I'm assuming you clicked 'Set Outfit', right?
      00C4932B  |. 56             PUSH ESI
      00C4932C  |. 2B79 14        SUB EDI,DWORD PTR DS:[ECX+14] ; EDI holds cursor X, DWORD PTR DS:[ECX+14] is Context Menu X position, so it's X offset relative to the Context Menu left border, because I'm assuming you clicked 'Set Outfit', right?
      00C4932F  |. 57             PUSH EDI
      00C49330  |. FF50 5C        CALL DWORD PTR DS:[EAX+5C] ; The function field in the offset 0x5C from Context Menu vtable


    Stepping into this call, we get


    There is not much to comment, basically forwarding the call, so step into the next call


    CIY: This function is really useful. Basically, this function tries to fit the clicked position in one of the item rectangles. In case of a match, it accesses the Context Menu Item vtable and calls the function field at offset 0x5C.
    • The green circle is the main loop looking for a match;
    • The blue box is saying: "Hey, the loop found a match, let's call the Context Menu Item vtable function at offset 0x5C".

  4. #4
    Senior Member
    Join Date
    Jan 2012
    Posts
    417
    How to find the function responsible to handle user click on context menu items
    Part 2



    Step into the call and we get this


    CIY: At the beginning of this function, the 'this' pointer is obviously the matched item ('Set Outfit') button if we clicked correctly. I'm not sure what's happenning here, but in the green box, ECX is increased 0x6C and thus, the field at Context Menu Item's 0x6C offset is reserved for its parent pointer (the Context Menu pointer), so this looks like invoking the Context Menu in some way.

    Let's step into again.


    Let's analyze what's happenning here

    CIY:

    Code:
    00CF90D1  |. 8BD1           MOV EDX,ECX ; ECX contains Context Menu pointer, so it's copying the pointer to EDX
    00CF90D3  |. C745 FC 000000>MOV DWORD PTR SS:[EBP-4],0 ; Assigning 0 to the first stack element
    00CF90DA  |. 8B0A           MOV ECX,DWORD PTR DS:[EDX] ; Assigning the Context Menu to ECX;
    00CF90DC  |. 85C9           TEST ECX,ECX ; Check Context Menu
    00CF90DE  |. 74 0F          JE SHORT Tibia.00CF90EF
    00CF90E0  |. 8B01           MOV EAX,DWORD PTR DS:[ECX] ; Assigns vtable pointer to EAX
    00CF90E2  |. FF72 04        PUSH DWORD PTR DS:[EDX+4] ; Push on stack the value at address Context Menu + 0x4 = 0x6C + 0x4 = Context Menu Item + 0x70 = eventId offset from Context Menu Item
    00CF90E5  |. FF50 78        CALL DWORD PTR DS:[EAX+78] ; Calls the function field at offset 0x78 in the vtable
    Step into again to get this


    CIY: Lovely news. A switch on eventIds and our clicked 'Set Outfit' button eventId is the first stack element (blue circle).

    Note:
    • Scrolling down to the function end, we can see a
      Code:
      RETN 4
      In the __thiscall calling convention, this means the function has a single parameter 4-bytes long (a DWORD, for instance) and it's the clicked button eventId;
    • From a hooking perspective, this function is perfect to hook, because the sole parameter is the button eventId. This means we can create buttons with any id other than those specified in the switch, handling ourselves such cases and hide it from the original function. On the other hand, we can forward these already used eventIds to the original function;
    • If we are curious enough, we can check that each function in each case just prepares the packet and sends them to the server.


    CIY: So, this function is our ContextMenuItemClickCallback and its signature is

    Code:
    typedef void (__thiscall* ContextMenuItemClickCallback)(void *_this, int eventId);
    • void *_this: Context Menu 'this' pointer
    • int eventId: Clicked item eventId


    All we have to do is hook this function and place our own handler function.

    All in all, we have found everything we were looking for. Now, it's time to get our hands dirty with some code.

  5. #5
    Senior Member
    Join Date
    Jan 2012
    Posts
    417
    How to Code



    In the first place, we have to hook AddContextMenuItem described earlier.


    Code:
    typedef void (__thiscall* AddContextMenuItem)(void *_this, int eventId, const char *text, const char *shortcut);
    How can we hook a __thiscall function?

    In any case, you can code your function in ASM using __declspec(naked), but I prefer to code in C++ rather than ASM.

    We can check calling conventions in the wiki: x86 Calling Conventions

    Usually, __thiscall is a class instance call, but since we don't have the class definition nor the this pointer address, we use a fake __fastcall.

    We can see that __fastcall is much like the same thing as __thiscall, but the second parameter is passed in EDX register rather than the stack.

    Thus we can define our function this way:

    Code:
    void __fastcall CustomAddContextMenuItem(void *_this, void *_edx, int eventId, const char *text, const char *shortcut)
    {
    	// Code goes here
    	// We simply don't use the param 'void *_edx'
    }
    Now, you hook the call in AddContextMenuItem as we're used to.

    And, how can we hook the ContextMenuItemClickCallback function described earlier


    Code:
    typedef void (__thiscall* ContextMenuItemClickCallback)(void *_this, int eventId);
    We have a few ways, but I'm going to show you a really simple one to solve the problem.

    As said earlier, this function invokes the Context Menu vtable function field at offset 0x78. Let's take a look at the Context Menu vtable:


    As we can see, in the left part, it's the view of Context Menu vtable and all the addresses are static. In the red circle at left, the function address we were looking for. In the right part, the process to find the function address. Since the Protect mode is Read Only (in orange box), we uncheck Writable option when searching in CE.

    The hook became obvious: replace original function address with our custom function address and we're done.

    Wrapping up everything explained here, check the following complete sample that writes something about the clicked item.

    Expected result


    Instructions:

    • In the callback function of each item we created, we cannot call something like MessageBox, because it's chained inside the window message loop... this will result the callback get called two times, which we don't want, that's why I'm writing something to a file;
    • So, everytime you hit some newly created button, check your outputFileName defined at top to see what has been written;
    • To execute this code, build it as DLL with MSVC 2015 or newer;
    • Open Tibia and attach CE to your Tibia instance;
    • Open CE Memory Viewer;
    • Tools (in the menu at top) -> Inject DLL -> Choose 'Execute' function to be executed right after injection;
    • Log in, open SCM and test it.


    The sample code provided here was written in C++ with MSVC 2015.

    Code:
    #include <Windows.h>
    #include <map>
    #include <functional>
    #include <iostream>
    #include <fstream>
    
    char outputFileName[] = "C:\\Temp\\item_clicked_output.txt";
    
    #define SET_OUTFIT_ADDRESS 0x90840
    #define CONTEXT_MENU_ITEM_CLICK_CALLBACK 0x478A6C
    
    // Helper functions
    
    // Thanks to Stepler for HookCall function
    void HookCall(DWORD dwCallAddress, DWORD dwNewAddress, LPDWORD pOldAddress)
    {
    	DWORD dwOldProtect, dwNewProtect, dwOldCall, dwNewCall;
    	BYTE call[5] = { 0xE8, 0x00, 0x00, 0x00, 0x00 };
    
    	dwNewCall = dwNewAddress - dwCallAddress - 5;
    	memcpy(&call[1], &dwNewCall, 4);
    
    	HANDLE process = GetCurrentProcess();
    
    	VirtualProtectEx(process, (LPVOID)(dwCallAddress), sizeof(call), PAGE_READWRITE, &dwOldProtect);
    	if (pOldAddress)
    	{
    		memcpy(&dwOldCall, (LPVOID)(dwCallAddress + 1), 4);
    		*pOldAddress = dwCallAddress + dwOldCall + 5;
    	}
    	memcpy((LPVOID)(dwCallAddress), &call, sizeof(call));
    	VirtualProtectEx(process, (LPVOID)(dwCallAddress), sizeof(call), dwOldProtect, &dwNewProtect);
    }
    
    // Swaps original callback address with our custom callback address in the vtable
    void HookVtableContextMenuItemClickCallback(DWORD dwVtableFunctionAddress, DWORD dwNewAddress, LPDWORD pOldAddress)
    {
    	DWORD dwOldProctect, dwNewProtect;
    	HANDLE currentProcess = GetCurrentProcess();
    
    	VirtualProtectEx(currentProcess, (LPVOID)(dwVtableFunctionAddress), sizeof(DWORD), PAGE_READWRITE, &dwOldProctect);
    	if (pOldAddress)
    	{
    		memcpy(pOldAddress, (LPVOID)(dwVtableFunctionAddress), sizeof(DWORD));
    	}
    	memcpy((LPVOID)(dwVtableFunctionAddress), &dwNewAddress, sizeof(DWORD));
    	VirtualProtectEx(currentProcess, (LPVOID)(dwVtableFunctionAddress), sizeof(DWORD), dwOldProctect, &dwNewProtect);
    }
    // end of helper functions
    
    // Helper variables
    HINSTANCE _dll;
    DWORD _tibiaBaseAddress;
    
    // functions prototypes
    typedef void(__thiscall* AddContextMenuItem)(void *_this, int eventId, const char *text, const char *shortcut);
    typedef void(__thiscall* ContextMenuItemClickCallback)(void *_this, int eventId);
    
    // will hold original Tibia functions;
    AddContextMenuItem originalAddContextMenuItem;
    ContextMenuItemClickCallback originalContextMenuItemClickCallback;
    
    // ContextMenuItem
    class ContextMenuItem
    {
    public:
    	ContextMenuItem();
    
    	ContextMenuItem(
    		int eventId,
    		const std::string &text,
    		const std::string &shortcut,
    		bool prependsSeparator,
    		std::function<void()> callback);
    
    	int getEventId() const { return _eventId; }
    	const char *getText() const { return _text; }
    	const char *getShortcut() const { return _hasShortcut ? _shortcut : nullptr; }
    	bool prependsSeparator() const { return _prependsSeparator; }
    	std::function<void()> getCallback() const { return _callback; }
    
    private:
    	int _eventId;
    	char _text[0x32];
    	char _shortcut[0x14];
    	bool _hasShortcut;
    	bool _prependsSeparator;
    	std::function<void()> _callback;
    };
    
    ContextMenuItem::ContextMenuItem()
    {
    
    }
    
    ContextMenuItem::ContextMenuItem(
    	int eventId,
    	const std::string &text,
    	const std::string &shortcut,
    	bool prependsSeparator,
    	std::function<void()> callback)
    
    {
    	_eventId = eventId;
    
    	int textLen = text.size();
    	if (textLen == 0)
    	{
    		throw std::exception("text cannot be empty");
    	}
    
    	if (textLen >= sizeof(_text))
    	{
    		throw std::exception("text is too long");
    	}
    
    	text.copy(_text, textLen, 0);
    	_text[textLen] = '\0';
    
    	_hasShortcut = false;
    
    	int shortcutLen = shortcut.size();
    	if (shortcutLen == 0)
    	{
    		_shortcut[0] = '\0';
    	}
    	else
    	{
    		if (shortcutLen >= sizeof(_shortcut))
    		{
    			throw std::exception("shortcut is too long");
    		}
    
    		shortcut.copy(_shortcut, shortcutLen, 0);
    		_shortcut[shortcutLen] = '\0';
    		_hasShortcut = true;
    	}
    
    	_prependsSeparator = prependsSeparator;
    	_callback = callback;
    }
    
    // will hold our custom context menus
    std::map<int, ContextMenuItem> customContextMenus;
    
    void __fastcall customAddContextMenuItem(void *_this, void *_edx, int eventId, const char *text, const char *shortcut)
    {
    	// We simply don't use the param 'void *_edx'
    
    	// store a pointer to prependSeparator
    	PBYTE prependSeparatorPtr = ((PBYTE)_this) + 0x30;
    	
    	// save initial prependSeparator state
    	BYTE prependSeparator = *prependSeparatorPtr;
    
    	for (auto keyValuePair = customContextMenus.begin(); keyValuePair != customContextMenus.end(); keyValuePair++)
    	{
    		auto current = keyValuePair->second;
    
    		// must be set before the function call to prepend a separator
    		*prependSeparatorPtr = current.prependsSeparator() ? 1 : 0;
    
    		// call the original function
    		originalAddContextMenuItem(_this, current.getEventId(), current.getText(), current.getShortcut());
    	}
    
    	// restore initial prependSeparator state
    	*prependSeparatorPtr = prependSeparator;
    
    	// appends the hooked context menu item
    	originalAddContextMenuItem(_this, eventId, text, shortcut);
    }
    
    void __fastcall customContextMenuItemClickCallback(void *_this, void *_edx, int eventId)
    {
    	// We simply don't use the param 'void *_edx'
    
    	auto keyValuePair = customContextMenus.find(eventId);
    
    	// if not found
    	if (keyValuePair == customContextMenus.end())
    	{
    		// forward to the original callback function
    		originalContextMenuItemClickCallback(_this, eventId);
    	}
    	else
    	{
    		// call our own item-defined callback and don't forward to the original callback function
    		auto item = keyValuePair->second;
    		item.getCallback()();
    	}
    }
    
    extern "C" __declspec(dllexport)
    void Execute()
    {
    	ContextMenuItem firstItem(1, "Foo", "", true, [&]() -> void
    	{
    		std::ofstream file;
    		file.open(outputFileName, std::ios::out | std::ios::app);
    		file << "You clicked the Foo item!" << std::endl;
    		file.close();
    	});
    
    	ContextMenuItem secondItem(2, "Bar", "Ctrl+HUEUHE", false, [&]() -> void
    	{
    		std::ofstream file;
    		file.open(outputFileName, std::ios::out | std::ios::app);
    		file << "You clicked the Bar item!" << std::endl;
    		file.close();
    	});
    
    	customContextMenus.clear();
    	customContextMenus[firstItem.getEventId()] = firstItem;
    	customContextMenus[secondItem.getEventId()] = secondItem;
    
    	HookCall(
    		_tibiaBaseAddress + SET_OUTFIT_ADDRESS, 
    		(DWORD)(&customAddContextMenuItem), 
    		(LPDWORD)(&originalAddContextMenuItem));
    
    	HookVtableContextMenuItemClickCallback(
    		_tibiaBaseAddress + CONTEXT_MENU_ITEM_CLICK_CALLBACK,
    		(DWORD)(&customContextMenuItemClickCallback),
    		(LPDWORD)(&originalContextMenuItemClickCallback));
    }
    
    BOOL WINAPI DllMain(
    	_In_ HINSTANCE hinstDLL,
    	_In_ DWORD     fdwReason,
    	_In_ LPVOID    lpvReserved
    	)
    {
    	_dll = hinstDLL;
    	_tibiaBaseAddress = (DWORD)GetModuleHandle(NULL);
    
    	return TRUE;
    }
    Last edited by Blequi; 03-26-2016 at 11:33 AM.

  6. #6
    Junior Member
    Join Date
    Mar 2016
    Posts
    9
    One word: Awesome!
    Most well detailed tutorial I've ever seen at tpforums. Well done man!
    I've been thinking about doing a detailed tutorial like that one showing how to traverse the whole Tibia GUI. But I've been too busy lately. Maybe a next time.
    Last edited by Brunocg; 03-26-2016 at 03:41 AM.

  7. #7
    Junior Member
    Join Date
    Nov 2013
    Posts
    5
    Love your tutorials, really appreciate them!

  8. #8
    Super Moderator klusbert's Avatar
    Join Date
    Dec 2007
    Posts
    1,201
    Really great job man! Are you actually working on any project or do you do this just for fun?
    How to find battlelist address --> http://tpforums.org/forum/thread-8146.html
    Updating addresses --> http://tpforums.org/forum/thread-8625.html
    DataReader --> http://tpforums.org/forum/thread-10387.html

  9. #9
    Senior Member
    Join Date
    Jan 2012
    Posts
    417
    Quote Originally Posted by klusbert View Post
    Really great job man! Are you actually working on any project or do you do this just for fun?
    Thanks for the kind words. I don't plan to get involved in cheating projects anymore. I just wanna have fun and help beginners.

    My main interest now is provide content that was left before NeoBot era and no one talks about, like modifying Tibia.exe through dll injection.

    I do this mainly for a few reasons:


    1. I'm tired of questions like: "How can I bla bla bla with Mouse Clicks and Keyboard?"
    2. It is not easy for beginners to figure out alone, so they need a step-by-step guide
    3. There are not many people releasing such cool things in a long time

  10. #10
    Super Moderator klusbert's Avatar
    Join Date
    Dec 2007
    Posts
    1,201
    The reason why I am asking is simply becouse I want to get tibiaAPi rolling again. Context menus was one thing that was hard for me to update.

    The most pain with tibiaApi is to update packets.
    How to find battlelist address --> http://tpforums.org/forum/thread-8146.html
    Updating addresses --> http://tpforums.org/forum/thread-8625.html
    DataReader --> http://tpforums.org/forum/thread-10387.html

Posting Permissions

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