Programming with the IceOps Plugin API

More than a decade ago now the guys of the IceOps-Team developed a server extension for the stock MW1 game offering a programmable plugin API. In this article I will detail the process of writing a plugin to prevent IP and URL advertisement in a game server.

The plugin API offers a broad range of functions for managing cvars, interacting with registered commands, file operations and so on.

As well as that, it offers callback functions for game events:

PCL void OnPreFastRestart();
PCL void OnExitLevel();
PCL void OnPostFastRestart();
PCL void OnPreGameRestart(int savepersist);
PCL void OnPostGameRestart(int savepersist);
PCL void OnSpawnServer();

First things first, compile suitable regexes for later use:

qboolean create_regex()
{
    return regcomp(&regex.ip, REG_IP, REG_EXTENDED) == 0 &&
           regcomp(&regex.url, REG_URL, REG_EXTENDED) == 0;
}

We indicate to the server if they fail to compile in the OnInit() function:

PCL int OnInit()
{
    if (create_regex() == qfalse)
    {
        log_info("AdStop: Failed to compile RegEx, exiting...");
        return 1;
    }

    ...
}

Then we register our plugin cvars, this gives users of the plugin the ability to configure its behaviour from config files:

PCL int OnInit()
{
    ...

    cvars.sub = Plugin_Cvar_RegisterString(..., ..., ...);
    cvars.sub_ip = Plugin_Cvar_RegisterBool(..., ..., ...);
    cvars.sub_url = Plugin_Cvar_RegisterBool(..., ..., ...);

    return 0;
}

Before checking for regex matches we must first clean the incoming message of colour codes. If we don't do this coloured messages may fail a regex test.

The following snippet uses pointer arithmetic to step through the string until we meet ^[0-9], skips those characters if present, otherwise the char at p is copied into the position at q. The message string is effectively cleaned in situ.

ptrdiff_t remove_colours(char *message)
{
    char *p, *q;

    p = q = message;
    while (*p)
    {
        if (*p == '^' && *(p + 1) && isdigit(*(p + 1)))
        {
            p++;
        }
        else if (isprint(*p))
        {
            *q++ = *p;
        }
        p++;
    }
    *q = '\0';

    return q - message;
}

Finally, if a match occurs we overwrite the original message. All of this is handled in the APIs OnMessageSent callback function:

PCL void OnMessageSent(char *msg, int slot, qboolean *show, int mode)
{
    remove_colours(msg);

    enum match_type match = matches(msg);
    if (match == IP  || match == URL)
    {
        snprintf(message,
                 MAX_SAY_TEXT,
                 Plugin_Cvar_GetString(cvars.sub));
    }

    ...
}

Once the plugin is loaded abusers attempting to advertise on the server will have their messages replaced.

AdStop Plugin

Note. if the replacement text is set to a blank string the users message will not show at all, not even a prompt indicating an attempted message.

Subscribe to this blog's RSS feed