Using nodejs to read counter strike server info

NodeJs is a very powerfull tool for creating server side applications. Nodejs is a event-driven I/O JavaScript environment based on V8, wich is the engine for the chrome browser and is very fast, . This article shows how to read counter strike server info trought udp protocol wich the server runs. On default the server runs on the port 27015, and to read the info you need to send some pakets for the what you need: the server details, the players list or the server rules.

var Packet = {
	ping : [0xFF,0xFF,0xFF,0xFF,0x69],
	challenge : [0xFF,0xFF,0xFF,0xFF,0x56,0x00,0x00,0x00,0x00],
	details : [0xFF,0xFF,0xFF,0xFF, 0x54, 0x53, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x20, 0x45, 0x6E, 0x67, 0x69, 0x6E, 0x65, 0x20, 0x51, 0x75, 0x65, 0x72, 0x79, 0x00],
	players : [0xFF,0xFF,0xFF,0xFF,0x55],
	rules : [0xFF,0xFF,0xFF,0xFF,0x56]
};

For every information wich server can serve you need to send a packet from above and you will receive the binary data. To read more about the counter strike server queries click here.

Here is the code to take a look and next i will explain much more about it.


var util = require('util');
var DGram = require('dgram');
var EventEmitter = require('events').EventEmitter;

var Packet = {
	ping : [0xFF,0xFF,0xFF,0xFF,0x69],
	challenge : [0xFF,0xFF,0xFF,0xFF,0x56,0x00,0x00,0x00,0x00],
	details : [0xFF,0xFF,0xFF,0xFF, 0x54, 0x53, 0x6F, 0x75, 0x72, 0x63, 0x65, 0x20, 0x45, 0x6E, 0x67, 0x69, 0x6E, 0x65, 0x20, 0x51, 0x75, 0x65, 0x72, 0x79, 0x00],
	players : [0xFF,0xFF,0xFF,0xFF,0x55],
	rules : [0xFF,0xFF,0xFF,0xFF,0x56]
};

var XBuffer = function(data)
{
	this.data = data;
	this.position = 0;
}

XBuffer.prototype.readString = function()
{
	var string = '';

	while(this.data[this.position] != 0x00)
	{
		string += String.fromCharCode(this.data[this.position]);
		this.position++;

		if(this.position > this.data.length)
			break;
	}

	//skip  0x00
	this.position++;

	return string;
}
XBuffer.prototype.skip = function(n)
{
	n = n || 1;
	this.position += n;
}
XBuffer.prototype.available = function()
{
	return Math.max(this.data.length - this.position, 0);
}
XBuffer.prototype.read = function(n)
{
	var b = new Buffer(n);
	n = n || 1;
	for(var i=0; i < n; i++)
	{
		b[i] = this.data[this.position+n];
		this.position++;
	}

	return new XBuffer(b);
}
XBuffer.prototype.readByte = function()
{
	var value = this.data[this.position];
	this.position++;
	return String.fromCharCode(value);
}
XBuffer.prototype.lookAhead = function(n)
{
	var tempPos = this.position;
	var buff = this.read(n);
	this.position=tempPos;
	return buff;
}

XBuffer.prototype.readUInt8 = function()
{
	var value = this.data.readUInt8(this.position);
	this.position++;
	return value;
}
XBuffer.prototype.readInt8 = function()
{
	var value = this.data.readInt8(this.position);
	this.position++;
	return value;
}
XBuffer.prototype.readInt16LE = function()
{
	var value = this.data.readInt16LE(this.position);
	this.position+=2;
	return value;
}
XBuffer.prototype.readInt16BE = function()
{
	var value = this.data.readInt16BE(this.position);
	this.position+=2;
	return value;
}
XBuffer.prototype.readInt32LE = function()
{
	var value = this.data.readInt32LE(this.position);
	this.position+=4;
	return value;
}
XBuffer.prototype.readFloatLE = function()
{
	var value = this.data.readFloatLE(this.position);
	this.position+=4;
	return value;
}
XBuffer.prototype.toString = function()
{
	return "[XBuffer " + this.data + "]";
}

function parseRules( data )
{
	var d = {};
	var b = new XBuffer(data);

	b.skip(5);

	var count = b.readInt16LE();

	if (count == 65535)
	{
		b.skip();
		count = b.readInt16LE();
	}

	d.num_rules = count;

	while(b.available())
	{
		d[b.readString()]=b.readString();
	}

	return d;
}

function parseDetails( data )
{
	b = new XBuffer(data);
	var d = {};

	b.skip(4);

	d.type =  b.readInt8();

	if (d.type == 0x6D)
		d.address =  b.readString();
	else
		d.protocol= b.readInt8();

	d.hostname =  b.readString();
	d.map =  b.readString();
	d.game_dir =  b.readString();
	d.game_desc =  b.readString();

	if (d.type != 0x6D) d.steamappid = b.readInt16LE();

	d.num_players= b.readInt8();
	d.max_players= b.readInt8();

	if (d.type == 0x6D) d.protocol = b.readInt8();
	else               d.num_bots = b.readInt8();

	d.dedicated = b.readByte();
	d.os		= b.readByte();
	d.password		= b.readInt8();
	d.secure		= b.readInt8();
	d.version		= b.readInt8();

	return d;
}

