Client-Lobby Protocol Parsers
Here are some parsers/constructors to help communicate with the lobby using the Client-Lobby_Protocol
PHP
By PerroAZUL
It has a function RequestServers. You can pass it an array with the filters you want, and they are optional which means you don't need to write all the filters. The filters you can set are these:
dedicated: true/false/undefined noPassworded: true/false/undefined noBots: true/false/undefined survival: true/false/undefined realistic: true/false/undefined noBonuses: true/false/undefined advance: true/undefined weapMod: true/undefined notFull: true/false/undefined notEmpty: true/false/undefined battleye: true/false/undefined version: XY for 1.X.Y gametype: -1 to 6 name: can be part of the name (case insensitive) country: 2 chars code linux: true/false/undefined connection: 0/1/2/3 (modem/ISDN/DSL/T1) ip: *.*.*.* format port: number map: name or part of the name (case insensitive)
Here's the code:
<?php
define('SOLDATLOBBY_DEFAULT_VERSION', '42');
function ParseServerPacket(&$packet)
{
$params = explode("\n", $packet);
$params = explode('©', $params[0]);
if ($params[0] != 'g')
return null;
$server['version'] = $params[1];
$server['ip'] = $params[2];
$server['port'] = $params[3];
$server['gamestyle'] = $params[4];
$server['players'] = $params[5];
$server['maxplayers'] = $params[6];
$server['map'] = $params[7];
$server['name'] = $params[8];
$server['bots'] = $params[9];
$server['bonusfreq'] = $params[10];
$server['respawn'] = $params[11];
$server['connection'] = $params[12];
$server['survival'] = $params[13];
$server['realistic'] = $params[14];
$server['dedicated'] = $params[15];
$server['linux'] = $params[16];
$server['passworded'] = $params[17];
$server['battleye'] = $params[18];
$server['country'] = $params[19];
return $server;
}
function RequestServers($filters = null)
{
// validate parameters
$dedicated = isset($filters['dedicated']) && $filters['dedicated'] ? 1 : 0;
$noPassworded = isset($filters['noPassworded']) && $filters['noPassworded'] ? 1 : 0;
$noBots = isset($filters['noBots']) && $filters['noBots'] ? 1 : 0;
$survival = isset($filters['survival']) && $filters['survival'] ? 1 : 0;
$realistic = isset($filters['realistic']) && $filters['realistic'] ? 1 : 0;
$noBonuses = isset($filters['noBonuses']) && $filters['noBonuses'] ? 1 : 0;
$advance = isset($filters['advance']) && $filters['advance'] ? 1 : 0;
$weapMod = isset($filters['weapMod']) && $filters['weapMod'] ? 1 : 0;
$notFull = isset($filters['notFull']) && $filters['notFull'] ? 1 : 0;
$notEmpty = isset($filters['notEmpty']) && $filters['notEmpty'] ? 1 : 0;
$battleye = isset($filters['battleye']) && $filters['battleye'] ? 1 : 0;
$version = SOLDATLOBBY_DEFAULT_VERSION;
if (isset($filters['version']) && is_numeric($filters['version']) && strlen("$filters[version]") == 2)
$version = $filters['version'];
$gametype = -1;
if (isset($filters['gametype']) && is_numeric($filters['gametype']) && $filters['gametype'] >= -1 && $filters['gametype'] <= 6)
$gametype = $filters['gametype'];
$name = null;
if (isset($filters['name']))
$name = $filters['name'];
$country = null;
if (isset($filters['country']))
$country = $filters['country'];
// open the socket
$socket = fsockopen('rr.soldat.pl', 13073);
if (!$socket)
return null;
// send the request
$req = "e©{$version}©{$dedicated}©{$noPassworded}©{$noBots}©{$survival}©{$realistic}©{$noBonuses}©".
"{$advance}©{$weapMod}©{$notFull}©{$notEmpty}©{$gametype}©{$battleye}\n";
fwrite($socket, $req);
// read the number of servers returned
$packet = explode('©', fgets($socket, 64));
if ($packet[0] != 'f') // WTF?
{
fclose($socket);
return null;
}
$count = $packet[1];
$servers = array();
for ($i = 0; $i < $count; $i++)
{
$packet = fgets($socket, 256); // 256 should be far enough
if ($packet === FALSE || $packet == "h©©©©©\n")
break;
$server = ParseServerPacket($packet);
if ($server == null)
break;
if (isset($filters['dedicated']) && $filters['dedicated'] === false && $server['dedicated'])
continue;
if (isset($filters['noPassworded']) && $filters['noPassworded'] === false && !$server['passworded'])
continue;
if (isset($filters['noBots']) && $filters['noBots'] === false && $server['bots'] == 0)
continue;
if (isset($filters['survival']) && $filters['survival'] === false && $server['survival'])
continue;
if (isset($filters['realistic']) && $filters['realistic'] === false && $server['realistic'])
continue;
if (isset($filters['notFull']) && $filters['notFull'] === false && $server['players'] != $server['maxplayers'])
continue;
if (isset($filters['notEmpty']) && $filters['notEmpty'] === false && $server['players'] != 0)
continue;
if (isset($filters['battleye']) && $filters['battleye'] === false && $server['battleye'])
continue;
if (isset($filters['noBonuses']) && $filters['noBonuses'] === false && $server['bonusfreq'] == 0)
continue;
if (isset($filters['linux']) && ($filters['linux'] XOR $server['linux']))
continue;
if (isset($filters['connection']) && $filters['connection'] != $server['connection'])
continue;
if (isset($filters['ip']) && $filters['ip'] != $server['ip'])
continue;
if (isset($filters['port']) && $filters['port'] != $server['port'])
continue;
if (isset($filters['map']) && strstr(strtolower($server['map']), strtolower($filters['map'])) === FALSE)
continue;
if ($name != null && strstr(strtolower($server['name']), strtolower($name)) === FALSE)
continue;
if ($country != null && $server['country'] != $country)
continue;
$servers[] = $server;
}
fclose($socket);
return $servers;
}
Here's an example:
<?php
header('Content-type: text/plain');
$filters = array(
'country' => 'US',
'notFull' => true,
'notEmpty' => true
);
$servers = RequestServers($filters);
echo 'Showing ' . count($servers) . " servers.\r\n\r\n";
foreach ($servers as $server)
{
echo "--------------\r\n";
foreach ($server as $param => $value)
echo "$param: $value\r\n";
}
?>
Ruby
By Flippeh
class Lobby
def initialize
@update = nil
@host = "rr.soldat.pl"
@port = 13073
@list = []
@lobby = Struct.new("Lobby", :players, :servers).new(0, 0)
@server = Struct.new("Server", :version, :ip, :port, :game_style, :players, :maxplayers, :map, :name, :bots, :bonus_freq, :respawn, :connection, :survival, :realistic, :dedicated, :os, :passworded, :battleye, :country)
@base_request = "e©42©0©0©0©0©0©0©0©0©0©0©-1©0"
update_thread
end
def update_thread
@update = Thread.new do
loop do
update
sleep(10)
end
end
end
def update
lobby = nil
tmp = []
begin
lobby = TCPSocket.open("rr.soldat.pl", 13073)
rescue SocketError
return nil
end
lobby.puts("#{@base_request}\n")
@lobby.players = 0
@lobby.servers = 0
until lobby.eof?
b = lobby.gets.chomp
if b =~ /^f©(\d+)/ then
@lobby.servers = $1.to_i
elsif b =~ /^g©(.+)/
info = $1.split("©")
@lobby.players += info[4].to_i
tmp << @server.new(info[0], info[1], info[2], info[3], info[4], info[5], info[6], info[7], info[8], info[9], info[10], info[11], info[12], info[13], info[14], info[15], info[16], info[17], info[18])
end
@list = tmp
end
end
def find_by_name(name)
res = @list.select { |server| server.name =~ name }
return res.length > 0 ? res : nil
end
def find_by_lang(code)
res = @list.select { |server| server.country =~ code }
return res.length > 0 ? res : nil
end
attr_accessor :lobby, :list
end
l = Lobby.new
res = l.find_by_name(/dedicated/)
puts res
C++
By jrgp
/*
* Cross platform lobby client / server lister, by jrgp - 2/14/2011
*
* Compile me for windows on unix with: i586-mingw32msvc-c++ client.cpp -lws2_32
*/
// Legacy C
#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <errno.h>
#include <cstring>
#include <sys/types.h>
// Lovely C++ stuff
#include <iostream>
#include <vector>
#include <string>
#ifdef WIN32
//windows networking
#define _WIN32_WINNT 0x501
#include <winsock2.h>
#include <ws2tcpip.h>
#else
// Unix networking stuff
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#endif
// Lobby location
#define LOBBY_HOST "rr.soldat.pl"
#define LOBBY_PORT "13073"
// Because using std:: everywhere pisses off people, apparently
using namespace std;
// Simple explode implementation
vector<string> c_explode(const string &string, const char &sep) {
// Result stored here
vector<string> result;
// Temp buffer stored here
string curr;
// Characater by character
for (unsigned int i = 0, len = string.length(); i < len; i++) {
// This match?
if (string[i] == sep) {
// Save buffer if it isn't shit
if (curr.length() > 0)
result.push_back(curr);
// No use in getting redundant, now
curr = "";
}
// No; add this to current buffer
else
curr += string[i];
}
// Save a trailing one if it's useful
if (curr.length() > 0)
result.push_back(curr);
// Give
return result;
}
// Server record
struct server_entry {
unsigned short int port;
unsigned short int version;
string name;
string map;
string ip;
unsigned short int gametype;
unsigned short int bonus_freq;
unsigned short int connection_type;
unsigned short int num_bots;
unsigned short int respawn;
bool is_linux; // "linux" is apparently a reserved word of some sort
bool survival;
bool advanced;
bool realistic;
unsigned short int num_players;
unsigned short int max_players;
bool be;
bool dedicated;
bool passworded;
string country;
};
// Lobby interface class
class lobby_client {
private:
// Weird windows
#ifdef WIN32
WSADATA wsadata;
#endif
// Store actual servers here
vector<server_entry> servers;
public:
// Constructor
lobby_client() {
// More weird windows
#ifdef WIN32
if (WSAStartup(MAKEWORD(2, 0), &wsadata) != 0)
cerr << "Error initializing winsock " << endl;
#endif
}
// Destructor
~lobby_client() {
// End the windows
#ifdef WIN32
WSACleanup();
#endif
}
// Number of server's we've got
unsigned int num_servers() {
return servers.size();
}
// Load the servers
bool load_servers() {
// Request
char req[100];
sprintf(req, "e%c0%c0%c0%c0%c0%c0%c0%c0%c0%c0%c0%c-1%c0%c0\n", 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169, 169);
// Address of where we're connecting
struct addrinfo hints, *res;
// Socket handle
int sockfd;
// Initiate hints to nothing
memset(&hints, 0, sizeof hints);
// We only want ipv4
hints.ai_family = AF_INET;
// TCP, not UDP
hints.ai_socktype = SOCK_STREAM;
// Address we're connecting to
int ar;
if ((ar = getaddrinfo(LOBBY_HOST, LOBBY_PORT, &hints, &res)) != 0) {
cerr << "Error resolving: " << gai_strerror(ar) << endl;
return false;
}
// Create socket
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
// Connect
if(connect(sockfd, res->ai_addr, res->ai_addrlen) == -1) {
cerr << "Error: " << strerror(errno) << endl;
return false;
}
// Kill ram that used
freeaddrinfo(res);
// Debugging output
cout << "Connected" << endl;
// Send our request (may fail)
send(sockfd, req, strlen(req), 0);
int rst = 0; // recv result
int i = 0; // buff iterator
int j = 0; // line pos counter
int len;
char buff[100]; // sock buff
char line[500]; // current line holder
// Parts of each split go here
vector<string> parts;
// Temporary struct for each current server
server_entry curr_server;
// Go forever until we decide not to
while (true) {
// Get
rst = recv(sockfd, buff, sizeof buff, 0);
// Motherfucker
if (rst < 1)
break;
// terminate it. very important
buff[rst] = '\0';
// go through each character in buff
for (i = 0, len = strlen(buff); i < len; i++) {
// a newline? use line, empty it, reset counter
if (buff[i] == '\n') {
// Split that by the copyrihgt symbols
parts = c_explode(line, 169);
// Does this look like a valid server entry?
if (parts[0][0] == 'g' && parts.size() == 20) {
// Pack that temp struct
curr_server.version = atoi(parts[1].c_str());
curr_server.ip = parts[2];
curr_server.port = atoi(parts[3].c_str());
curr_server.gametype = atoi(parts[4].c_str());
curr_server.num_players = atoi(parts[5].c_str());
curr_server.max_players = atoi(parts[6].c_str());
curr_server.map = parts[7];
curr_server.name = parts[8];
curr_server.num_bots = atoi(parts[9].c_str());
curr_server.bonus_freq = atoi(parts[10].c_str());
curr_server.respawn = atoi(parts[11].c_str());
curr_server.connection_type = atoi(parts[12].c_str());
curr_server.survival = parts[13] == "1";
curr_server.realistic = parts[14] == "1";
curr_server.dedicated = parts[15] == "1";
curr_server.is_linux = parts[16] == "1";
curr_server.passworded = parts[17] == "1";
curr_server.be = parts[18] == "1";
curr_server.country = parts[19].substr(0, 2);
// pack that
servers.push_back(curr_server);
}
// End of line; clear buffers
j = 0;
memset(line, '\0', sizeof line);
}
// no; char in current line.
else {
line[j] = buff[i]; // save char in line buff
line[j+1] = '\0'; // terminate it
j++; // increase counter
}
}
}
// Kill socket
#ifdef WIN32
closesocket(sockfd);
#else
close(sockfd);
#endif
// Apparent success
return true;
}
// Return result
vector<server_entry> get_servers() {
return servers;
}
};
int main() {
// Usage
// Load class
lobby_client *lc = new lobby_client;
// Fetch
lc->load_servers();
// Maybe see how many we got
cout << "Found " << lc->num_servers() << " servers" << endl;
// Get them
vector<server_entry> servers = lc->get_servers();
// Show them
for (int num = servers.size(), i = 0; i < num; i++) {
// (refer to any of the fields mentioned by struct near beginning of file)
cout << servers[i].name << " " << servers[i].ip << ":" << servers[i].port << endl;
}
// Great Success!
return 0;
}