Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Newmenus: Add menu timeouts (+ extra bugfix) #21

Merged
merged 10 commits into from May 27, 2014

Conversation

Nextra
Copy link
Contributor

@Nextra Nextra commented May 25, 2014

This series of patches adds timeouts to newmenus, working similar to those of show_menu. Fixes bug 2974.

When a menu is acted upon after the specified timeout has run out it will pass a new MENU_TIMEOUT status code into the menu handler. Actions are: player disconnect, selecting an item, cancelling/destroying the menu in the plugin, sending a different menu or using get_user_menu on a player with an open menu.

Just like with the old menus timeouts are not actively polled, and MENU_TIMEOUT is not guaranteed to fire directly after the time has run out. Also get_user_menu will detect timeouts and reset the menu flags, which is why it also has to trigger the callback.

I discovered another issue when playing around with newmenus. Using the old show_menu when a newmenu was opened just reset CPlayer::newmenu without actually triggering the menu callback. This could result in menu handle leaks and is theoretically even client exploitable. This is also fixed.

This is the plugin I used to test the various possible occurances of MENU_TIMEOUT and verify that old behavior is not broken.

#include <amxmodx>

new last[33] = {-1, ...};

public plugin_init() {
    register_clcmd("menu_display", "menudisplay");
    register_clcmd("menu_destroy", "menudestroy");
    register_clcmd("menu_cancel", "menucancel");
    register_clcmd("show_menu", "menushow");
    register_clcmd("get_user_menu", "menuget");
}

public menudisplay(const client) {
    if (read_argc() != 2 || client == 0) {
        return PLUGIN_HANDLED;
    }

    new arg[32];
    read_argv(1, arg, charsmax(arg));
    new time = max(strtol(arg), -1);

    new menu = menu_create("Menu Test", "_handler");
    menu_additem(menu, "Test");
    menu_display(client, menu, _, time);

    last[client] = menu;

    return PLUGIN_HANDLED;
}

public _handler(const client, const menu, const item) {
    server_print("_handler(%d, %d, %d)", client, menu, item);
    client_print(client, print_console, "_handler(%d, %d, %d)", client, menu, item);

    last[client] = -1;
    menu_destroy(menu);
}

public menushow(const client) {
    show_menu(client, MENU_KEY_1, "Test");
    return PLUGIN_HANDLED;
}

public menudestroy(const client) {
    if (last[client] != -1) {
        menu_destroy(last[client]);
    }

    return PLUGIN_HANDLED;
}

public menucancel(const client) {
    if (last[client] != -1) {
        menu_cancel(client);
    }

    return PLUGIN_HANDLED;
}

public menuget(const client) {
    new menu, keys;
    get_user_menu(client, menu, keys);
    return PLUGIN_HANDLED;
}

show_menu simply resets CPlayer::newmenu. The menu callback is never fired and the plugin never informed that the menu has been closed. This can result in leaking menu handles. Using "menuselect 10" on the client is not an appropriate solution because it is possible to construct newmenus that contain 10 valid items.
@Arkshine Arkshine assigned dvander and unassigned dvander May 25, 2014
@Arkshine
Copy link
Member

This looks good for me.

@dvander , if you have some thoughts on it before I merge this, please let us hear (because of your answer in bug 2974, in case we may missed something as Nextra's implementation is fairly light (if you remember!))

@dvander
Copy link
Member

dvander commented May 27, 2014

It looks good at a glance - thank you for abstracting a lot of that previous gunk into Close(), that was a good idea. If you wanted to take another step you could factor out another antipattern that was present:

Menu *get_menu_by_id(int id)
{
    if (id < 0 || size_t(id) >= g_NewMenus.size() || !g_NewMenus[id])
        return NULL;
    return g_NewMenus[id];
}

...
if (Menu *pMenu = get_menu_by_id(pPlayer->menu)) {
    ...
}

@Nextra
Copy link
Contributor Author

Nextra commented May 27, 2014

Good idea. Done.

Arkshine added a commit that referenced this pull request May 27, 2014
Newmenus: Add menu timeouts (+ extra bugfix)
@Arkshine Arkshine merged commit a828ee8 into alliedmodders:master May 27, 2014
@Nextra Nextra deleted the newmenus branch May 27, 2014 10:55
Arkshine pushed a commit that referenced this pull request Jun 19, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants