Page 1 of 1

Server stat tool and script

PostPosted: 26 Jul 2011, 01:56
by orbitaldecay
Hey all. I wrote a simple program for querying servers for connected players, game time remaining, etc on version 1.1. Feel free to do whatever you want with it. I'm using it with some bash scripts to make graphs of my server usage. It only compiles under *nix (gcc retool.c -o retool), but shouldn't be hard to port if you know any C.

Basically you use it like
{l Code}: {l Select All Code}
./retool <server ip> <server port> <fields>

where fields is a string of numbers representing stats you'd like it to spit out. See below for example.

Edit: Quick note, the port which the server receives queries on is +1 from the game port. For example, play.redeclipse.net (74.82.3.216) runs on game port 28801. You would direct queries to port 28802. If you wanted to know the number of connected clients and the maximum number of connected clients, you could run "./retool 74.82.3.216 28802 06".

{l Code}: {l Select All Code}
/* retool.c - a server stat tool for red eclipse v1.1 */

#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <string.h>
#include <stdio.h>

void parse_stats(unsigned char *buf, size_t len, int *stats);

int main(int argc, char **argv)
{
    struct sockaddr_in  si_other;
    int                 slen;
    char                buf[64];
    int                 stats[10];
    int                 sock;
    int                 result;
    int                 i, k;

    if (argc != 4)
    {
        fprintf(stderr, "usage: ./retool <server ip> <port> <stats>\n");
        exit(1);
    }

    slen = sizeof(si_other);
    sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (sock == -1)
    {
        fprintf(stderr, "Cannot create socket.\n");
        exit(1);
    }

    memset((char *) &si_other, 0, slen);

    si_other.sin_family = AF_INET;
    si_other.sin_port = htons(atoi(argv[2]));
    if (inet_aton(argv[1], &si_other.sin_addr) == 0)
    {
        fprintf(stderr, "Failed to locate %s!.\n", argv[1]);
        exit(1);
    }
   
    /* ^shrugs^ - it works */
    buf[0] = 0x81;
    buf[1] = 0xE1;
    buf[2] = 0x38;
    buf[3] = 0x02;

    result = sendto(sock, buf, 4, 0, (struct sockaddr *)&si_other, slen);
    if (result == -1)
    {
        fprintf(stderr, "Sending failed.\n");
        exit(1);
    }

    result = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr *)&si_other, &slen);
    if (result == -1)
    {
        fprintf(stderr, "Receiving failed.\n");
        exit(1);
    }

    parse_stats(buf, sizeof(buf), stats);

    char *field = argv[3];
    for (i = 0; i < strlen(argv[3]); i++)
    {

    /*
        Fields:
   
        0   Connected Clients
        1   Always equal to 8
        2   Game Version
        3   Game Mode
        4   Mutators
        5   Time Remaining (in seconds)
        6   Max Clients
        7   Passworded?
        8   Numgamevars
        9   Numgamemods
    */

        if ((field[i] >= '0') || (field[i] <= '9'))
            printf("%d ", stats[field[i] - '0']);
    }
    printf("\n");
    fflush(stdout);
    exit(0);
}


/* See engine/server.cpp line 161 of v1.1 */
void parse_stats(unsigned char *buf, size_t len, int *stats) {

    int             i, c, n, k;
   
    i = 4;
    k = 0;

    while ((k < 10) && (i < len))
    {
        c = (char)buf[i];
        i++;
        if ((c == -128) && (i + 2 < len))
        {
            stats[k] = buf[i];
            stats[k] |= buf[i + 1] << 8;
            i+=2;
        }
        else if ((c == -127) && (i + 4 < len))
        {
            stats[k] = buf[i];
            stats[k] |= buf[i + 1] << 8;
            stats[k] |= buf[i + 2] << 16;
            stats[k] |= buf[i + 3] << 24;
            i+=4;
        }
        else
        {
            stats[k] = c;
        }
        k++;
    }
}


Example bash script:
{l Code}: {l Select All Code}
#!/bin/bash

IP=$1
PORT=$2

# Which fields to include in the output.  Fields are output in the order
# in which they are provided.  They correspond to the following attributes:
#
#    0   Connected Clients
#    1   Always equal to 8
#    2   Game Version
#    3   Game Mode
#    4   Mutators
#    5   Time Remaining (in seconds)
#    6   Max Clients
#    7   Does server have password?
#    8   Numgamevars
#    9   Numgamemods

# This will output connected players, max players, time remaining (in that order)
FIELDS="065"

STATS="$(./retool $IP $PORT $FIELDS)"
printf "Players %d/%d (%ds remaining)\n" ${STATS[0]} ${STATS[1]} ${STATS[2]}
exit 0

Re: Server stat tool and script

PostPosted: 26 Jul 2011, 02:50
by riidom
I dont understand much of that, but would it be possible with that to make a server list (like you said, with player number, time left etc - or in other words, like ingame in server menu) that is watchable from redeclipse.net ?

Because... that would be pretty cool I think! ;)

Re: Server stat tool and script

PostPosted: 26 Jul 2011, 02:57
by orbitaldecay
Sure, the code I wrote doesn't talk to the master server at all, but that wouldn't be hard to write.

Re: Server stat tool and script

PostPosted: 26 Jul 2011, 09:53
by sireus
Nice! I wrote something like that in Perl, but C is better :D
orbitaldecay {l Wrote}:
{l Code}: {l Select All Code}
/* ^shrugs^ - it works */


Yeah, you can send anything you like, 1 byte is sufficient if you don't actually use it. AFAIK, the serverbrowser uses it to measure ping (btw, that's something you could do too ;) )
You probably know that already, but you can also retrieve the map name, server description, and a list with the names of all players on the server. That's the stuff after numgamemods.

Re: Server stat tool and script

PostPosted: 26 Jul 2011, 10:43
by qreeves

Re: Server stat tool and script

PostPosted: 27 Jul 2011, 13:19
by orbitaldecay
Sweet, thanks :D