Q3 Server Polling
For a time, I had a Quake 3 server running on my file and print server here, but I took it off because it ran slow on such an old machine, it ate my bandwidth, and it caused lots of spurious false positives in my firewall logs. In any case, I had written a cool PHP script to poll the server and see who was on it at any given time. There are a couple tricks involved. The raw information can be gleaned from id’s own pages about the protocol, but I’ve lost that link. The beauty of my system is that you can use the Perl script at the command line (on any system that runs Perl, which is all of them), or you can embed it in a web page, like I did.
#!/usr/bin/perluse Getopt::Std;use IO::Socket;
getopt ('ph');
$port = $opt_p;
$host = $opt_h;
$socket = IO::Socket::INET->new(Proto => "udp", PeerPort => $port, PeerAddr => $host );
# This sends an "Out of Band" UDP packet to the server, meaning the
# first 4 characters are hex FF's, followed by the command to get
# the server's status. What follows is a long string parsed by
# backslashes that yield the server settings and their values. After
# that comes a caret and at sign, then a line (terminated by another
# caret and at sign) for every player.
$socket->send("xffxffxffxffgetstatus");
# You need the buffer limit here because there is no "end of packet" # mark of any kind. This will just read up to that many characters.
$socket->recv($status, 2048);
# So this gets the xxxxstatusResponse from the server in @status_array[0], # and the server variables into @status_array[1], and the players in# @status_array[2 .. LAST]. Just throw away the [0] one we don't care.
@status_array = split (/x0a/, $status);
# This is seriously cool, but seriously whacked out for understanding.
# It sorts the players in order of kills.
@players = sort ({$b <=> $a} @status_array[2 .. $#status_array]);
if ($#players == -1) {
print ("<h5 style='text-align: center'>No one is playing right now.</h5>");
exit;
}
print ("<h2>Current Players</h2>");
print ("<table align="center" width="80%" border=1>");
print ("<tr><th>Kills</th><th>Ping</th> <th>Nickname</th></tr>");
foreach $player (@players) {
$_ = $player;
m/^(d+)s(d+)s(.*)$/;
print ("<tr><td>", $1, "</td><td>", $2, "</td><td>", $3, "</td></tr>");
}
print ("</table>");
# This is a steady stream of value pairs that must be looped over in 2's
# Upshot: don't try to sort this!
@rules = split(//, $status_array[1]);
print ("<h2>Server Rules</h2>");
print ("<table align="center" width="80%" border=1>");
print ("<tr><th>Variable</th><th>Setting</th></tr>");
for ($counter = 1; $counter <= $#rules; $counter += 2) {
print ("<tr><td>", $rules[$counter], "</td><td>", $rules[$counter+1], "</td></tr>");
}
print ("</table>");
And then you can just include a call to the script within your web page, like so:
<?php system ("scripts/q3status.pl -p 27960 -h excelsior"); ?>
Got a good idea in the middle of the day. I know I shouldn’t have, but I updated the format of the output.