function parsePlayers( buffer )
{
	var b = new XBuffer( buffer );

	var players=[];

	b.skip(5);

	var playersn = b.readUInt8();

	for(var i=0; i < playersn; i++)
	{
		players.push({
			id : b.readUInt8(),
			name : b.readString(),
			score : b.readInt32LE(),
			time : b.readFloatLE()
		});
	}

	return players;
}

var Protocol = function(ip, port)
{
	this.ip = ip;
	this.port = port;
	this.client = DGram.createSocket("udp4");

	if(false === (this instanceof Protocol)) {
        return new Protocol(ip, port);
    }

	EventEmitter.call(this);
}

util.inherits(Protocol, EventEmitter);

Protocol.prototype.request = function()
{
	var self = this;

	var data = {};

	self.dgramRequest(new Buffer(Packet['ping']), function(challData,rinfo){
		if(challData[4] == 0x6A)
		{
			//pong received

			//request details
			self.dgramRequest(new Buffer(Packet['details']), function( detailsData, rinfo1){

				//parse server details
				try {
					data.details = parseDetails( detailsData );
				}catch(e){
					self.emit("exception", e);
				}

				//request the server details
				self.dgramRequest( new Buffer(Packet['players']), function(playersData, rinfo2){

					//parse players data and ad it to the data object
					try {
						data.players = parsePlayers( playersData );
					}catch(e) {
						self.emit("exception", e);
					}

					self.dgramRequest( new Buffer(Packet['rules']), function(rulesData, rinfo3){

						//parse server rules
						try {
						data.rules = parseRules( rulesData );
						} catch(e) {
							self.emit("exception", e);
						}

						//dispatch event
						self.emit("success", data);
					});

				});
			});

		}
		else
		{
			//no pong received, server is down
			self.emit("offline");
		}
	});

}

function preprocess( data )
{
	//used if you need to filter data from the server
	return data;
}

Protocol.prototype.dgramRequest = function( buffer, callback )
{
	var self = this;

	var offlineTimeout;

	var _internalCallback = function(data, rinfo)
	{
		data = preprocess( data );

		clearTimeout(offlineTimeout);

		if(callback)
		setTimeout(callback,0, data, rinfo);

		self.client.removeListener("message", _internalCallback);
	}

	offlineTimeout = setTimeout(function(){
		self.emit("offline");
	}, 2000);

	this.client.on("message", _internalCallback);

	this.client.send(buffer, 0, buffer.length, parseInt(this.port), this.ip, function(err, bytes) {
	  if(err){	console.log("Fail to broadcase packet to " + self.ip); }
	});

}

exports.Protocol = Protocol;

As you see there is required the dgram package wich is used for udp communications. UDP uses a simple transmission model without implicit handshaking dialogues for providing reliability, ordering, or data integrity. Thus, UDP provides an unreliable service and datagrams may arrive out of order, appear duplicated, or go missing without notice.

In this code are two objects the XBuffer and Protocol, XBuffer simply increase the reading pointer when reading data, and the Protocol send all pakets, filter data and dispatched as event.

Example of requesting info from the server

self.dgramRequest( new Buffer(Packet['players']), function(playersData, rinfo2) {
//your code here
} );

The function gramRequest is used to make syncron request to the server, send the packet and wait for response. If the response not apear for 2 senconds will trigger the offline event

The requests are sent one after another, parsed for a human readable format and pushed to the data object, when all complete is dispatched the success event ( self.emit(“success”, data); )

For every kind of received info i used functions to parse the binary data, for example, the player data is parsed with parsePlayers function. See the code comments for see how its work.

function parsePlayers( buffer )
{
        //create the xbuffer object with received buffer
	var b = new XBuffer( buffer );

	var players=[];
        //skip 5 unused bytes
	b.skip(5);
        //read a 2 bytes int wich is the number of the players
	var playersn = b.readUInt8();
        //make a loop to players length
	for(var i=0; i < playersn; i++)
	{
		players.push({
			id : b.readUInt8(), // read 2 bytes of int with the id of the player
			name : b.readString(), //read n bytes ending with 0x00 wich is the player name
			score : b.readInt32LE(), //read 4 bytes int with user score
			time : b.readFloatLE() //read 4 bytes float with user playing time
		});
	}

	return players;
}

Mini example to use this code

var Protocol = require("./communication.js").Protocol;

var protocol = new Protocol(server.ip, server.port);

protocol.on("success", function(data){
 //use data
});
protocol.request();

I hope this article helps you, and if you have some questions leave me a message.

Invisible detector made to be simple and user friendly

Today invisible-detector.net is live and is more user friendly and fast then last version. Yesterday i refused to go to club and I decided to restore design to insible-detector.net. I have made ​​it much easier to use and much faster, the results are now directly displayed in the input where you write the id. History is quite different  then others websites, this is with suggestions autocomplete. Enter and tell your opinion! Thanks!

invisible-detector.net

Flash player 10.3 video problem with hardware acceleration

Working on a video player project with osmf and flex i had some strange problems. When going in fullscreen mode the screen it was black or more exactly the video it was not been sown.  After one hour of testing and debugging i found the problem, the problem was that i compiled the video player with 10.3 flash player version and this works with hardware accelerated and if there is no video driver or the driver is out of date the video will not be shown only when the hardware acceleration is disabled or you update your video drivers. The solution for this is to compile with older version 10.2 or below witch is not working with video acceleration.

Hardware Accelerated Setting

Hardware Accelerated Setting