Last active
December 28, 2015 13:39
-
-
Save dotjosh/7509488 to your computer and use it in GitHub Desktop.
Arma2 UDP Query in NodeJS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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