Client-Lobby Protocol Parsers

From Soldat Community Wiki
Jump to: navigation, search

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;
}