Skip to content

Instantly share code, notes, and snippets.

@dotjosh
Last active December 28, 2015 13:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dotjosh/7509488 to your computer and use it in GitHub Desktop.
Save dotjosh/7509488 to your computer and use it in GitHub Desktop.
Arma2 UDP Query in NodeJS
var dgram = require( "dgram" ),
fs = require("fs"),
_ = require("lodash");
exports.query = query;
var packet = {
initialChallenge: "FE" + "FD" + "09",
base: "FE" + "FD" + "00",
id: "04" + "05" + "06" + "07",
fullInfo: "FF" + "FF" + "FF" + "01"
};
function query(ip, port, complete, options){
var responseCount = 0,
client = null,
responses = [],
lastIndex = 100,
timeoutTimer = null,
callbackCalled = false,
settings = _.extend({
timeout: 8000
}, options);
timeoutTimer = setTimeout(function(){
onError("timeout", null);
}, settings.timeout);
try{
client = dgram.createSocket( "udp4" ) ;
client.on( "message", function(msg, rinfo){
try{
messageRecieved(msg, rinfo);
} catch(e){
onError(e);
}
});
client.on("error", function(err){
onError(err);
});
var intialMessage = new Buffer( packet.initialChallenge + packet.id, "hex");
client.send(intialMessage, 0, intialMessage.length, port, ip );
} catch(e){
onError(e);
}
function messageRecieved( msg, rinfo ) {
if(callbackCalled) return;
responseCount++;
if(responseCount == 1){
var challengeInteger = parseInt(new Buffer(msg.slice(5), "utf8"));
var challengePacket = decimalToHexString(challengeInteger);
var getInfoMessage = new Buffer(packet.base + packet.id + challengePacket + packet.fullInfo, "hex");
client.send(getInfoMessage, 0, getInfoMessage.length, port, ip );
}
if(responseCount >= 2){
var buffer = new Buffer(msg, "utf8"),
index = parseInt(buffer[14]) & 127,
last = parseInt(buffer[14] >> 7) == 1;
if(last){
lastIndex = index;
}
var bufferText = new Buffer(msg.slice(16), "UTF8").toString();
responses.push({text: bufferText, index: index, last: last});
if(responses.length-1 == lastIndex){
pingComplete();
}
}
}
function pingComplete(){
var sortedResponses = _.sortBy(responses, "index");
var text = _.reduce(sortedResponses, function(str, resp){
return str + resp.text
}, ""),
result = {},
splitText = text.split("\u0000"),
i=0;
result.ip = ip;
result.port = port;
for(;i < splitText.length;i++){
var key = splitText[i];
if(key.length == 0){
break;
}
var val = splitText[++i];
result[key] = val;
}
i+=3;
result.players = [];
for(;i< splitText.length;i++){
var player = splitText[i];
if(player.length == 0){
break;
}
result.players.push(player);
}
onSuccess(result);
}
function onSuccess(result){
onComplete(null, result);
}
function onError(e){
console.log(e);
onComplete(e, null);
}
function onComplete(err, result){
clearTimeout(timeoutTimer);
try{
client.close();
}
catch(e){
}
if(callbackCalled) return;
callbackCalled = true;
complete(err, result);
}
}
function decimalToHexString(number)
{
if (number < 0)
{
number = 0xFFFFFFFF + number + 1;
}
return number.toString(16).toUpperCase();
}
var arma2query = require("./arma2query"),
fs = require("fs"),
async = require("async"),
_ = require("lodash"),
request = require("request"),
MongoClient = require("mongodb").MongoClient;
var Util = (function () {
return {
escapeRegExp: function (str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}
};
}());
var ServerAdapter = (function () {
var me = function (rawServer, mods) {
this.rawServer = rawServer;
this.mods = mods;
};
var offsetRegex = new RegExp("(?:GMT|UTC)\\s?([+-])\\s?([0-9]{1,2})", "i");
var isOfficialRegex = new RegExp(".*\\@hive.*", "i");
me.prototype.populateDynamicProperties = function () {
var mod = this.mods.findByModString(this.rawServer.mod);
this.rawServer.modName = mod.rawMod.FriendlyName;
this.rawServer.modVersion = mod.getVersionFromHostName(this.rawServer.hostname);
this.rawServer.offset = this.getOffsetFromHostName();
this.rawServer.lastUpdate = new Date();
this.rawServer.numplayers = parseInt(this.rawServer.numplayers);
this.rawServer.maxplayers = parseInt(this.rawServer.maxplayers);
this.rawServer.hasPassword = parseInt(this.rawServer.password) > 0;
this.rawServer.isOfficial = this.getIsOfficialFromModString();
this.rawServer.uiServer = {
modName: this.rawServer.modName,
modVersion: this.rawServer.modVersion,
offset: this.rawServer.offset,
hostname: this.rawServer.hostname,
ip: this.rawServer.ip,
hasPassword: this.rawServer.hasPassword,
isOfficial: this.rawServer.isOfficial,
port: this.rawServer.port,
numplayers: this.rawServer.numplayers,
maxplayers: this.rawServer.maxplayers,
gamever: this.rawServer.gamever
};
};
me.prototype.getOffsetFromHostName = function () {
var match = offsetRegex.exec(this.rawServer.hostname);
if (match && match.length === 3) {
return match[1] + match[2];
}
return null;
};
me.prototype.getIsOfficialFromModString = function() {
return isOfficialRegex.test(this.rawServer.mod);
};
return me;
}());
var Mod = (function () {
var me = function (rawMod) {
this.rawMod = rawMod;
this.buildPatterns();
this.buildVersions();
};
me.prototype.buildPatterns = function () {
this.patterns = {
serverModStrings: _.map(this.rawMod.ServerModStrings, function (sms) {
return new RegExp(".*" + Util.escapeRegExp(sms) + ".*", "i");
}),
serverNotModStrings: _.map(this.ServerNotModStrings, function (snms) {
return new RegExp(".*" + Util.escapeRegExp(snms) + ".*", "i");
})
};
};
me.prototype.buildVersions = function () {
this.versions = _.map(this.rawMod.Versions, function (rawVersion) {
return rawVersion.Version;
});
};
me.prototype.isForModString = function (modString) {
if (this.patterns.serverModStrings.length === 0) {
return false;
}
var isMod = _.all(this.patterns.serverModStrings, function (regex) {
return regex.test(modString);
});
if (isMod && this.patterns.serverNotModStrings.length) {
isMod = !_.any(this.patterns.serverNotModStrings, function (regex) {
return regex.test(modString);
});
}
return isMod;
};
me.prototype.getVersionFromHostName = function (hostname) {
return _.findWhere(this.versions, function (version) {
return hostname.indexOf(version) > -1;
}) || null;
};
return me;
}());
var Mods = (function () {
var me = function () {
var that = this;
that.modDict = {};
that.mods = [];
var rawMods = JSON.parse(fs.readFileSync("modversionsdev.json")).ModVersions;
_.each(rawMods, function (rawMod) {
var mod = that.modDict[rawMod.FriendlyName] = new Mod(rawMod);
that.mods.push(mod);
});
};
me.prototype.getByName = function (name) {
return this.modDict[name];
};
me.prototype.findByModString = function (modString) {
return _.findWhere(this.mods, function (mod) {
return mod.isForModString(modString);
}) || this.modDict.DayZ;
};
return me;
}());
var queryTasks = null,
servers = [],
successes = 0,
errors = 0,
timeouts = 0;
function buildQueryTask(ip, port) {
return function (callback) {
arma2query.query(
ip,
port,
function (err, rawServer) {
if (err) {
if (err === "timeout") {
timeouts++;
} else {
errors++;
}
console.log(err);
console.log("Success: " + successes + " errors: " + errors + " timeouts: " + timeouts);
callback();
} else {
servers.push(rawServer);
successes++;
console.log("Success: " + successes + " errors: " + errors + " timeouts: " + timeouts);
callback();
}
}
);
};
}
function saveToMongo() {
console.log("UPDATING...");
MongoClient.connect('mongodb://127.0.0.1:27017/dayzcommander', function (err, db) {
if (err) {
throw err;
}
console.log("Connected");
var mods = new Mods();
var i = 0;
_(servers).each(function (rawServer) {
var serverAdapter = new ServerAdapter(rawServer, mods);
serverAdapter.populateDynamicProperties();
db.collection('servers').update(
{
$and: [{
ip: rawServer.ip
}, {
port: rawServer.port
}]
},
rawServer,
{
upsert: true,
safe: true
},
function (err) {
i++;
if (err) {
console.error("ERROR");
console.warn(err.message);
} else {
console.log('successfully updated: ' + i);
}
}
);
});
});
}
request(
{
uri: "https://dayzcommander.blob.core.windows.net/releases/dayzservers2raw.txt"
},
function (err, response, body) {
var serversSplit = body.split("|");
queryTasks = _.map(serversSplit, function (server) {
var serverSplit = server.split(":");
return buildQueryTask(serverSplit[0], serverSplit[1]);
});
async.parallelLimit(queryTasks, 100, saveToMongo);
}
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment