From 69288baebc4e909930884e81781d075a3e61a41c Mon Sep 17 00:00:00 2001 From: mmorrison Date: Wed, 9 Aug 2017 04:05:55 -0500 Subject: [PATCH] Upgrade syntax of everything to more modern javascript --- .gitignore | 2 + bin/gamedig.js | 43 +++-- bin/genreadme.js | 20 +- lib/Class.js | 74 -------- lib/index.js | 56 +++--- lib/reader.js | 145 ++++++++------- lib/typeresolver.js | 77 ++++---- protocols/americasarmy.js | 25 +-- protocols/armagetron.js | 82 +++++---- protocols/ase.js | 64 +++---- protocols/battlefield.js | 157 ++++++++-------- protocols/buildandshoot.js | 36 ++-- protocols/core.js | 258 +++++++++++++------------- protocols/doom3.js | 82 ++++----- protocols/ffow.js | 20 +- protocols/gamespy1.js | 87 ++++----- protocols/gamespy2.js | 94 +++++----- protocols/gamespy3.js | 145 +++++++-------- protocols/hexenworld.js | 11 +- protocols/jc2mp.js | 26 +-- protocols/killingfloor.js | 8 +- protocols/m2mp.js | 46 ++--- protocols/minecraftping.js | 63 +++---- protocols/mumble.js | 45 +++-- protocols/mumbleping.js | 27 ++- protocols/mutantfactions.js | 69 ++++--- protocols/nadeo.js | 83 +++++---- protocols/openttd.js | 103 +++++------ protocols/quake1.js | 10 +- protocols/quake2.js | 65 +++---- protocols/quake3.js | 26 +-- protocols/samp.js | 83 ++++----- protocols/starmade.js | 54 +++--- protocols/teamspeak2.js | 93 +++++----- protocols/teamspeak3.js | 78 ++++---- protocols/terraria.js | 27 +-- protocols/unreal2.js | 112 ++++++------ protocols/ut2004.js | 8 +- protocols/ut3.js | 18 +- protocols/valve.js | 355 ++++++++++++++++++------------------ protocols/ventrilo.js | 129 +++++++------ protocols/warsow.js | 13 +- test.sh | 1 + 43 files changed, 1499 insertions(+), 1521 deletions(-) delete mode 100644 lib/Class.js create mode 100644 test.sh diff --git a/.gitignore b/.gitignore index 7a3a95d..9bbce42 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /node_modules /npm-debug.log +/*.iml +/.idea diff --git a/bin/gamedig.js b/bin/gamedig.js index 8c6a69b..131e371 100644 --- a/bin/gamedig.js +++ b/bin/gamedig.js @@ -1,40 +1,51 @@ #!/usr/bin/env node -var argv = require('minimist')(process.argv.slice(2)); +const argv = require('minimist')(process.argv.slice(2)), + Gamedig = require('..'); -var debug = argv.debug; +const debug = argv.debug; delete argv.debug; -var outputFormat = argv.output; +const outputFormat = argv.output; delete argv.output; -var options = {}; -for(var key in argv) { - var value = argv[key]; +const options = {}; +for(const key of Object.keys(argv)) { + const value = argv[key]; if( - key == '_' - || key.charAt(0) == '$' - || (typeof value != 'string' && typeof value != 'number') + key === '_' + || key.charAt(0) === '$' + || (typeof value !== 'string' && typeof value !== 'number') ) continue; options[key] = value; } -var Gamedig = require('../lib/index'); if(debug) Gamedig.debug = true; Gamedig.isCommandLine = true; Gamedig.query(options) .then((state) => { - if(outputFormat == 'pretty') { + if(outputFormat === 'pretty') { console.log(JSON.stringify(state,null,' ')); } else { console.log(JSON.stringify(state)); } }) .catch((error) => { - if(outputFormat == 'pretty') { - console.log(JSON.stringify({error:error},null,' ')); - } else { - console.log(JSON.stringify({error:error})); - } + if (debug) { + if (error instanceof Error) { + console.log(error.stack); + } else { + console.log(error); + } + } else { + if (error instanceof Error) { + error = error.message; + } + if (outputFormat === 'pretty') { + console.log(JSON.stringify({error: error}, null, ' ')); + } else { + console.log(JSON.stringify({error: error})); + } + } }); diff --git a/bin/genreadme.js b/bin/genreadme.js index 81cee99..1a69010 100644 --- a/bin/genreadme.js +++ b/bin/genreadme.js @@ -1,19 +1,19 @@ #!/usr/bin/env node -var fs = require('fs'); +const fs = require('fs'), + TypeResolver = require('../lib/typeresolver'); -var TypeResolver = require('../lib/typeresolver'); -var generated = TypeResolver.printReadme(); +const generated = TypeResolver.printReadme(); -var readmeFilename = __dirname+'/../README.md'; -var readme = fs.readFileSync(readmeFilename, {encoding:'utf8'}); +const readmeFilename = __dirname+'/../README.md'; +const readme = fs.readFileSync(readmeFilename, {encoding:'utf8'}); -var marker_top = ''; -var marker_bottom = ''; +const marker_top = ''; +const marker_bottom = ''; -var start = readme.indexOf(marker_top); +let start = readme.indexOf(marker_top); start += marker_top.length; -var end = readme.indexOf(marker_bottom); +const end = readme.indexOf(marker_bottom); -var updated = readme.substr(0,start)+"\n\n"+generated+"\n"+readme.substr(end); +const updated = readme.substr(0,start)+"\n\n"+generated+"\n"+readme.substr(end); fs.writeFileSync(readmeFilename, updated); diff --git a/lib/Class.js b/lib/Class.js deleted file mode 100644 index 6e8efbc..0000000 --- a/lib/Class.js +++ /dev/null @@ -1,74 +0,0 @@ -/* based on Simple JavaScript Inheritance -* By John Resig http://ejohn.org/ -* MIT Licensed. -*/ - -var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; - -// The base Class implementation (does nothing) -var Class = function(){}; - -// Create a new Class that inherits from this class -Class.extend = function() { - var args = Array.prototype.slice.call(arguments); - var name = 'Class'; - var parent = this; - var prop = {}; - if(typeof args[0] == 'string') name = args.shift(); - if(args.length >= 2) parent = args.shift(); - prop = args.shift(); - - // Copy prototype from the parent object - var prototype = {}; - for(var name in parent.prototype) { - prototype[name] = parent.prototype[name]; - } - - // Copy the properties over onto the new prototype - for(var name in prop) { - if(typeof prop[name] == "function" && fnTest.test(prop[name])) { - // this is a function that references _super, so we have to wrap it - // and provide it with its super function - prototype[name] = (function(name, fn){ - return function() { - var tmp = this._super; - - // Add a new ._super() method that is the same method - // but on the super-class - if(typeof parent.prototype[name] == 'undefined') { - if(name == 'init') this._super = parent.prototype.constructor; - else this._super = function() { throw new Error('Called _super in method without a parent'); } - } else this._super = parent.prototype[name]; - - // The method only need to be bound temporarily, so we - // remove it when we're done executing - var ret = fn.apply(this, arguments); - this._super = tmp; - - return ret; - }; - })(name, prop[name]); - } else { - prototype[name] = prop[name]; - } - } - - // The dummy class constructor - function Class() { - // All construction is actually done in the init method - if(this.init) this.init.apply(this, arguments); - } - - // Populate our constructed prototype object - Class.prototype = prototype; - - // Enforce the constructor to be what we expect - Class.prototype.constructor = Class; - - // And make this class extendable - Class.extend = arguments.callee; - - return Class; -}; - -module.exports = Class; diff --git a/lib/index.js b/lib/index.js index d8f7305..2ef9804 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,46 +1,48 @@ -var dgram = require('dgram'), - EventEmitter = require('events').EventEmitter, - util = require('util'), - dns = require('dns'), - TypeResolver = require('./typeresolver'); +const dgram = require('dgram'), + TypeResolver = require('./typeresolver'); -var activeQueries = []; +const activeQueries = []; -var udpSocket = dgram.createSocket('udp4'); +const udpSocket = dgram.createSocket('udp4'); udpSocket.unref(); udpSocket.bind(21943); -udpSocket.on('message', function(buffer, rinfo) { +udpSocket.on('message', (buffer, rinfo) => { if(Gamedig.debug) console.log(rinfo.address+':'+rinfo.port+" <--UDP "+buffer.toString('hex')); - for(var i = 0; i < activeQueries.length; i++) { - var query = activeQueries[i]; + for(const query of activeQueries) { if( - query.options.address != rinfo.address - && query.options.altaddress != rinfo.address + query.options.address !== rinfo.address + && query.options.altaddress !== rinfo.address ) continue; - if(query.options.port_query != rinfo.port) continue; + if(query.options.port_query !== rinfo.port) continue; query._udpResponse(buffer); break; } }); -udpSocket.on('error', function(e) { +udpSocket.on('error', (e) => { if(Gamedig.debug) console.log("UDP ERROR: "+e); }); -Gamedig = { +class Gamedig { - query: function(options,callback) { + static query(options,callback) { const promise = new Promise((resolve,reject) => { + for (const key of Object.keys(options)) { + if (['port_query', 'port'].includes(key)) { + options[key] = parseInt(options[key]); + } + } + options.callback = (state) => { if (state.error) reject(state.error); else resolve(state); }; - var query; + let query; try { query = TypeResolver.lookup(options.type); } catch(e) { - process.nextTick(function() { - options.callback({error:e.message}); + process.nextTick(() => { + options.callback({error:e}); }); return; } @@ -63,26 +65,28 @@ Gamedig = { } // copy over options - for(var i in options) query.options[i] = options[i]; + for(const key of Object.keys(options)) { + query.options[key] = options[key]; + } activeQueries.push(query); - query.on('finished',function(state) { - var i = activeQueries.indexOf(query); + query.on('finished',() => { + const i = activeQueries.indexOf(query); if(i >= 0) activeQueries.splice(i, 1); }); - process.nextTick(function() { + process.nextTick(() => { query.start(); }); }); if (callback && callback instanceof Function) { - if(callback.length == 2) { + if(callback.length === 2) { promise .then((state) => callback(null,state)) .catch((error) => callback(error)); - } else if (callback.length == 1) { + } else if (callback.length === 1) { promise .then((state) => callback(state)) .catch((error) => callback({error:error})); @@ -92,6 +96,6 @@ Gamedig = { return promise; } -}; +} module.exports = Gamedig; diff --git a/lib/reader.js b/lib/reader.js index 4dee0dd..385559e 100644 --- a/lib/reader.js +++ b/lib/reader.js @@ -1,52 +1,57 @@ -var Iconv = require('iconv-lite'), +const Iconv = require('iconv-lite'), Long = require('long'); function readUInt64BE(buffer,offset) { - var high = buffer.readUInt32BE(offset); - var low = buffer.readUInt32BE(offset+4); + const high = buffer.readUInt32BE(offset); + const low = buffer.readUInt32BE(offset+4); return new Long(low,high,true); } function readUInt64LE(buffer,offset) { - var low = buffer.readUInt32LE(offset); - var high = buffer.readUInt32LE(offset+4); + const low = buffer.readUInt32LE(offset); + const high = buffer.readUInt32LE(offset+4); return new Long(low,high,true); } -function Reader(query,buffer) { - this.query = query; - this.buffer = buffer; - this.i = 0; -} +class Reader { + constructor(query,buffer) { + this.query = query; + this.buffer = buffer; + this.i = 0; + } -Reader.prototype = { - offset: function() { return this.i; }, - skip: function(i) { this.i += i; }, - string: function() { - var args = Array.prototype.slice.call(arguments); - var options = {}; - if(args.length == 0) { + offset() { + return this.i; + } + + skip(i) { + this.i += i; + } + + string(...args) { + let options = {}; + if(args.length === 0) { options = {}; - } else if(args.length == 1) { - if(typeof args[0] == 'string') options = { delimiter: args[0] }; - else if(typeof args[0] == 'number') options = { length: args[0] }; + } else if(args.length === 1) { + if(typeof args[0] === 'string') options = { delimiter: args[0] }; + else if(typeof args[0] === 'number') options = { length: args[0] }; else options = args[0]; } options.encoding = options.encoding || this.query.encoding; - if(options.encoding == 'latin1') options.encoding = 'win1252'; + if(options.encoding === 'latin1') options.encoding = 'win1252'; - var start = this.i+0; - var end = start; + const start = this.i+0; + let end = start; if(!('length' in options)) { // terminated by the delimiter - var delim = options.delimiter || this.query.delimiter; - if(typeof delim == 'string') delim = delim.charCodeAt(0); + let delim = options.delimiter || this.query.delimiter; + if(typeof delim === 'string') delim = delim.charCodeAt(0); while(true) { if(end >= this.buffer.length) { end = this.buffer.length; break; } - if(this.buffer.readUInt8(end) == delim) break; + if(this.buffer.readUInt8(end) === delim) break; end++; } this.i = end+1; @@ -58,77 +63,85 @@ Reader.prototype = { this.i = end; } - var out = this.buffer.slice(start, end); - var enc = options.encoding; - if(enc == 'utf8' || enc == 'ucs2' || enc == 'binary') { + let out = this.buffer.slice(start, end); + const enc = options.encoding; + if(enc === 'utf8' || enc === 'ucs2' || enc === 'binary') { out = out.toString(enc); } else { out = Iconv.decode(out,enc); } return out; - }, - int: function(bytes) { - var r = 0; + } + + int(bytes) { + let r = 0; if(this.remaining() >= bytes) { - if(this.query.byteorder == 'be') { - if(bytes == 1) r = this.buffer.readInt8(this.i); - else if(bytes == 2) r = this.buffer.readInt16BE(this.i); - else if(bytes == 4) r = this.buffer.readInt32BE(this.i); + if(this.query.byteorder === 'be') { + if(bytes === 1) r = this.buffer.readInt8(this.i); + else if(bytes === 2) r = this.buffer.readInt16BE(this.i); + else if(bytes === 4) r = this.buffer.readInt32BE(this.i); } else { - if(bytes == 1) r = this.buffer.readInt8(this.i); - else if(bytes == 2) r = this.buffer.readInt16LE(this.i); - else if(bytes == 4) r = this.buffer.readInt32LE(this.i); + if(bytes === 1) r = this.buffer.readInt8(this.i); + else if(bytes === 2) r = this.buffer.readInt16LE(this.i); + else if(bytes === 4) r = this.buffer.readInt32LE(this.i); } } this.i += bytes; return r; - }, - uint: function(bytes) { - var r = 0; + } + + /** @returns {number} */ + uint(bytes) { + let r = 0; if(this.remaining() >= bytes) { - if(this.query.byteorder == 'be') { - if(bytes == 1) r = this.buffer.readUInt8(this.i); - else if(bytes == 2) r = this.buffer.readUInt16BE(this.i); - else if(bytes == 4) r = this.buffer.readUInt32BE(this.i); - else if(bytes == 8) r = readUInt64BE(this.buffer,this.i).toString(); + if(this.query.byteorder === 'be') { + if(bytes === 1) r = this.buffer.readUInt8(this.i); + else if(bytes === 2) r = this.buffer.readUInt16BE(this.i); + else if(bytes === 4) r = this.buffer.readUInt32BE(this.i); + else if(bytes === 8) r = readUInt64BE(this.buffer,this.i).toString(); } else { - if(bytes == 1) r = this.buffer.readUInt8(this.i); - else if(bytes == 2) r = this.buffer.readUInt16LE(this.i); - else if(bytes == 4) r = this.buffer.readUInt32LE(this.i); - else if(bytes == 8) r = readUInt64LE(this.buffer,this.i).toString(); + if(bytes === 1) r = this.buffer.readUInt8(this.i); + else if(bytes === 2) r = this.buffer.readUInt16LE(this.i); + else if(bytes === 4) r = this.buffer.readUInt32LE(this.i); + else if(bytes === 8) r = readUInt64LE(this.buffer,this.i).toString(); } } this.i += bytes; return r; - }, - float: function() { - var r = 0; + } + + float() { + let r = 0; if(this.remaining() >= 4) { - if(this.query.byteorder == 'be') r = this.buffer.readFloatBE(this.i); + if(this.query.byteorder === 'be') r = this.buffer.readFloatBE(this.i); else r = this.buffer.readFloatLE(this.i); } this.i += 4; return r; - }, - part: function(bytes) { - var r; + } + + part(bytes) { + let r; if(this.remaining() >= bytes) { r = this.buffer.slice(this.i,this.i+bytes); } else { - r = new Buffer(); + r = Buffer.from([]); } this.i += bytes; return r; - }, - remaining: function() { + } + + remaining() { return this.buffer.length-this.i; - }, - rest: function() { + } + + rest() { return this.buffer.slice(this.i); - }, - done: function() { + } + + done() { return this.i >= this.buffer.length; } -}; +} module.exports = Reader; diff --git a/lib/typeresolver.js b/lib/typeresolver.js index aebaacf..a882193 100644 --- a/lib/typeresolver.js +++ b/lib/typeresolver.js @@ -1,38 +1,37 @@ -var Path = require('path'), +const Path = require('path'), fs = require('fs'); -var protocolDir = Path.normalize(__dirname+'/../protocols'); -var gamesFile = Path.normalize(__dirname+'/../games.txt'); +const protocolDir = Path.normalize(__dirname+'/../protocols'); +const gamesFile = Path.normalize(__dirname+'/../games.txt'); function parseList(str) { if(!str) return {}; - var split = str.split(','); - var out = {}; - split.forEach(function(one) { - var equals = one.indexOf('='); - var key = equals == -1 ? one : one.substr(0,equals); - var value = equals == -1 ? '' : one.substr(equals+1); + const out = {}; + for (const one of str.split(',')) { + const equals = one.indexOf('='); + const key = equals === -1 ? one : one.substr(0,equals); + let value = equals === -1 ? '' : one.substr(equals+1); if(value === 'true' || value === '') value = true; else if(value === 'false') value = false; else if(!isNaN(value)) value = parseInt(value); out[key] = value; - }); + } return out; } function readGames() { - var lines = fs.readFileSync(gamesFile,'utf8').split('\n'); - var games = {}; + const lines = fs.readFileSync(gamesFile,'utf8').split('\n'); + const games = {}; - lines.forEach(function(line) { + for (let line of lines) { // strip comments - var comment = line.indexOf('#'); - if(comment != -1) line = line.substr(0,comment); + const comment = line.indexOf('#'); + if(comment !== -1) line = line.substr(0,comment); line = line.trim(); - if(!line) return; + if(!line) continue; - var split = line.split('|'); + const split = line.split('|'); games[split[0].trim()] = { pretty: split[1].trim(), @@ -40,52 +39,56 @@ function readGames() { options: parseList(split[3]), params: parseList(split[4]) }; - }); + } return games; } -var games = readGames(); +const games = readGames(); function createProtocolInstance(type) { type = Path.basename(type); - var path = protocolDir+'/'+type; + const path = protocolDir+'/'+type; if(!fs.existsSync(path+'.js')) throw Error('Protocol definition file missing: '+type); - var protocol = require(path); + const protocol = require(path); return new protocol(); } -module.exports = { - lookup: function(type) { +class TypeResolver { + static lookup(type) { if(!type) throw Error('No game specified'); - if(type.substr(0,9) == 'protocol-') { + if(type.substr(0,9) === 'protocol-') { return createProtocolInstance(type.substr(9)); } - var game = games[type]; + const game = games[type]; if(!game) throw Error('Invalid game: '+type); - var query = createProtocolInstance(game.protocol); + const query = createProtocolInstance(game.protocol); query.pretty = game.pretty; - for(var key in game.options) - query.options[key] = game.options[key]; - for(var key in game.params) - query[key] = game.params[key]; + for(const key of Object.keys(game.options)) { + query.options[key] = game.options[key]; + } + for(const key of Object.keys(game.params)) { + query[key] = game.params[key]; + } return query; - }, - printReadme: function() { - var out = ''; - for(var key in games) { - var game = games[key]; + } + static printReadme() { + let out = ''; + for(const key of Object.keys(games)) { + const game = games[key]; out += "* "+game.pretty+" ("+key+")"; if(game.options.port_query_offset || game.options.port_query) out += " [[Separate Query Port](#separate-query-port)]"; if(game.params.doc_notes) - out += " [[Additional Notes](#"+game.params.doc_notes+")]" + out += " [[Additional Notes](#"+game.params.doc_notes+")]"; out += "\n"; } return out; } -}; +} + +module.exports = TypeResolver; diff --git a/protocols/americasarmy.js b/protocols/americasarmy.js index bd86bf5..6ed7d7c 100644 --- a/protocols/americasarmy.js +++ b/protocols/americasarmy.js @@ -1,20 +1,23 @@ -module.exports = require('./gamespy2').extend({ - finalizeState: function(state) { - this._super(state); +class AmericasArmy extends require('./gamespy2') { + finalizeState(state) { + super.finalizeState(state); state.name = this.stripColor(state.name); state.map = this.stripColor(state.map); - for(var i in state.raw) { - if(!(typeof state.raw[i] == 'string')) continue; - state.raw[i] = this.stripColor(state.raw[i]); + for(const key of Object.keys(state.raw)) { + if(typeof state.raw[key] === 'string') { + state.raw[key] = this.stripColor(state.raw[key]); + } } - for(var i = 0; i < state.players.length; i++) { - var player = state.players[i]; + for(const player of state.players) { if(!('name' in player)) continue; player.name = this.stripColor(player.name); } - }, - stripColor: function(str) { + } + + stripColor(str) { // uses unreal 2 color codes return str.replace(/\x1b...|[\x00-\x1a]/g,''); } -}); +} + +module.exports = AmericasArmy; diff --git a/protocols/armagetron.js b/protocols/armagetron.js index 7f13ef4..289d95f 100644 --- a/protocols/armagetron.js +++ b/protocols/armagetron.js @@ -1,64 +1,66 @@ -module.exports = require('./core').extend({ - init: function() { - this._super(); +class Armagetron extends require('./core') { + constructor() { + super(); this.encoding = 'latin1'; this.byteorder = 'be'; - }, - run: function(state) { - var self = this; + } - var b = new Buffer([0,0x35,0,0,0,0,0,0x11]); + run(state) { + const b = Buffer.from([0,0x35,0,0,0,0,0,0x11]); - this.udpSend(b,function(buffer) { - var reader = self.reader(buffer); + this.udpSend(b,(buffer) => { + const reader = this.reader(buffer); reader.skip(6); - state.raw.port = self.readUInt(reader); - state.raw.hostname = self.readString(reader,buffer); - state.name = self.stripColorCodes(self.readString(reader,buffer)); - state.raw.numplayers = self.readUInt(reader); - state.raw.versionmin = self.readUInt(reader); - state.raw.versionmax = self.readUInt(reader); - state.raw.version = self.readString(reader,buffer); - state.maxplayers = self.readUInt(reader); + state.raw.port = this.readUInt(reader); + state.raw.hostname = this.readString(reader); + state.name = this.stripColorCodes(this.readString(reader)); + state.raw.numplayers = this.readUInt(reader); + state.raw.versionmin = this.readUInt(reader); + state.raw.versionmax = this.readUInt(reader); + state.raw.version = this.readString(reader); + state.maxplayers = this.readUInt(reader); - var players = self.readString(reader,buffer); - var list = players.split('\n'); - for(var i = 0; i < list.length; i++) { - if(!list[i]) continue; + const players = this.readString(reader); + const list = players.split('\n'); + for(const name of list) { + if(!name) continue; state.players.push({ - name:self.stripColorCodes(list[i]) + name: this.stripColorCodes(name) }); } - state.raw.options = self.stripColorCodes(self.readString(reader,buffer)); - state.raw.uri = self.readString(reader,buffer); - state.raw.globalids = self.readString(reader,buffer); - self.finish(state); + state.raw.options = this.stripColorCodes(this.readString(reader)); + state.raw.uri = this.readString(reader); + state.raw.globalids = this.readString(reader); + this.finish(state); return true; }); - }, - readUInt: function(reader) { - var a = reader.uint(2); - var b = reader.uint(2); + } + + readUInt(reader) { + const a = reader.uint(2); + const b = reader.uint(2); return (b<<16) + a; - }, - readString: function(reader,b) { - var len = reader.uint(2); + } + readString(reader) { + const len = reader.uint(2); if(!len) return ''; - var out = ''; - for(var i = 0; i < len; i+=2) { - var hi = reader.uint(1); - var lo = reader.uint(1); + let out = ''; + for(let i = 0; i < len; i += 2) { + const hi = reader.uint(1); + const lo = reader.uint(1); if(i+1 { + const reader = this.reader(buffer); - var header = reader.string({length:4}); - if(header != 'EYE1') return; + const header = reader.string({length:4}); + if(header !== 'EYE1') return; - state.raw.gamename = self.readString(reader); - state.raw.port = parseInt(self.readString(reader)); - state.name = self.readString(reader); - state.raw.gametype = self.readString(reader); - state.map = self.readString(reader); - state.raw.version = self.readString(reader); - state.password = self.readString(reader) == '1'; - state.raw.numplayers = parseInt(self.readString(reader)); - state.maxplayers = parseInt(self.readString(reader)); + state.raw.gamename = this.readString(reader); + state.raw.port = parseInt(this.readString(reader)); + state.name = this.readString(reader); + state.raw.gametype = this.readString(reader); + state.map = this.readString(reader); + state.raw.version = this.readString(reader); + state.password = this.readString(reader) === '1'; + state.raw.numplayers = parseInt(this.readString(reader)); + state.maxplayers = parseInt(this.readString(reader)); while(!reader.done()) { - var key = self.readString(reader); + const key = this.readString(reader); if(!key) break; - var value = self.readString(reader); + const value = this.readString(reader); state.raw[key] = value; } console.log(reader.rest()); while(!reader.done()) { - var flags = reader.uint(1); - var player = {}; - if(flags & 1) player.name = self.readString(reader); - if(flags & 2) player.team = self.readString(reader); - if(flags & 4) player.skin = self.readString(reader); - if(flags & 8) player.score = parseInt(self.readString(reader)); - if(flags & 16) player.ping = parseInt(self.readString(reader)); - if(flags & 32) player.time = parseInt(self.readString(reader)); + const flags = reader.uint(1); + const player = {}; + if(flags & 1) player.name = this.readString(reader); + if(flags & 2) player.team = this.readString(reader); + if(flags & 4) player.skin = this.readString(reader); + if(flags & 8) player.score = parseInt(this.readString(reader)); + if(flags & 16) player.ping = parseInt(this.readString(reader)); + if(flags & 32) player.time = parseInt(this.readString(reader)); state.players.push(player); } - self.finish(state); + this.finish(state); }); - }, - readString: function(reader) { - var len = reader.uint(1); + } + + readString(reader) { + const len = reader.uint(1); return reader.string({length:len-1}); } -}); +} + +module.exports = Ase; diff --git a/protocols/battlefield.js b/protocols/battlefield.js index 6c22483..7da1d79 100644 --- a/protocols/battlefield.js +++ b/protocols/battlefield.js @@ -1,19 +1,17 @@ -var async = require('async'); +const async = require('async'); -module.exports = require('./core').extend({ - init: function() { - this._super(); +class Battlefield extends require('./core') { + constructor() { + super(); this.encoding = 'latin1'; - }, - run: function(state) { - var self = this; - var decoded; + } + run(state) { async.series([ - function(c) { - self.query(['serverInfo'], function(data) { - if(self.debug) console.log(data); - if(data.shift() != 'OK') return self.fatal('Missing OK'); + (c) => { + this.query(['serverInfo'], (data) => { + if(this.debug) console.log(data); + if(data.shift() !== 'OK') return this.fatal('Missing OK'); state.raw.name = data.shift(); state.raw.numplayers = parseInt(data.shift()); @@ -23,10 +21,10 @@ module.exports = require('./core').extend({ state.raw.roundsplayed = parseInt(data.shift()); state.raw.roundstotal = parseInt(data.shift()); - var teamCount = data.shift(); + const teamCount = data.shift(); state.raw.teams = []; - for(var i = 0; i < teamCount; i++) { - var tickets = parseFloat(data.shift()); + for(let i = 0; i < teamCount; i++) { + const tickets = parseFloat(data.shift()); state.raw.teams.push({ tickets:tickets }); @@ -34,130 +32,129 @@ module.exports = require('./core').extend({ state.raw.targetscore = parseInt(data.shift()); data.shift(); - state.raw.ranked = (data.shift() == 'true'); - state.raw.punkbuster = (data.shift() == 'true'); - state.password = (data.shift() == 'true'); + state.raw.ranked = (data.shift() === 'true'); + state.raw.punkbuster = (data.shift() === 'true'); + state.password = (data.shift() === 'true'); state.raw.uptime = parseInt(data.shift()); state.raw.roundtime = parseInt(data.shift()); - if(self.isBadCompany2) { + if(this.isBadCompany2) { data.shift(); data.shift(); } state.raw.ip = data.shift(); state.raw.punkbusterversion = data.shift(); - state.raw.joinqueue = (data.shift() == 'true'); + state.raw.joinqueue = (data.shift() === 'true'); state.raw.region = data.shift(); - if(!self.isBadCompany2) { + if(!this.isBadCompany2) { state.raw.pingsite = data.shift(); state.raw.country = data.shift(); - state.raw.quickmatch = (data.shift() == 'true'); + state.raw.quickmatch = (data.shift() === 'true'); } c(); }); }, - function(c) { - self.query(['version'], function(data) { - if(self.debug) console.log(data); - if(data[0] != 'OK') return self.fatal('Missing OK'); + (c) => { + this.query(['version'], (data) => { + if(this.debug) console.log(data); + if(data[0] !== 'OK') return this.fatal('Missing OK'); state.raw.version = data[2]; c(); }); }, - function(c) { - self.query(['listPlayers','all'], function(data) { - if(self.debug) console.log(data); - if(data.shift() != 'OK') return self.fatal('Missing OK'); + (c) => { + this.query(['listPlayers','all'], (data) => { + if(this.debug) console.log(data); + if(data.shift() !== 'OK') return this.fatal('Missing OK'); - var fieldCount = parseInt(data.shift()); - var fields = []; - for(var i = 0; i < fieldCount; i++) { + const fieldCount = parseInt(data.shift()); + const fields = []; + for(let i = 0; i < fieldCount; i++) { fields.push(data.shift()); } - var numplayers = data.shift(); - for(var i = 0; i < numplayers; i++) { - var player = {}; - fields.forEach(function(key) { - var value = data.shift(); + const numplayers = data.shift(); + for(let i = 0; i < numplayers; i++) { + const player = {}; + for (let key of fields) { + let value = data.shift(); - if(key == 'teamId') key = 'team'; - else if(key == 'squadId') key = 'squad'; + if(key === 'teamId') key = 'team'; + else if(key === 'squadId') key = 'squad'; if( - key == 'kills' - || key == 'deaths' - || key == 'score' - || key == 'rank' - || key == 'team' - || key == 'squad' - || key == 'ping' - || key == 'type' + key === 'kills' + || key === 'deaths' + || key === 'score' + || key === 'rank' + || key === 'team' + || key === 'squad' + || key === 'ping' + || key === 'type' ) { value = parseInt(value); } player[key] = value; - }); + } state.players.push(player); } - self.finish(state); + this.finish(state); }); } ]); - }, - query: function(params,c) { - var self = this; - this.tcpSend(buildPacket(params), function(data) { - var decoded = self.decodePacket(data); + } + query(params,c) { + this.tcpSend(buildPacket(params), (data) => { + const decoded = this.decodePacket(data); if(!decoded) return false; c(decoded); return true; }); - }, - decodePacket: function(buffer) { + } + decodePacket(buffer) { if(buffer.length < 8) return false; - var reader = this.reader(buffer); - var header = reader.uint(4); - var totalLength = reader.uint(4); + const reader = this.reader(buffer); + const header = reader.uint(4); + const totalLength = reader.uint(4); if(buffer.length < totalLength) return false; - - var paramCount = reader.uint(4); - var params = []; - for(var i = 0; i < paramCount; i++) { - var len = reader.uint(4); + + const paramCount = reader.uint(4); + const params = []; + for(let i = 0; i < paramCount; i++) { + const len = reader.uint(4); params.push(reader.string({length:len})); - var strNull = reader.uint(1); + const strNull = reader.uint(1); } return params; } -}); +} function buildPacket(params) { - var self = this; - - var paramBuffers = []; - params.forEach(function(param) { - paramBuffers.push(new Buffer(param,'utf8')); - }); + const paramBuffers = []; + for (const param of params) { + paramBuffers.push(Buffer.from(param,'utf8')); + } - var totalLength = 12; - paramBuffers.forEach(function(paramBuffer) { + let totalLength = 12; + for (const paramBuffer of paramBuffers) { totalLength += paramBuffer.length+1+4; - }); + } - var b = new Buffer(totalLength); + const b = Buffer.alloc(totalLength); b.writeUInt32LE(0,0); b.writeUInt32LE(totalLength,4); b.writeUInt32LE(params.length,8); - var offset = 12; - paramBuffers.forEach(function(paramBuffer) { + let offset = 12; + for (const paramBuffer of paramBuffers) { b.writeUInt32LE(paramBuffer.length, offset); offset += 4; paramBuffer.copy(b, offset); offset += paramBuffer.length; b.writeUInt8(0, offset); offset += 1; - }); + } return b; } + +module.exports = Battlefield; \ No newline at end of file diff --git a/protocols/buildandshoot.js b/protocols/buildandshoot.js index 00fc46e..54c02b1 100644 --- a/protocols/buildandshoot.js +++ b/protocols/buildandshoot.js @@ -1,35 +1,37 @@ -var request = require('request'); +const request = require('request'); -module.exports = require('./core').extend({ - run: function(state) { - var self = this; +class BuildAndShoot extends require('./core') { + run(state) { request({ uri: 'http://'+this.options.address+':'+this.options.port_query+'/', timeout: 3000, - }, function(e,r,body) { - if(e) return self.fatal('HTTP error'); + }, (e,r,body) => { + if(e) return this.fatal('HTTP error'); - var m = body.match(/status server for (.*?)\r|\n/); + let m; + + m = body.match(/status server for (.*?)\r|\n/); if(m) state.name = m[1]; - var m = body.match(/Current uptime: (\d+)/); + m = body.match(/Current uptime: (\d+)/); if(m) state.raw.uptime = m[1]; - var m = body.match(/currently running (.*?) by /); + m = body.match(/currently running (.*?) by /); if(m) state.map = m[1]; - var m = body.match(/Current players: (\d+)\/(\d+)/); + m = body.match(/Current players: (\d+)\/(\d+)/); if(m) { state.raw.numplayers = m[1]; state.maxplayers = m[2]; } - var m = body.match(/class="playerlist"([^]+?)\/table/); + m = body.match(/class="playerlist"([^]+?)\/table/); if(m) { - var table = m[1]; - var pre = /[^]*([^]*)<\/td>[^]*([^]*)<\/td>[^]*([^]*)<\/td>[^]*([^]*)<\/td>/g; + const table = m[1]; + const pre = /[^]*([^]*)<\/td>[^]*([^]*)<\/td>[^]*([^]*)<\/td>[^]*([^]*)<\/td>/g; + let pm; while(pm = pre.exec(table)) { - if(pm[2] == 'Ping') continue; + if(pm[2] === 'Ping') continue; state.players.push({ name: pm[1], ping: pm[2], @@ -49,7 +51,9 @@ module.exports = require('./core').extend({ state.raw.url = 'aos://'+addr; } */ - self.finish(state); + this.finish(state); }); } -}); +} + +module.exports = BuildAndShoot; diff --git a/protocols/core.js b/protocols/core.js index 1ed7d4d..1331464 100644 --- a/protocols/core.js +++ b/protocols/core.js @@ -1,13 +1,12 @@ -var EventEmitter = require('events').EventEmitter, +const EventEmitter = require('events').EventEmitter, dns = require('dns'), net = require('net'), async = require('async'), - Class = require('../lib/Class'), Reader = require('../lib/reader'); -module.exports = Class.extend(EventEmitter,{ - init: function() { - this._super(); +class Core extends EventEmitter { + constructor() { + super(); this.options = { tcpTimeout: 1000, udpTimeout: 1000 @@ -19,13 +18,12 @@ module.exports = Class.extend(EventEmitter,{ this.byteorder = 'le'; this.delimiter = '\0'; - var self = this; - this.globalTimeoutTimer = setTimeout(function() { - self.fatal('timeout'); + this.globalTimeoutTimer = setTimeout(() => { + this.fatal('timeout'); },10000); - }, + } - fatal: function(err,noretry) { + fatal(err,noretry) { if(!noretry && this.attempt < this.maxAttempts) { this.attempt++; this.start(); @@ -33,8 +31,9 @@ module.exports = Class.extend(EventEmitter,{ } this.done({error: err.toString()}); - }, - initState: function() { + } + + initState() { return { name: '', map: '', @@ -46,15 +45,16 @@ module.exports = Class.extend(EventEmitter,{ players: [], bots: [] }; - }, - finalizeState: function(state) {}, + } - finish: function(state) { + finalizeState(state) {} + + finish(state) { this.finalizeState(state); this.done(state); - }, + } - done: function(state) { + done(state) { if(this.finished) return; clearTimeout(this.globalTimeoutTimer); @@ -73,13 +73,13 @@ module.exports = Class.extend(EventEmitter,{ this.finished = true; this.emit('finished',state); if(this.options.callback) this.options.callback(state); - }, + } - reset: function() { + reset() { if(this.timers) { - this.timers.forEach(function(timer) { + for (const timer of this.timers) { clearTimeout(timer); - }); + } } this.timers = []; @@ -90,58 +90,58 @@ module.exports = Class.extend(EventEmitter,{ this.udpTimeoutTimer = false; this.udpCallback = false; - }, - start: function() { - var self = this; - var options = self.options; + } + + start() { + const options = this.options; this.reset(); async.series([ - function(c) { + (c) => { // resolve host names if(!('host' in options)) return c(); if(options.host.match(/\d+\.\d+\.\d+\.\d+/)) { options.address = options.host; c(); } else { - self.parseDns(options.host,c); + this.parseDns(options.host,c); } }, - function(c) { + (c) => { // calculate query port if needed if(!('port_query' in options) && 'port' in options) { - var offset = options.port_query_offset || 0; + const offset = options.port_query_offset || 0; options.port_query = options.port + offset; } c(); }, - function(c) { + (c) => { // run - self.run(self.initState()); + this.run(this.initState()); } ]); - }, - parseDns: function(host,c) { - var self = this; + } - function resolveStandard(host,c) { - dns.lookup(host, function(err,address,family) { - if(err) return self.fatal(err); - self.options.address = address; + parseDns(host,c) { + const resolveStandard = (host,c) => { + dns.lookup(host, (err,address,family) => { + if(err) return this.fatal(err); + this.options.address = address; c(); }); - } - function resolveSrv(srv,host,c) { - dns.resolve(srv+'.'+host, 'SRV', function(err,addresses) { + }; + + const resolveSrv = (srv,host,c) => { + dns.resolve(srv+'.'+host, 'SRV', (err,addresses) => { if(err) return resolveStandard(host,c); if(addresses.length >= 1) { - var line = addresses[0]; - self.options.port = line.port; - var srvhost = line.name; + const line = addresses[0]; + this.options.port = line.port; + const srvhost = line.name; if(srvhost.match(/\d+\.\d+\.\d+\.\d+/)) { - self.options.address = srvhost; + this.options.address = srvhost; c(); } else { // resolve yet again @@ -151,51 +151,50 @@ module.exports = Class.extend(EventEmitter,{ } return resolveStandard(host,c); }); - } + }; if(this.srvRecord) resolveSrv(this.srvRecord,host,c); else resolveStandard(host,c); - }, + } // utils - reader: function(buffer) { + /** @returns {Reader} */ + reader(buffer) { return new Reader(this,buffer); - }, - translate: function(obj,trans) { - for(var from in trans) { - var to = trans[from]; + } + translate(obj,trans) { + for(const from of Object.keys(trans)) { + const to = trans[from]; if(from in obj) { if(to) obj[to] = obj[from]; delete obj[from]; } } - }, - setTimeout: function(c,t) { + } + setTimeout(c,t) { if(this.finished) return 0; - var id = setTimeout(c,t); + const id = setTimeout(c,t); this.timers.push(id); return id; - }, + } - - - trueTest: function(str) { - if(typeof str == 'boolean') return str; - if(typeof str == 'number') return str != 0; - if(typeof str == 'string') { - if(str.toLowerCase() == 'true') return true; - if(str == 'yes') return true; - if(str == '1') return true; + trueTest(str) { + if(typeof str === 'boolean') return str; + if(typeof str === 'number') return str !== 0; + if(typeof str === 'string') { + if(str.toLowerCase() === 'true') return true; + if(str === 'yes') return true; + if(str === '1') return true; } return false; - }, - debugBuffer: function(buffer) { - var out = ''; - var out2 = ''; - for(var i = 0; i < buffer.length; i++) { - var sliced = buffer.slice(i,i+1); + } + debugBuffer(buffer) { + let out = ''; + let out2 = ''; + for(let i = 0; i < buffer.length; i++) { + const sliced = buffer.slice(i,i+1); out += sliced.toString('hex')+' '; - var chr = sliced.toString(); + let chr = sliced.toString(); if(chr < ' ' || chr > '~') chr = ' '; out2 += chr+' '; if(out.length > 60) { @@ -206,22 +205,21 @@ module.exports = Class.extend(EventEmitter,{ } console.log(out); console.log(out2); - }, + } - _tcpConnect: function(c) { - var self = this; + _tcpConnect(c) { if(this.tcpSocket) return c(this.tcpSocket); - var connected = false; - var received = new Buffer(0); - var address = this.options.address; - var port = this.options.port_query; + let connected = false; + let received = Buffer.from([]); + const address = this.options.address; + const port = this.options.port_query; - var socket = this.tcpSocket = net.connect(port,address,function() { - if(self.debug) console.log(address+':'+port+" TCPCONNECTED"); + const socket = this.tcpSocket = net.connect(port,address,() => { + if(this.debug) console.log(address+':'+port+" TCPCONNECTED"); connected = true; c(socket); }); @@ -229,76 +227,74 @@ module.exports = Class.extend(EventEmitter,{ socket.setNoDelay(true); if(this.debug) console.log(address+':'+port+" TCPCONNECT"); - var writeHook = socket.write; - socket.write = function(data) { - if(self.debug) console.log(address+':'+port+" TCP--> "+data.toString('hex')); - writeHook.apply(this,arguments); - } + const writeHook = socket.write; + socket.write = (...args) => { + if(this.debug) console.log(address+':'+port+" TCP--> "+args[0].toString('hex')); + writeHook.apply(socket,args); + }; - socket.on('error', function() {}); - socket.on('close', function() { - if(!self.tcpCallback) return; - if(connected) return self.fatal('Socket closed while waiting on TCP'); - else return self.fatal('TCP Connection Refused'); + socket.on('error', () => {}); + socket.on('close', () => { + if(!this.tcpCallback) return; + if(connected) return this.fatal('Socket closed while waiting on TCP'); + else return this.fatal('TCP Connection Refused'); }); - socket.on('data', function(data) { - if(!self.tcpCallback) return; - if(self.debug) console.log(address+':'+port+" <--TCP "+data.toString('hex')); + socket.on('data', (data) => { + if(!this.tcpCallback) return; + if(this.debug) console.log(address+':'+port+" <--TCP "+data.toString('hex')); received = Buffer.concat([received,data]); - if(self.tcpCallback(received)) { - clearTimeout(self.tcpTimeoutTimer); - self.tcpCallback = false; - received = new Buffer(0); + if(this.tcpCallback(received)) { + clearTimeout(this.tcpTimeoutTimer); + this.tcpCallback = false; + received = Buffer.from([]); } }); - }, - tcpSend: function(buffer,ondata) { - var self = this; - process.nextTick(function() { - if(self.tcpCallback) return self.fatal('Attempted to send TCP packet while still waiting on a managed response'); - self._tcpConnect(function(socket) { + } + tcpSend(buffer,ondata) { + process.nextTick(() => { + if(this.tcpCallback) return this.fatal('Attempted to send TCP packet while still waiting on a managed response'); + this._tcpConnect((socket) => { socket.write(buffer); }); if(!ondata) return; - self.tcpTimeoutTimer = self.setTimeout(function() { - self.tcpCallback = false; - self.fatal('TCP Watchdog Timeout'); - },self.options.tcpTimeout); - self.tcpCallback = ondata; + this.tcpTimeoutTimer = this.setTimeout(() => { + this.tcpCallback = false; + this.fatal('TCP Watchdog Timeout'); + },this.options.tcpTimeout); + this.tcpCallback = ondata; }); - }, + } - udpSend: function(buffer,onpacket,ontimeout) { - var self = this; - process.nextTick(function() { - if(self.udpCallback) return self.fatal('Attempted to send UDP packet while still waiting on a managed response'); - self._udpSendNow(buffer); + udpSend(buffer,onpacket,ontimeout) { + process.nextTick(() => { + if(this.udpCallback) return this.fatal('Attempted to send UDP packet while still waiting on a managed response'); + this._udpSendNow(buffer); if(!onpacket) return; - self.udpTimeoutTimer = self.setTimeout(function() { - self.udpCallback = false; - var timeout = false; + this.udpTimeoutTimer = this.setTimeout(() => { + this.udpCallback = false; + let timeout = false; if(!ontimeout || ontimeout() !== true) timeout = true; - if(timeout) self.fatal('UDP Watchdog Timeout'); - },self.options.udpTimeout); - self.udpCallback = onpacket; + if(timeout) this.fatal('UDP Watchdog Timeout'); + },this.options.udpTimeout); + this.udpCallback = onpacket; }); - }, - _udpSendNow: function(buffer) { + } + _udpSendNow(buffer) { if(!('port_query' in this.options)) return this.fatal('Attempted to send without setting a port'); if(!('address' in this.options)) return this.fatal('Attempted to send without setting an address'); - if(typeof buffer == 'string') buffer = new Buffer(buffer,'binary'); + if(typeof buffer === 'string') buffer = Buffer.from(buffer,'binary'); if(this.debug) console.log(this.options.address+':'+this.options.port_query+" UDP--> "+buffer.toString('hex')); this.udpSocket.send(buffer,0,buffer.length,this.options.port_query,this.options.address); - }, - _udpResponse: function(buffer) { + } + _udpResponse(buffer) { if(this.udpCallback) { - var result = this.udpCallback(buffer); + const result = this.udpCallback(buffer); if(result === true) { // we're done with this udp session clearTimeout(this.udpTimeoutTimer); @@ -307,6 +303,8 @@ module.exports = Class.extend(EventEmitter,{ } else { this.udpResponse(buffer); } - }, - udpResponse: function() {} -}); + } + udpResponse() {} +} + +module.exports = Core; diff --git a/protocols/doom3.js b/protocols/doom3.js index 9379fba..9a68c4e 100644 --- a/protocols/doom3.js +++ b/protocols/doom3.js @@ -1,41 +1,38 @@ -module.exports = require('./core').extend({ - init: function() { - this._super(); +class Doom3 extends require('./core') { + constructor() { + super(); this.pretty = 'Doom 3'; this.encoding = 'latin1'; this.isEtqw = false; this.hasSpaceBeforeClanTag = false; this.hasClanTag = false; this.hasTypeFlag = false; - }, - run: function(state) { - var self = this; + } + run(state) { + this.udpSend('\xff\xffgetInfo\x00PiNGPoNG\x00', (buffer) => { + const reader = this.reader(buffer); - this.udpSend('\xff\xffgetInfo\x00PiNGPoNG\x00',function(buffer) { - var reader = self.reader(buffer); + const header = reader.uint(2); + if(header !== 0xffff) return; + const header2 = reader.string(); + if(header2 !== 'infoResponse') return; - var header = reader.uint(2); - if(header != 0xffff) return; - var header2 = reader.string(); - if(header2 != 'infoResponse') return; - - var tailSize = 5; - if(self.isEtqw) { - var taskId = reader.uint(4); + if(this.isEtqw) { + const taskId = reader.uint(4); } - - var challenge = reader.uint(4); - var protoVersion = reader.uint(4); + + const challenge = reader.uint(4); + const protoVersion = reader.uint(4); state.raw.protocolVersion = (protoVersion>>16)+'.'+(protoVersion&0xffff); - if(self.isEtqw) { - var size = reader.uint(4); + if(this.isEtqw) { + const size = reader.uint(4); } while(!reader.done()) { - var key = reader.string(); - var value = self.stripColors(reader.string()); - if(key == 'si_map') { + const key = reader.string(); + let value = this.stripColors(reader.string()); + if(key === 'si_map') { value = value.replace('maps/',''); value = value.replace('.entities',''); } @@ -43,20 +40,20 @@ module.exports = require('./core').extend({ state.raw[key] = value; } - var i = 0; + let i = 0; while(!reader.done()) { i++; - var player = {}; + const player = {}; player.id = reader.uint(1); - if(player.id == 32) break; + if(player.id === 32) break; player.ping = reader.uint(2); - if(!self.isEtqw) player.rate = reader.uint(4); - player.name = self.stripColors(reader.string()); - if(self.hasClanTag) { - if(self.hasSpaceBeforeClanTag) reader.uint(1); - player.clantag = self.stripColors(reader.string()); + if(!this.isEtqw) player.rate = reader.uint(4); + player.name = this.stripColors(reader.string()); + if(this.hasClanTag) { + if(this.hasSpaceBeforeClanTag) reader.uint(1); + player.clantag = this.stripColors(reader.string()); } - if(self.hasTypeFlag) player.typeflag = reader.uint(1); + if(this.hasTypeFlag) player.typeflag = reader.uint(1); if(!player.ping || player.typeflag) state.bots.push(player); @@ -65,15 +62,15 @@ module.exports = require('./core').extend({ } state.raw.osmask = reader.uint(4); - if(self.isEtqw) { + if(this.isEtqw) { state.raw.ranked = reader.uint(1); state.raw.timeleft = reader.uint(4); state.raw.gamestate = reader.uint(1); state.raw.servertype = reader.uint(1); // 0 = regular, 1 = tv - if(state.raw.servertype == 0) { + if(state.raw.servertype === 0) { state.raw.interestedClients = reader.uint(1); - } else if(state.raw.servertype == 1) { + } else if(state.raw.servertype === 1) { state.raw.connectedClients = reader.uint(4); state.raw.maxClients = reader.uint(4); } @@ -82,14 +79,17 @@ module.exports = require('./core').extend({ if(state.raw.si_name) state.name = state.raw.si_name; if(state.raw.si_map) state.map = state.raw.si_map; if(state.raw.si_maxplayers) state.maxplayers = parseInt(state.raw.si_maxplayers); - if(state.raw.si_usepass == '1') state.password = true; + if(state.raw.si_usepass === '1') state.password = true; - self.finish(state); + this.finish(state); return true; }); - }, - stripColors: function(str) { + } + + stripColors(str) { // uses quake 3 color codes return str.replace(/\^(X.{6}|.)/g,''); } -}); +} + +module.exports = Doom3; diff --git a/protocols/ffow.js b/protocols/ffow.js index e346399..96e1807 100644 --- a/protocols/ffow.js +++ b/protocols/ffow.js @@ -1,14 +1,12 @@ -module.exports = require('./valve').extend({ - init: function() { - this._super(); +class Ffow extends require('./valve') { + constructor() { + super(); this.byteorder = 'be'; this.legacyChallenge = true; - }, - queryInfo: function(state,c) { - var self = this; - self.sendPacket(0x46,false,'LSQ',0x49,function(b) { - var reader = self.reader(b); - + } + queryInfo(state,c) { + this.sendPacket(0x46,false,'LSQ',0x49, (b) => { + const reader = this.reader(b); state.raw.protocol = reader.uint(1); state.name = reader.string(); state.map = reader.string(); @@ -30,4 +28,6 @@ module.exports = require('./valve').extend({ c(); }); } -}); +} + +module.exports = Ffow; diff --git a/protocols/gamespy1.js b/protocols/gamespy1.js index f2379c4..e5498f4 100644 --- a/protocols/gamespy1.js +++ b/protocols/gamespy1.js @@ -1,79 +1,80 @@ -var async = require('async'); +const async = require('async'); -module.exports = require('./core').extend({ - init: function() { - this._super(); +class Gamespy1 extends require('./core') { + constructor() { + super(); this.sessionId = 1; this.encoding = 'latin1'; this.byteorder = 'be'; - }, - run: function(state) { - var self = this; + } + run(state) { async.series([ - function(c) { - self.sendPacket('info', function(data) { + (c) => { + this.sendPacket('info', (data) => { state.raw = data; if('hostname' in state.raw) state.name = state.raw.hostname; if('mapname' in state.raw) state.map = state.raw.mapname; - if(self.trueTest(state.raw.password)) state.password = true; + if(this.trueTest(state.raw.password)) state.password = true; if('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers); c(); }); }, - function(c) { - self.sendPacket('rules', function(data) { + (c) => { + this.sendPacket('rules', (data) => { state.raw.rules = data; c(); }); }, - function(c) { - self.sendPacket('players', function(data) { - var players = {}; - var teams = {}; - for(var ident in data) { - var split = ident.split('_'); - var key = split[0]; - var id = split[1]; - var value = data[ident]; + (c) => { + this.sendPacket('players', (data) => { + const players = {}; + const teams = {}; + for(const ident of Object.keys(data)) { + const split = ident.split('_'); + let key = split[0]; + const id = split[1]; + let value = data[ident]; - if(key == 'teamname') { + if(key === 'teamname') { teams[id] = value; } else { if(!(id in players)) players[id] = {}; - if(key == 'playername') key = 'name'; - else if(key == 'team') value = parseInt(value); - else if(key == 'score' || key == 'ping' || key == 'deaths') value = parseInt(value); + if(key === 'playername') key = 'name'; + else if(key === 'team') value = parseInt(value); + else if(key === 'score' || key === 'ping' || key === 'deaths') value = parseInt(value); players[id][key] = value; } } state.raw.teams = teams; - for(var i in players) state.players.push(players[i]); - self.finish(state); + for(const id of Object.keys(players)) { + state.players.push(players[id]); + } + this.finish(state); }); } ]); - }, - sendPacket: function(type,callback) { - var self = this; - var queryId = ''; - var output = {}; - this.udpSend('\\'+type+'\\',function(buffer) { - var reader = self.reader(buffer); - var str = reader.string({length:buffer.length}); - var split = str.split('\\'); + } + + sendPacket(type,callback) { + const queryId = ''; + const output = {}; + this.udpSend('\\'+type+'\\', (buffer) => { + const reader = this.reader(buffer); + const str = reader.string({length:buffer.length}); + const split = str.split('\\'); split.shift(); - var data = {}; + const data = {}; while(split.length) { - var key = split.shift(); - var value = split.shift() || ''; + const key = split.shift(); + const value = split.shift() || ''; data[key] = value; } if(!('queryid' in data)) return; - if(queryId && data.queryid != queryId) return; - for(var i in data) output[i] = data[i]; + if(queryId && data.queryid !== queryId) return; + for(const i of Object.keys(data)) output[i] = data[i]; if('final' in output) { delete output.final; delete output.queryid; @@ -82,4 +83,6 @@ module.exports = require('./core').extend({ } }); } -}); +} + +module.exports = Gamespy1; diff --git a/protocols/gamespy2.js b/protocols/gamespy2.js index 86b4596..356c066 100644 --- a/protocols/gamespy2.js +++ b/protocols/gamespy2.js @@ -1,86 +1,86 @@ -module.exports = require('./core').extend({ - init: function() { - this._super(); +class Gamespy2 extends require('./core') { + constructor() { + super(); this.sessionId = 1; this.encoding = 'latin1'; this.byteorder = 'be'; - }, - run: function(state) { - var self = this; + } - var request = new Buffer([0xfe,0xfd,0x00,0x00,0x00,0x00,0x01,0xff,0xff,0xff]); - var packets = []; + run(state) { + const request = Buffer.from([0xfe,0xfd,0x00,0x00,0x00,0x00,0x01,0xff,0xff,0xff]); + const packets = []; this.udpSend(request, - function(buffer) { - if(packets.length && buffer.readUInt8(0) == 0) + (buffer) => { + if(packets.length && buffer.readUInt8(0) === 0) buffer = buffer.slice(1); packets.push(buffer); }, - function() { - var buffer = Buffer.concat(packets); - var reader = self.reader(buffer); - var header = reader.uint(1); - if(header != 0) return; - var pingId = reader.uint(4); - if(pingId != 1) return; + () => { + const buffer = Buffer.concat(packets); + const reader = this.reader(buffer); + const header = reader.uint(1); + if(header !== 0) return; + const pingId = reader.uint(4); + if(pingId !== 1) return; while(!reader.done()) { - var key = reader.string(); - var value = reader.string(); + const key = reader.string(); + const value = reader.string(); if(!key) break; state.raw[key] = value; } if('hostname' in state.raw) state.name = state.raw.hostname; if('mapname' in state.raw) state.map = state.raw.mapname; - if(self.trueTest(state.raw.password)) state.password = true; + if(this.trueTest(state.raw.password)) state.password = true; if('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers); - state.players = self.readFieldData(reader); - state.raw.teams = self.readFieldData(reader); + state.players = this.readFieldData(reader); + state.raw.teams = this.readFieldData(reader); - self.finish(state); + this.finish(state); return true; } ); - }, - readFieldData: function(reader) { - var count = reader.uint(1); + } + + readFieldData(reader) { + const count = reader.uint(1); // count is unreliable (often it's wrong), so we don't use it. // read until we hit an empty first field string if(this.debug) console.log("Reading fields, starting at: "+reader.rest()); - var fields = []; + const fields = []; while(!reader.done()) { - var field = reader.string(); + let field = reader.string(); if(!field) break; if(field.charCodeAt(0) <= 2) field = field.substring(1); fields.push(field); if(this.debug) console.log("field:"+field); } - var units = []; + const units = []; outer: while(!reader.done()) { - var unit = {}; - for(var iField = 0; iField < fields.length; iField++) { - var key = fields[iField]; - var value = reader.string(); - if(!value && iField == 0) break outer; + const unit = {}; + for(let iField = 0; iField < fields.length; iField++) { + let key = fields[iField]; + let value = reader.string(); + if(!value && iField === 0) break outer; if(this.debug) console.log("value:"+value); - if(key == 'player_') key = 'name'; - else if(key == 'score_') key = 'score'; - else if(key == 'deaths_') key = 'deaths'; - else if(key == 'ping_') key = 'ping'; - else if(key == 'team_') key = 'team'; - else if(key == 'kills_') key = 'kills'; - else if(key == 'team_t') key = 'name'; - else if(key == 'tickets_t') key = 'tickets'; + if(key === 'player_') key = 'name'; + else if(key === 'score_') key = 'score'; + else if(key === 'deaths_') key = 'deaths'; + else if(key === 'ping_') key = 'ping'; + else if(key === 'team_') key = 'team'; + else if(key === 'kills_') key = 'kills'; + else if(key === 'team_t') key = 'name'; + else if(key === 'tickets_t') key = 'tickets'; if( - key == 'score' || key == 'deaths' - || key == 'ping' || key == 'team' - || key == 'kills' || key == 'tickets' + key === 'score' || key === 'deaths' + || key === 'ping' || key === 'team' + || key === 'kills' || key === 'tickets' ) { if(value === '') continue; value = parseInt(value); @@ -93,4 +93,6 @@ module.exports = require('./core').extend({ return units; } -}); +} + +module.exports = Gamespy2; diff --git a/protocols/gamespy3.js b/protocols/gamespy3.js index 324da33..73bcb07 100644 --- a/protocols/gamespy3.js +++ b/protocols/gamespy3.js @@ -1,68 +1,68 @@ -var async = require('async'); +const async = require('async'); -module.exports = require('./core').extend({ - init: function() { - this._super(); +class Gamespy3 extends require('./core') { + constructor() { + super(); this.sessionId = 1; this.encoding = 'latin1'; this.byteorder = 'be'; this.noChallenge = false; this.useOnlySingleSplit = false; this.isJc2mp = false; - }, - run: function(state) { - var self = this; - var challenge,packets; + } + + run(state) { + let challenge,packets; async.series([ - function(c) { - if(self.noChallenge) return c(); - self.sendPacket(9,false,false,false,function(buffer) { - var reader = self.reader(buffer); + (c) => { + if(this.noChallenge) return c(); + this.sendPacket(9,false,false,false,(buffer) => { + const reader = this.reader(buffer); challenge = parseInt(reader.string()); c(); }); }, - function(c) { - var requestPayload; - if(self.isJc2mp) { + (c) => { + let requestPayload; + if(this.isJc2mp) { // they completely alter the protocol. because why not. - requestPayload = new Buffer([0xff,0xff,0xff,0x02]); + requestPayload = Buffer.from([0xff,0xff,0xff,0x02]); } else { - requestPayload = new Buffer([0xff,0xff,0xff,0x01]); + requestPayload = Buffer.from([0xff,0xff,0xff,0x01]); } - self.sendPacket(0,challenge,requestPayload,true,function(b) { + this.sendPacket(0,challenge,requestPayload,true,(b) => { packets = b; c(); }); }, - function(c) { + (c) => { // iterate over the received packets // the first packet will start off with k/v pairs, followed with data fields // the following packets will only have data fields state.raw.playerTeamInfo = {}; - for(var iPacket = 0; iPacket < packets.length; iPacket++) { - var packet = packets[iPacket]; - var reader = self.reader(packet); + for(let iPacket = 0; iPacket < packets.length; iPacket++) { + const packet = packets[iPacket]; + const reader = this.reader(packet); - if(self.debug) { + if(this.debug) { console.log("+++"+packet.toString('hex')); console.log(":::"+packet.toString('ascii')); } // Parse raw server key/values - if(iPacket == 0) { + if(iPacket === 0) { while(!reader.done()) { - var key = reader.string(); + const key = reader.string(); if(!key) break; - var value = reader.string(); + let value = reader.string(); // reread the next line if we hit the weird ut3 bug - if(value == 'p1073741829') value = reader.string(); + if(value === 'p1073741829') value = reader.string(); state.raw[key] = value; } @@ -70,37 +70,37 @@ module.exports = require('./core').extend({ // Parse player, team, item array state - if(self.isJc2mp) { + if(this.isJc2mp) { state.raw.numPlayers2 = reader.uint(2); while(!reader.done()) { - var player = {}; + const player = {}; player.name = reader.string(); player.steamid = reader.string(); player.ping = reader.uint(2); state.players.push(player); } } else { - var firstMode = true; + let firstMode = true; while(!reader.done()) { - var mode = reader.string(); + let mode = reader.string(); if(mode.charCodeAt(0) <= 2) mode = mode.substring(1); if(!mode) continue; - var offset = 0; - if(iPacket != 0 && firstMode) offset = reader.uint(1); + let offset = 0; + if(iPacket !== 0 && firstMode) offset = reader.uint(1); reader.skip(1); firstMode = false; - var modeSplit = mode.split('_'); - var modeName = modeSplit[0]; - var modeType = modeSplit.length > 1 ? modeSplit[1] : 'no_'; + const modeSplit = mode.split('_'); + const modeName = modeSplit[0]; + const modeType = modeSplit.length > 1 ? modeSplit[1] : 'no_'; if(!(modeType in state.raw.playerTeamInfo)) { state.raw.playerTeamInfo[modeType] = []; } - var store = state.raw.playerTeamInfo[modeType]; + const store = state.raw.playerTeamInfo[modeType]; while(!reader.done()) { - var item = reader.string(); + const item = reader.string(); if(!item) break; while(store.length <= offset) { store.push({}); } @@ -114,41 +114,40 @@ module.exports = require('./core').extend({ c(); }, - function(c) { + (c) => { // Turn all that raw state into something useful if('hostname' in state.raw) state.name = state.raw.hostname; else if('servername' in state.raw) state.name = state.raw.servername; if('mapname' in state.raw) state.map = state.raw.mapname; - if(state.raw.password == '1') state.password = true; + if(state.raw.password === '1') state.password = true; if('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers); if('' in state.raw.playerTeamInfo) { - state.raw.playerTeamInfo[''].forEach(function(playerInfo) { - var player = {}; - for(var from in playerInfo) { - var key = from; - var value = playerInfo[from]; + for (const playerInfo of state.raw.playerTeamInfo['']) { + const player = {}; + for(const from of Object.keys(playerInfo)) { + let key = from; + let value = playerInfo[from]; - if(key == 'player') key = 'name'; - if(key == 'score' || key == 'ping' || key == 'team' || key == 'deaths' || key == 'pid') value = parseInt(value); + if(key === 'player') key = 'name'; + if(key === 'score' || key === 'ping' || key === 'team' || key === 'deaths' || key === 'pid') value = parseInt(value); player[key] = value; } state.players.push(player); - }) + } } - self.finish(state); + this.finish(state); } ]); - }, - sendPacket: function(type,challenge,payload,assemble,c) { - var self = this; + } - var challengeLength = (this.noChallenge || challenge === false) ? 0 : 4; - var payloadLength = payload ? payload.length : 0; + sendPacket(type,challenge,payload,assemble,c) { + const challengeLength = (this.noChallenge || challenge === false) ? 0 : 4; + const payloadLength = payload ? payload.length : 0; - var b = new Buffer(7 + challengeLength + payloadLength); + const b = Buffer.alloc(7 + challengeLength + payloadLength); b.writeUInt8(0xFE, 0); b.writeUInt8(0xFD, 1); b.writeUInt8(type, 2); @@ -156,20 +155,20 @@ module.exports = require('./core').extend({ if(challengeLength) b.writeInt32BE(challenge, 7); if(payloadLength) payload.copy(b, 7+challengeLength); - var numPackets = 0; - var packets = {}; - this.udpSend(b,function(buffer) { - var reader = self.reader(buffer); - var iType = reader.uint(1); - if(iType != type) return; - var iSessionId = reader.uint(4); - if(iSessionId != self.sessionId) return; + let numPackets = 0; + const packets = {}; + this.udpSend(b,(buffer) => { + const reader = this.reader(buffer); + const iType = reader.uint(1); + if(iType !== type) return; + const iSessionId = reader.uint(4); + if(iSessionId !== this.sessionId) return; if(!assemble) { c(reader.rest()); return true; } - if(self.useOnlySingleSplit) { + if(this.useOnlySingleSplit) { // has split headers, but they are worthless and only one packet is used reader.skip(11); c([reader.rest()]); @@ -177,26 +176,26 @@ module.exports = require('./core').extend({ } reader.skip(9); // filler data -- usually set to 'splitnum\0' - var id = reader.uint(1); - var last = (id & 0x80); + let id = reader.uint(1); + const last = (id & 0x80); id = id & 0x7f; if(last) numPackets = id+1; reader.skip(1); // "another 'packet number' byte, but isn't understood." packets[id] = reader.rest(); - if(self.debug) { + if(this.debug) { console.log("Received packet #"+id); if(last) console.log("(last)"); } - if(!numPackets || Object.keys(packets).length != numPackets) return; + if(!numPackets || Object.keys(packets).length !== numPackets) return; // assemble the parts - var list = []; - for(var i = 0; i < numPackets; i++) { + const list = []; + for(let i = 0; i < numPackets; i++) { if(!(i in packets)) { - self.fatal('Missing packet #'+i); + this.fatal('Missing packet #'+i); return true; } list.push(packets[i]); @@ -205,4 +204,6 @@ module.exports = require('./core').extend({ return true; }); } -}); +} + +module.exports = Gamespy3; diff --git a/protocols/hexenworld.js b/protocols/hexenworld.js index 5153b72..5e1dc03 100644 --- a/protocols/hexenworld.js +++ b/protocols/hexenworld.js @@ -1,6 +1,9 @@ -module.exports = require('./quake2').extend({ - init: function() { - this._super(); +class HexenWorld extends require('./quake1') { + constructor() { + super(); this.sendHeader = '\xFFstatus\x0a'; + this.responseHeader = '\xffn'; } -}); +} + +module.exports = HexenWorld; diff --git a/protocols/jc2mp.js b/protocols/jc2mp.js index a76f0f6..eb9b121 100644 --- a/protocols/jc2mp.js +++ b/protocols/jc2mp.js @@ -1,24 +1,18 @@ -/* -module.exports = require('./valve').extend({ - init: function() { - this._super(); - } -}); -*/ - // supposedly, gamespy3 is the "official" query protocol for jcmp, // but it's broken (requires useOnlySingleSplit), and doesn't include player names -module.exports = require('./gamespy3').extend({ - init: function() { - this._super(); +class Jc2mp extends require('./gamespy3') { + constructor() { + super(); this.useOnlySingleSplit = true; - }, - finalizeState: function(state) { - this._super(state); + } + finalizeState(state) { + super.finalizeState(state); if(!state.players.length && parseInt(state.raw.numplayers)) { - for(var i = 0; i < parseInt(state.raw.numplayers); i++) { + for(let i = 0; i < parseInt(state.raw.numplayers); i++) { state.players.push({}); } } } -}); +} + +module.exports = Jc2mp; diff --git a/protocols/killingfloor.js b/protocols/killingfloor.js index 44d2d87..b27883c 100644 --- a/protocols/killingfloor.js +++ b/protocols/killingfloor.js @@ -1,6 +1,8 @@ -module.exports = require('./unreal2').extend({ - readExtraInfo: function(reader,state) { +class KillingFloor extends require('./unreal2') { + readExtraInfo(reader,state) { state.raw.wavecurrent = reader.uint(4); state.raw.wavetotal = reader.uint(4); } -}); +} + +module.exports = KillingFloor; diff --git a/protocols/m2mp.js b/protocols/m2mp.js index 5522a14..f0eed74 100644 --- a/protocols/m2mp.js +++ b/protocols/m2mp.js @@ -1,37 +1,39 @@ -module.exports = require('./core').extend({ - init: function() { - this._super(); +class M2mp extends require('./core') { + constructor() { + super(); this.encoding = 'latin1'; - }, - run: function(state) { - var self = this; + } - this.udpSend('M2MP',function(buffer) { - var reader = self.reader(buffer); + run(state) { + this.udpSend('M2MP',(buffer) => { + const reader = this.reader(buffer); + + const header = reader.string({length:4}); + if(header !== 'M2MP') return; - var header = reader.string({length:4}); - if(header != 'M2MP') return; - - state.name = self.readString(reader); - state.raw.numplayers = self.readString(reader); - state.maxplayers = self.readString(reader); - state.raw.gamemode = self.readString(reader); + state.name = this.readString(reader); + state.raw.numplayers = this.readString(reader); + state.maxplayers = this.readString(reader); + state.raw.gamemode = this.readString(reader); state.password = !!reader.uint(1); while(!reader.done()) { - var name = self.readString(reader); + const name = this.readString(reader); if(!name) break; state.players.push({ name:name }); } - self.finish(state); + this.finish(state); return true; }); - }, - readString: function(reader) { - var length = reader.uint(1); + } + + readString(reader) { + const length = reader.uint(1); return reader.string({length:length-1}); - }, -}); + } +} + +module.exports = M2mp; diff --git a/protocols/minecraftping.js b/protocols/minecraftping.js index 7197c1f..d9a106f 100644 --- a/protocols/minecraftping.js +++ b/protocols/minecraftping.js @@ -1,12 +1,12 @@ -var varint = require('varint'), +const varint = require('varint'), async = require('async'); function varIntBuffer(num) { - return new Buffer(varint.encode(num)); + return Buffer.alloc(varint.encode(num)); } function buildPacket(id,data) { - if(!data) data = new Buffer(0); - var idBuffer = varIntBuffer(id); + if(!data) data = Buffer.from([]); + const idBuffer = varIntBuffer(id); return Buffer.concat([ varIntBuffer(data.length+idBuffer.length), idBuffer, @@ -14,21 +14,20 @@ function buildPacket(id,data) { ]); } -module.exports = require('./core').extend({ - run: function(state) { - var self = this; - var receivedData; +class MinecraftPing extends require('./core') { + run(state) { + let receivedData; async.series([ - function(c) { + (c) => { // build and send handshake and status TCP packet - var portBuf = new Buffer(2); - portBuf.writeUInt16BE(self.options.port_query,0); + const portBuf = Buffer.alloc(2); + portBuf.writeUInt16BE(this.options.port_query,0); - var addressBuf = new Buffer(self.options.address,'utf8'); + const addressBuf = Buffer.from(this.options.address,'utf8'); - var bufs = [ + const bufs = [ varIntBuffer(4), varIntBuffer(addressBuf.length), addressBuf, @@ -36,14 +35,14 @@ module.exports = require('./core').extend({ varIntBuffer(1) ]; - var outBuffer = Buffer.concat([ + const outBuffer = Buffer.concat([ buildPacket(0,Buffer.concat(bufs)), buildPacket(0) ]); - self.tcpSend(outBuffer, function(data) { + this.tcpSend(outBuffer, (data) => { if(data.length < 10) return false; - var expected = varint.decode(data); + const expected = varint.decode(data); data = data.slice(varint.decode.bytes); if(data.length < expected) return false; receivedData = data; @@ -51,39 +50,39 @@ module.exports = require('./core').extend({ return true; }); }, - function(c) { + (c) => { // parse response - var data = receivedData; - var packetId = varint.decode(data); - if(self.debug) console.log("Packet ID: "+packetId); + let data = receivedData; + const packetId = varint.decode(data); + if(this.debug) console.log("Packet ID: "+packetId); data = data.slice(varint.decode.bytes); - var strLen = varint.decode(data); - if(self.debug) console.log("String Length: "+strLen); + const strLen = varint.decode(data); + if(this.debug) console.log("String Length: "+strLen); data = data.slice(varint.decode.bytes); - var str = data.toString('utf8'); - if(self.debug) { + const str = data.toString('utf8'); + if(this.debug) { console.log(str); } - var json; + let json; try { json = JSON.parse(str); delete json.favicon; } catch(e) { - return self.fatal('Invalid JSON'); + return this.fatal('Invalid JSON'); } state.raw.version = json.version.name; state.maxplayers = json.players.max; state.raw.description = json.description.text; if(json.players.sample) { - for(var i = 0; i < json.players.sample.length; i++) { + for(const player of json.players.sample) { state.players.push({ - id: json.players.sample[i].id, - name: json.players.sample[i].name + id: player.id, + name: player.name }); } } @@ -91,8 +90,10 @@ module.exports = require('./core').extend({ state.players.push({}); } - self.finish(state); + this.finish(state); } ]); } -}); +} + +module.exports = MinecraftPing; diff --git a/protocols/mumble.js b/protocols/mumble.js index c4313fd..5bc4077 100644 --- a/protocols/mumble.js +++ b/protocols/mumble.js @@ -1,17 +1,14 @@ -var async = require('async'); - -module.exports = require('./core').extend({ - init: function() { - this._super(); +class Mumble extends require('./core') { + constructor() { + super(); this.options.tcpTimeout = 5000; - }, - run: function(state) { - var self = this; - - this.tcpSend('json', function(buffer) { + } + + run(state) { + this.tcpSend('json', (buffer) => { if(buffer.length < 10) return; - var str = buffer.toString(); - var json; + const str = buffer.toString(); + let json; try { json = JSON.parse(str); } catch(e) { @@ -21,24 +18,26 @@ module.exports = require('./core').extend({ state.raw = json; state.name = json.name; - - var channelStack = [state.raw.root]; + + let channelStack = [state.raw.root]; while(channelStack.length) { - var channel = channelStack.shift(); - channel.description = self.cleanComment(channel.description); + const channel = channelStack.shift(); + channel.description = this.cleanComment(channel.description); channelStack = channelStack.concat(channel.channels); - for(var i = 0; i < channel.users.length; i++) { - var user = channel.users[i]; - user.comment = self.cleanComment(user.comment); + for(const user of channel.users) { + user.comment = this.cleanComment(user.comment); state.players.push(user); } } - self.finish(state); + this.finish(state); return true; }); - }, - cleanComment: function(str) { + } + + cleanComment(str) { return str.replace(/<.*>/g,''); } -}); +} + +module.exports = Mumble; diff --git a/protocols/mumbleping.js b/protocols/mumbleping.js index acac0b8..60b923d 100644 --- a/protocols/mumbleping.js +++ b/protocols/mumbleping.js @@ -1,16 +1,13 @@ -var async = require('async'); - -module.exports = require('./core').extend({ - init: function() { - this._super(); +class MumblePing extends require('./core') { + constructor() { + super(); this.byteorder = 'be'; - }, - run: function(state) { - var self = this; - - this.udpSend('\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08', function(buffer) { + } + + run(state) { + this.udpSend('\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08', (buffer) => { if(buffer.length < 24) return; - var reader = self.reader(buffer); + const reader = this.reader(buffer); reader.skip(1); state.raw.versionMajor = reader.uint(1); state.raw.versionMinor = reader.uint(1); @@ -19,11 +16,13 @@ module.exports = require('./core').extend({ state.raw.numplayers = reader.uint(4); state.maxplayers = reader.uint(4); state.raw.allowedbandwidth = reader.uint(4); - for(var i = 0; i < state.raw.numplayers; i++) { + for(let i = 0; i < state.raw.numplayers; i++) { state.players.push({}); } - self.finish(state); + this.finish(state); return true; }); } -}); +} + +module.exports = MumblePing; diff --git a/protocols/mutantfactions.js b/protocols/mutantfactions.js index e6bacc4..4c93ef9 100644 --- a/protocols/mutantfactions.js +++ b/protocols/mutantfactions.js @@ -1,54 +1,53 @@ -var request = require('request'); +const request = require('request'); -module.exports = require('./core').extend({ - run: function(state) { - var self = this; +class MutantFactions extends require('./core') { + run(state) { request({ uri: 'http://mutantfactions.net/game/receiveLobby.php', timeout: 3000, - }, function(e,r,body) { - if(e) return self.fatal('Lobby request error'); + }, (e,r,body) => { + if(e) return this.fatal('Lobby request error'); - var split = body.split('
'); - - var found = false; - for(var i = 0; i < split.length; i++) { - var line = split[i]; - var fields = line.split('::'); - var ip = fields[2]; - var port = fields[3]; - if(ip == self.options.address && port == self.options.port) { + const split = body.split('
'); + let found = false; + for(const line of split) { + const fields = line.split('::'); + const ip = fields[2]; + const port = fields[3]; + if(ip === this.options.address && port === this.options.port) { found = fields; break; } } - if(!found) return self.fatal('Server not found in list'); + if(!found) return this.fatal('Server not found in list'); - state.raw.countrycode = fields[0]; - state.raw.country = fields[1]; - state.name = fields[4]; - state.map = fields[5]; - state.raw.numplayers = fields[6]; - state.maxplayers = fields[7]; + state.raw.countrycode = found[0]; + state.raw.country = found[1]; + state.name = found[4]; + state.map = found[5]; + state.raw.numplayers = found[6]; + state.maxplayers = found[7]; // fields[8] is unknown? - state.raw.rules = fields[9]; - state.raw.gamemode = fields[10]; - state.raw.gangsters = fields[11]; - state.raw.cashrate = fields[12]; - state.raw.missions = fields[13]; - state.raw.vehicles = fields[14]; - state.raw.customweapons = fields[15]; - state.raw.friendlyfire = fields[16]; - state.raw.mercs = fields[17]; + state.raw.rules = found[9]; + state.raw.gamemode = found[10]; + state.raw.gangsters = found[11]; + state.raw.cashrate = found[12]; + state.raw.missions = found[13]; + state.raw.vehicles = found[14]; + state.raw.customweapons = found[15]; + state.raw.friendlyfire = found[16]; + state.raw.mercs = found[17]; // fields[18] is unknown? listen server? - state.raw.version = fields[19]; + state.raw.version = found[19]; - for(var i = 0; i < state.raw.numplayers; i++) { + for(let i = 0; i < state.raw.numplayers; i++) { state.players.push({}); } - self.finish(state); + this.finish(state); }); } -}); +} + +module.exports = MutantFactions; diff --git a/protocols/nadeo.js b/protocols/nadeo.js index d3c6ff0..7f24890 100644 --- a/protocols/nadeo.js +++ b/protocols/nadeo.js @@ -1,24 +1,24 @@ -var gbxremote = require('gbxremote'), +const gbxremote = require('gbxremote'), async = require('async'); -module.exports = require('./core').extend({ - init: function() { - this._super(); +class Nadeo extends require('./core') { + constructor() { + super(); this.options.port = 2350; this.options.port_query = 5000; this.gbxclient = false; - }, - reset: function() { - this._super(); + } + + reset() { + super.reset(); if(this.gbxclient) { this.gbxclient.terminate(); this.gbxclient = false; } - }, - run: function(state) { - var self = this; + } - var cmds = [ + run(state) { + const cmds = [ ['Connect'], ['Authenticate', this.options.login,this.options.password], ['GetStatus'], @@ -27,49 +27,52 @@ module.exports = require('./core').extend({ ['GetCurrentChallengeInfo'], ['GetCurrentGameInfo'] ]; - var results = []; + const results = []; - async.eachSeries(cmds, function(cmdset,c) { - var cmd = cmdset[0]; - var params = cmdset.slice(1); + async.eachSeries(cmds, (cmdset,c) => { + const cmd = cmdset[0]; + const params = cmdset.slice(1); - if(cmd == 'Connect') { - var client = self.gbxclient = gbxremote.createClient(self.options.port_query,self.options.host, function(err) { - if(err) return self.fatal('GBX error '+JSON.stringify(err)); + if(cmd === 'Connect') { + const client = this.gbxclient = gbxremote.createClient(this.options.port_query,this.options.host, (err) => { + if(err) return this.fatal('GBX error '+JSON.stringify(err)); c(); }); - client.on('error',function(){}); + client.on('error',() => {}); } else { - self.gbxclient.methodCall(cmd, params, function(err, value) { - if(err) return self.fatal('XMLRPC error '+JSON.stringify(err)); + this.gbxclient.methodCall(cmd, params, (err, value) => { + if(err) return this.fatal('XMLRPC error '+JSON.stringify(err)); results.push(value); c(); }); } - }, function() { - var gamemode = ''; - var igm = results[5].GameMode; - if(igm == 0) gamemode="Rounds"; - if(igm == 1) gamemode="Time Attack"; - if(igm == 2) gamemode="Team"; - if(igm == 3) gamemode="Laps"; - if(igm == 4) gamemode="Stunts"; - if(igm == 5) gamemode="Cup"; + }, () => { + let gamemode = ''; + const igm = results[5].GameMode; + if(igm === 0) gamemode="Rounds"; + if(igm === 1) gamemode="Time Attack"; + if(igm === 2) gamemode="Team"; + if(igm === 3) gamemode="Laps"; + if(igm === 4) gamemode="Stunts"; + if(igm === 5) gamemode="Cup"; - state.name = self.stripColors(results[3].Name); - state.password = (results[3].Password != 'No password'); + state.name = this.stripColors(results[3].Name); + state.password = (results[3].Password !== 'No password'); state.maxplayers = results[3].CurrentMaxPlayers; - state.map = self.stripColors(results[4].Name); + state.map = this.stripColors(results[4].Name); state.raw.gametype = gamemode; - results[2].forEach(function(player) { - state.players.push({name:self.stripColors(player.Name)}); - }); + for (const player of results[2]) { + state.players.push({name:this.stripColors(player.Name)}); + } - self.finish(state); + this.finish(state); }); - }, - stripColors: function(str) { + } + + stripColors(str) { return str.replace(/\$([0-9a-f][^\$]?[^\$]?|[^\$]?)/g,''); } -}); +} + +module.exports = Nadeo; diff --git a/protocols/openttd.js b/protocols/openttd.js index 616c132..efcabea 100644 --- a/protocols/openttd.js +++ b/protocols/openttd.js @@ -1,28 +1,24 @@ -var async = require('async'), +const async = require('async'), moment = require('moment'); -module.exports = require('./core').extend({ - run: function(state) { - - var self = this; - +class OpenTtd extends require('./core') { + run(state) { async.series([ - function(c) { - var b = new Buffer([0x03,0x00,0x00]); - self.query(0,1,1,4,function(reader, version) { + (c) => { + this.query(0,1,1,4,(reader, version) => { if(version >= 4) { - var numGrf = reader.uint(1); + const numGrf = reader.uint(1); state.raw.grfs = []; - for(var i = 0; i < numGrf; i++) { - var grf = {}; + for(let i = 0; i < numGrf; i++) { + const grf = {}; grf.id = reader.part(4).toString('hex'); grf.md5 = reader.part(16).toString('hex'); state.raw.grfs.push(grf); } } if(version >= 3) { - state.raw.date_current = self.readDate(reader); - state.raw.date_start = self.readDate(reader); + state.raw.date_current = this.readDate(reader); + state.raw.date_start = this.readDate(reader); } if(version >= 2) { state.raw.maxcompanies = reader.uint(1); @@ -33,7 +29,7 @@ module.exports = require('./core').extend({ state.name = reader.string(); state.raw.version = reader.string(); - state.raw.language = self.decode( + state.raw.language = this.decode( reader.uint(1), ['any','en','de','fr'] ); @@ -41,7 +37,7 @@ module.exports = require('./core').extend({ state.password = !!reader.uint(1); state.maxplayers = reader.uint(1); state.raw.numplayers = reader.uint(1); - for(var i = 0; i < state.raw.numplayers; i++) { + for(let i = 0; i < state.raw.numplayers; i++) { state.players.push({}); } state.raw.numspectators = reader.uint(1); @@ -49,7 +45,7 @@ module.exports = require('./core').extend({ state.raw.map_width = reader.uint(2); state.raw.map_height = reader.uint(2); - state.raw.landscape = self.decode( + state.raw.landscape = this.decode( reader.uint(1), ['temperate','arctic','desert','toyland'] ); @@ -60,18 +56,18 @@ module.exports = require('./core').extend({ }); }, - function(c) { - var vehicle_types = ['train','truck','bus','aircraft','ship']; - var station_types = ['station','truckbay','busstation','airport','dock']; + (c) => { + const vehicle_types = ['train','truck','bus','aircraft','ship']; + const station_types = ['station','truckbay','busstation','airport','dock']; - self.query(2,3,-1,-1, function(reader,version) { + this.query(2,3,-1,-1, (reader,version) => { // we don't know how to deal with companies outside version 6 - if(version != 6) return c(); + if(version !== 6) return c(); state.raw.companies = []; - var numCompanies = reader.uint(1); - for(var iCompany = 0; iCompany < numCompanies; iCompany++) { - var company = {}; + const numCompanies = reader.uint(1); + for(let iCompany = 0; iCompany < numCompanies; iCompany++) { + const company = {}; company.id = reader.uint(1); company.name = reader.string(); company.year_start = reader.uint(4); @@ -82,12 +78,12 @@ module.exports = require('./core').extend({ company.password = !!reader.uint(1); company.vehicles = {}; - for(var i = 0; i < vehicle_types.length; i++) { - company.vehicles[vehicle_types[i]] = reader.uint(2); + for(const type of vehicle_types) { + company.vehicles[type] = reader.uint(2); } company.stations = {}; - for(var i = 0; i < station_types.length; i++) { - company.stations[station_types[i]] = reader.uint(2); + for(const type of station_types) { + company.stations[type] = reader.uint(2); } company.clients = reader.string(); @@ -98,53 +94,54 @@ module.exports = require('./core').extend({ }); }, - function(c) { - self.finish(state); + (c) => { + this.finish(state); } ]); - }, + } - query: function(type,expected,minver,maxver,done) { - var self = this; - var b = new Buffer([0x03,0x00,type]); - self.udpSend(b,function(buffer) { - var reader = self.reader(buffer); + query(type,expected,minver,maxver,done) { + const b = Buffer.from([0x03,0x00,type]); + this.udpSend(b,(buffer) => { + const reader = this.reader(buffer); - var packetLen = reader.uint(2); - if(packetLen != buffer.length) { - self.fatal('Invalid reported packet length: '+packetLen+' '+buffer.length); + const packetLen = reader.uint(2); + if(packetLen !== buffer.length) { + this.fatal('Invalid reported packet length: '+packetLen+' '+buffer.length); return true; } - var packetType = reader.uint(1); - if(packetType != expected) { - self.fatal('Unexpected response packet type: '+packetType); + const packetType = reader.uint(1); + if(packetType !== expected) { + this.fatal('Unexpected response packet type: '+packetType); return true; } - var protocolVersion = reader.uint(1); - if((minver != -1 && protocolVersion < minver) || (maxver != -1 && protocolVersion > maxver)) { - self.fatal('Unknown protocol version: '+protocolVersion+' Expected: '+minver+'-'+maxver); + const protocolVersion = reader.uint(1); + if((minver !== -1 && protocolVersion < minver) || (maxver !== -1 && protocolVersion > maxver)) { + this.fatal('Unknown protocol version: '+protocolVersion+' Expected: '+minver+'-'+maxver); return true; } done(reader,protocolVersion); return true; }); - }, + } - readDate: function(reader) { - var daysSinceZero = reader.uint(4); - var temp = new Date(0,0,1); + readDate(reader) { + const daysSinceZero = reader.uint(4); + const temp = new Date(0,0,1); temp.setFullYear(0); temp.setDate(daysSinceZero+1); return moment(temp).format('YYYY-MM-DD'); - }, + } - decode: function(num,arr) { + decode(num,arr) { if(num < 0 || num >= arr.length) { return num; } return arr[num]; } -}); +} + +module.exports = OpenTtd; diff --git a/protocols/quake1.js b/protocols/quake1.js index 4b18c9a..93c23a4 100644 --- a/protocols/quake1.js +++ b/protocols/quake1.js @@ -1,7 +1,9 @@ -module.exports = require('./quake2').extend({ - init: function() { - this._super(); +class Quake1 extends require('./quake2') { + constructor() { + super(); this.responseHeader = 'n'; this.isQuake1 = true; } -}); +} + +module.exports = Quake1; diff --git a/protocols/quake2.js b/protocols/quake2.js index 5f1f584..e170a8f 100644 --- a/protocols/quake2.js +++ b/protocols/quake2.js @@ -1,60 +1,59 @@ -module.exports = require('./core').extend({ - init: function() { - this._super(); +class Quake2 extends require('./core') { + constructor() { + super(); this.encoding = 'latin1'; this.delimiter = '\n'; this.sendHeader = 'status'; this.responseHeader = 'print'; this.isQuake1 = false; - }, - run: function(state) { - var self = this; + } - this.udpSend('\xff\xff\xff\xff'+this.sendHeader+'\x00',function(buffer) { - var reader = self.reader(buffer); + run(state) { + this.udpSend('\xff\xff\xff\xff'+this.sendHeader+'\x00', (buffer) => { + const reader = this.reader(buffer); - var header = reader.string({length:4}); - if(header != '\xff\xff\xff\xff') return; + const header = reader.string({length:4}); + if(header !== '\xff\xff\xff\xff') return; - var response; + this.debugBuffer(buffer); + let response; if(this.isQuake1) { - response = reader.string({length:1}); + response = reader.string({length:this.responseHeader.length}); } else { response = reader.string(); } - if(response != this.responseHeader) return; + if(response !== this.responseHeader) return; - var info = reader.string().split('\\'); - if(info[0] == '') info.shift(); + const info = reader.string().split('\\'); + if(info[0] === '') info.shift(); while(true) { - var key = info.shift(); - var value = info.shift(); - if(typeof value == 'undefined') break; + const key = info.shift(); + const value = info.shift(); + if(typeof value === 'undefined') break; state.raw[key] = value; } while(!reader.done()) { - var line = reader.string(); - if(!line || line.charAt(0) == '\0') break; + const line = reader.string(); + if(!line || line.charAt(0) === '\0') break; - var args = []; - var split = line.split('"'); - var inQuote = false; - split.forEach(function(part,i) { - var inQuote = (i%2 == 1); + const args = []; + const split = line.split('"'); + split.forEach((part,i) => { + const inQuote = (i%2 === 1); if(inQuote) { args.push(part); } else { - var splitSpace = part.split(' '); - splitSpace.forEach(function(subpart) { + const splitSpace = part.split(' '); + for (const subpart of splitSpace) { if(subpart) args.push(subpart); - }); + } } }); - var player = {}; - if(self.isQuake1) { + const player = {}; + if(this.isQuake1) { player.id = parseInt(args.shift()); player.score = parseInt(args.shift()); player.time = parseInt(args.shift()); @@ -80,8 +79,10 @@ module.exports = require('./core').extend({ if('sv_hostname' in state.raw) state.name = state.raw.sv_hostname; if('hostname' in state.raw) state.name = state.raw.hostname; - self.finish(state); + this.finish(state); return true; }); } -}); +} + +module.exports = Quake2; diff --git a/protocols/quake3.js b/protocols/quake3.js index 7fc7990..f07b7cc 100644 --- a/protocols/quake3.js +++ b/protocols/quake3.js @@ -1,19 +1,21 @@ -module.exports = require('./quake2').extend({ - init: function() { - this._super(); +class Quake3 extends require('./quake2') { + constructor() { + super(); this.sendHeader = 'getstatus'; this.responseHeader = 'statusResponse'; - }, - finalizeState: function(state) { + } + finalizeState(state) { state.name = this.stripColors(state.name); - for(var i in state.raw) { - state.raw[i] = this.stripColors(state.raw[i]); + for(const key of Object.keys(state.raw)) { + state.raw[key] = this.stripColors(state.raw[key]); } - for(var i = 0; i < state.players.length; i++) { - state.players[i].name = this.stripColors(state.players[i].name); + for(const player of state.players) { + player.name = this.stripColors(player.name); } - }, - stripColors: function(str) { + } + stripColors(str) { return str.replace(/\^(X.{6}|.)/g,''); } -}); +} + +module.exports = Quake3; diff --git a/protocols/samp.js b/protocols/samp.js index 2614d10..eebfc03 100644 --- a/protocols/samp.js +++ b/protocols/samp.js @@ -1,30 +1,26 @@ -var async = require('async'); - -module.exports = require('./core').extend({ - run: function(state) { - - var self = this; - var len; +const async = require('async'); +class Samp extends require('./core') { + run(state) { async.series([ - function(c) { - self.sendPacket('i',function(reader) { + (c) => { + this.sendPacket('i',(reader) => { state.password = !!reader.uint(1); state.raw.numplayers = reader.uint(2); state.maxplayers = reader.uint(2); - state.name = self.readString(reader,4); - state.raw.gamemode = self.readString(reader,4); - self.map = self.readString(reader,4); + state.name = this.readString(reader,4); + state.raw.gamemode = this.readString(reader,4); + this.map = this.readString(reader,4); c(); }); }, - function(c) { - self.sendPacket('r',function(reader) { - var ruleCount = reader.uint(2); + (c) => { + this.sendPacket('r',(reader) => { + const ruleCount = reader.uint(2); state.raw.rules = {}; - for(var i = 0; i < ruleCount; i++) { - var key = self.readString(reader,1); - var value = self.readString(reader,1); + for(let i = 0; i < ruleCount; i++) { + const key = this.readString(reader,1); + const value = this.readString(reader,1); state.raw.rules[key] = value; } if('mapname' in state.raw.rules) @@ -32,60 +28,61 @@ module.exports = require('./core').extend({ c(); }); }, - function(c) { - self.sendPacket('d',function(reader) { - var playerCount = reader.uint(2); - for(var i = 0; i < playerCount; i++) { - var player = {}; + (c) => { + this.sendPacket('d',(reader) => { + const playerCount = reader.uint(2); + for(let i = 0; i < playerCount; i++) { + const player = {}; player.id = reader.uint(1); - player.name = self.readString(reader,1); + player.name = this.readString(reader,1); player.score = reader.int(4); player.ping = reader.uint(4); state.players.push(player); } c(); - },function() { - for(var i = 0; i < state.raw.numplayers; i++) { + },() => { + for(let i = 0; i < state.raw.numplayers; i++) { state.players.push({}); } c(); }); }, - function(c) { - self.finish(state); + (c) => { + this.finish(state); } ]); - }, - readString: function(reader,lenBytes) { - var length = reader.uint(lenBytes); + } + readString(reader,lenBytes) { + const length = reader.uint(lenBytes); if(!length) return ''; - var string = reader.string({length:length}); + const string = reader.string({length:length}); return string; - }, - sendPacket: function(type,onresponse,ontimeout) { - var self = this; - var outbuffer = new Buffer(11); + } + sendPacket(type,onresponse,ontimeout) { + const outbuffer = Buffer.alloc(11); outbuffer.writeUInt32BE(0x53414D50,0); - var ipSplit = self.options.address.split('.'); + const ipSplit = this.options.address.split('.'); outbuffer.writeUInt8(parseInt(ipSplit[0]),4); outbuffer.writeUInt8(parseInt(ipSplit[1]),5); outbuffer.writeUInt8(parseInt(ipSplit[2]),6); outbuffer.writeUInt8(parseInt(ipSplit[3]),7); - outbuffer.writeUInt16LE(self.options.port,8); + outbuffer.writeUInt16LE(this.options.port,8); outbuffer.writeUInt8(type.charCodeAt(0),10); - this.udpSend(outbuffer,function(buffer) { - var reader = self.reader(buffer); - for(var i = 0; i < outbuffer.length; i++) { + this.udpSend(outbuffer,(buffer) => { + const reader = this.reader(buffer); + for(let i = 0; i < outbuffer.length; i++) { if(outbuffer.readUInt8(i) !== reader.uint(1)) return; } onresponse(reader); return true; - },function() { + },() => { if(ontimeout) { ontimeout(); return true; } }); } -}); +} + +module.exports = Samp; diff --git a/protocols/starmade.js b/protocols/starmade.js index 5d7aa8c..6528f73 100644 --- a/protocols/starmade.js +++ b/protocols/starmade.js @@ -1,62 +1,62 @@ -module.exports = require('./core').extend({ - init: function() { - this._super(); +class Starmade extends require('./core') { + constructor() { + super(); this.encoding = 'latin1'; this.byteorder = 'be'; - }, - run: function(state) { - var self = this; + } + run(state) { + const b = Buffer.from([0x00,0x00,0x00,0x09,0x2a,0xff,0xff,0x01,0x6f,0x00,0x00,0x00,0x00]); - var b = new Buffer([0x00,0x00,0x00,0x09,0x2a,0xff,0xff,0x01,0x6f,0x00,0x00,0x00,0x00]); - - this.tcpSend(b,function(buffer) { - var reader = self.reader(buffer); + this.tcpSend(b,(buffer) => { + const reader = this.reader(buffer); if(buffer.length < 4) return false; - var packetLength = reader.uint(4); + const packetLength = reader.uint(4); if(buffer.length < packetLength+12) return false; - var data = []; + const data = []; state.raw.data = data; reader.skip(2); while(!reader.done()) { - var mark = reader.uint(1); - if(mark == 1) { + const mark = reader.uint(1); + if(mark === 1) { // signed int data.push(reader.int(4)); - } else if(mark == 3) { + } else if(mark === 3) { // float data.push(reader.float()); - } else if(mark == 4) { + } else if(mark === 4) { // string - var length = reader.uint(2); + const length = reader.uint(2); data.push(reader.string(length)); - } else if(mark == 6) { + } else if(mark === 6) { // byte data.push(reader.uint(1)); } } if(data.length < 9) { - self.fatal("Not enough units in data packet"); + this.fatal("Not enough units in data packet"); return true; } - if(typeof data[3] == 'number') state.raw.version = data[3].toFixed(7).replace(/0+$/, ''); - if(typeof data[4] == 'string') state.name = data[4]; - if(typeof data[5] == 'string') state.raw.description = data[5]; - if(typeof data[7] == 'number') state.raw.numplayers = data[7]; - if(typeof data[8] == 'number') state.maxplayers = data[8]; + if(typeof data[3] === 'number') state.raw.version = data[3].toFixed(7).replace(/0+$/, ''); + if(typeof data[4] === 'string') state.name = data[4]; + if(typeof data[5] === 'string') state.raw.description = data[5]; + if(typeof data[7] === 'number') state.raw.numplayers = data[7]; + if(typeof data[8] === 'number') state.maxplayers = data[8]; if('numplayers' in state.raw) { - for(var i = 0; i < state.raw.numplayers; i++) { + for(let i = 0; i < state.raw.numplayers; i++) { state.players.push({}); } } - self.finish(state); + this.finish(state); return true; }); } -}); +} + +module.exports = Starmade; diff --git a/protocols/teamspeak2.js b/protocols/teamspeak2.js index a61e687..5bff195 100644 --- a/protocols/teamspeak2.js +++ b/protocols/teamspeak2.js @@ -1,77 +1,78 @@ -var async = require('async'); - -module.exports = require('./core').extend({ - run: function(state) { - var self = this; +const async = require('async'); +class Teamspeak2 extends require('./core') { + run(state) { async.series([ - function(c) { - self.sendCommand('sel '+self.options.port, function(data) { - if(data != '[TS]') self.fatal('Invalid header'); + (c) => { + this.sendCommand('sel '+this.options.port, (data) => { + if(data !== '[TS]') this.fatal('Invalid header'); c(); }); }, - function(c) { - self.sendCommand('si', function(data) { - var split = data.split('\r\n'); - split.forEach(function(line) { - var equals = line.indexOf('='); - var key = equals == -1 ? line : line.substr(0,equals); - var value = equals == -1 ? '' : line.substr(equals+1); + (c) => { + this.sendCommand('si', (data) => { + for (const line of data.split('\r\n')) { + const equals = line.indexOf('='); + const key = equals === -1 ? line : line.substr(0,equals); + const value = equals === -1 ? '' : line.substr(equals+1); state.raw[key] = value; - }); + } c(); }); }, - function(c) { - self.sendCommand('pl', function(data) { - var split = data.split('\r\n'); - var fields = split.shift().split('\t'); - split.forEach(function(line) { - var split2 = line.split('\t'); - var player = {}; - split2.forEach(function(value,i) { - var key = fields[i]; + (c) => { + this.sendCommand('pl', (data) => { + const split = data.split('\r\n'); + const fields = split.shift().split('\t'); + for (const line of split) { + const split2 = line.split('\t'); + const player = {}; + split2.forEach((value,i) => { + let key = fields[i]; if(!key) return; - if(key == 'nick') key = 'name'; - if(m = value.match(/^"(.*)"$/)) value = m[1]; + if(key === 'nick') key = 'name'; + const m = value.match(/^"(.*)"$/); + if(m) value = m[1]; player[key] = value; }); state.players.push(player); - }); + } c(); }); }, - function(c) { - self.sendCommand('cl', function(data) { - var split = data.split('\r\n'); - var fields = split.shift().split('\t'); + (c) => { + this.sendCommand('cl', (data) => { + const split = data.split('\r\n'); + const fields = split.shift().split('\t'); state.raw.channels = []; - split.forEach(function(line) { - var split2 = line.split('\t'); - var channel = {}; - split2.forEach(function(value,i) { - var key = fields[i]; + for (const line of split) { + const split2 = line.split('\t'); + const channel = {}; + split2.forEach((value,i) => { + const key = fields[i]; if(!key) return; - if(m = value.match(/^"(.*)"$/)) value = m[1]; + const m = value.match(/^"(.*)"$/); + if(m) value = m[1]; channel[key] = value; }); state.raw.channels.push(channel); - }); + } c(); }); }, - function(c) { - self.finish(state); + (c) => { + this.finish(state); } ]); - }, - sendCommand: function(cmd,c) { - this.tcpSend(cmd+'\x0A', function(buffer) { + } + sendCommand(cmd,c) { + this.tcpSend(cmd+'\x0A', (buffer) => { if(buffer.length < 6) return; - if(buffer.slice(-6).toString() != '\r\nOK\r\n') return; + if(buffer.slice(-6).toString() !== '\r\nOK\r\n') return; c(buffer.slice(0,-6).toString()); return true; }); } -}); +} + +module.exports = Teamspeak2; diff --git a/protocols/teamspeak3.js b/protocols/teamspeak3.js index f576927..432ae96 100644 --- a/protocols/teamspeak3.js +++ b/protocols/teamspeak3.js @@ -1,73 +1,71 @@ -var async = require('async'); - -module.exports = require('./core').extend({ - run: function(state) { - var self = this; +const async = require('async'); +class Teamspeak3 extends require('./core') { + run(state) { async.series([ - function(c) { - self.sendCommand('use port='+self.options.port, function(data) { - var split = data.split('\n\r'); - if(split[0] != 'TS3') self.fatal('Invalid header'); + (c) => { + this.sendCommand('use port='+this.options.port, (data) => { + const split = data.split('\n\r'); + if(split[0] !== 'TS3') this.fatal('Invalid header'); c(); }, true); }, - function(c) { - self.sendCommand('serverinfo', function(data) { + (c) => { + this.sendCommand('serverinfo', (data) => { state.raw = data[0]; if('virtualserver_name' in state.raw) state.name = state.raw.virtualserver_name; if('virtualserver_maxclients' in state.raw) state.maxplayers = state.raw.virtualserver_maxclients; c(); }); }, - function(c) { - self.sendCommand('clientlist', function(data) { - for(var i = 0; i < data.length; i++) { - data[i].name = data[i].client_nickname; - delete data[i].client_nickname; - if(data[i].client_type == 0) { - state.players.push(data[i]); + (c) => { + this.sendCommand('clientlist', (list) => { + for (const client of list) { + client.name = client.client_nickname; + delete client.client_nickname; + if(client.client_type === '0') { + state.players.push(client); } } c(); }); }, - function(c) { - self.sendCommand('channellist -topic', function(data) { + (c) => { + this.sendCommand('channellist -topic', (data) => { state.raw.channels = data; c(); }); }, - function(c) { - self.finish(state); + (c) => { + this.finish(state); } ]); - }, - sendCommand: function(cmd,c,raw) { - this.tcpSend(cmd+'\x0A', function(buffer) { + } + sendCommand(cmd,c,raw) { + this.tcpSend(cmd+'\x0A', (buffer) => { if(buffer.length < 21) return; - if(buffer.slice(-21).toString() != '\n\rerror id=0 msg=ok\n\r') return; - var body = buffer.slice(0,-21).toString(); + if(buffer.slice(-21).toString() !== '\n\rerror id=0 msg=ok\n\r') return; + const body = buffer.slice(0,-21).toString(); - var out; + let out; if(raw) { out = body; } else { - var segments = body.split('|'); + const segments = body.split('|'); out = []; - segments.forEach(function(line) { - var split = line.split(' '); - var unit = {}; - split.forEach(function(field) { - var equals = field.indexOf('='); - var key = equals == -1 ? field : field.substr(0,equals); - var value = equals == -1 ? '' : field.substr(equals+1) + for (const line of segments) { + const split = line.split(' '); + const unit = {}; + for (const field of split) { + const equals = field.indexOf('='); + const key = equals === -1 ? field : field.substr(0,equals); + const value = equals === -1 ? '' : field.substr(equals+1) .replace(/\\s/g,' ').replace(/\\\//g,'/'); unit[key] = value; - }); + } out.push(unit); - }); + } } c(out); @@ -75,4 +73,6 @@ module.exports = require('./core').extend({ return true; }); } -}); +} + +module.exports = Teamspeak3; diff --git a/protocols/terraria.js b/protocols/terraria.js index e35bd80..660ecd0 100644 --- a/protocols/terraria.js +++ b/protocols/terraria.js @@ -1,8 +1,7 @@ -var request = require('request'); +const request = require('request'); -module.exports = require('./core').extend({ - run: function(state) { - var self = this; +class Terraria extends require('./core') { + run(state) { request({ uri: 'http://'+this.options.address+':'+this.options.port_query+'/v2/server/status', timeout: 3000, @@ -10,26 +9,28 @@ module.exports = require('./core').extend({ players: 'true', token: this.options.token } - }, function(e,r,body) { - if(e) return self.fatal('HTTP error'); - var json; + }, (e,r,body) => { + if(e) return this.fatal('HTTP error'); + let json; try { json = JSON.parse(body); } catch(e) { - return self.fatal('Invalid JSON'); + return this.fatal('Invalid JSON'); } - if(json.status != 200) return self.fatal('Invalid status'); + if(json.status !== 200) return this.fatal('Invalid status'); - json.players.forEach(function(one) { + for (const one of json.players) { state.players.push({name:one.nickname,team:one.team}); - }); + } state.name = json.name; state.raw.port = json.port; state.raw.numplayers = json.playercount; - self.finish(state); + this.finish(state); }); } -}); +} + +module.exports = Terraria; diff --git a/protocols/unreal2.js b/protocols/unreal2.js index b4ea301..001d778 100644 --- a/protocols/unreal2.js +++ b/protocols/unreal2.js @@ -1,78 +1,75 @@ -var async = require('async'); +const async = require('async'); -module.exports = require('./core').extend({ - init: function() { - this._super(); +class Unreal2 extends require('./core') { + constructor() { + super(); this.encoding = 'latin1'; - }, - run: function(state) { - - var self = this; - + } + run(state) { async.series([ - function(c) { - self.sendPacket(0,true,function(b) { - var reader = self.reader(b); + (c) => { + this.sendPacket(0,true,(b) => { + const reader = this.reader(b); state.raw.serverid = reader.uint(4); - state.raw.ip = self.readUnrealString(reader); + state.raw.ip = this.readUnrealString(reader); state.raw.port = reader.uint(4); state.raw.queryport = reader.uint(4); - state.name = self.readUnrealString(reader,true); - state.map = self.readUnrealString(reader,true); - state.raw.gametype = self.readUnrealString(reader,true); + state.name = this.readUnrealString(reader,true); + state.map = this.readUnrealString(reader,true); + state.raw.gametype = this.readUnrealString(reader,true); state.raw.numplayers = reader.uint(4); state.maxplayers = reader.uint(4); - self.readExtraInfo(reader,state); + this.readExtraInfo(reader,state); c(); }); }, - function(c) { - self.sendPacket(1,true,function(b) { - var reader = self.reader(b); + (c) => { + this.sendPacket(1,true,(b) => { + const reader = this.reader(b); state.raw.mutators = []; state.raw.rules = {}; while(!reader.done()) { - var key = self.readUnrealString(reader,true); - var value = self.readUnrealString(reader,true); - if(key == 'Mutator') state.raw.mutators.push(value); + const key = this.readUnrealString(reader,true); + const value = this.readUnrealString(reader,true); + if(key === 'Mutator') state.raw.mutators.push(value); else state.raw.rules[key] = value; } if('GamePassword' in state.raw.rules) - state.password = state.raw.rules.GamePassword != 'True'; + state.password = state.raw.rules.GamePassword !== 'True'; c(); }); }, - function(c) { - self.sendPacket(2,false,function(b) { - var reader = self.reader(b); + (c) => { + this.sendPacket(2,false,(b) => { + const reader = this.reader(b); while(!reader.done()) { - var player = {}; + const player = {}; player.id = reader.uint(4); if(!player.id) break; - if(player.id == 0) { + if(player.id === 0) { // Unreal2XMP Player (ID is always 0) reader.skip(4); } - player.name = self.readUnrealString(reader,true); + player.name = this.readUnrealString(reader,true); player.ping = reader.uint(4); player.score = reader.int(4); reader.skip(4); // stats ID // Extra data for Unreal2XMP players - if(player.id == 0) { - var count = reader.uint(1); - for(var iField = 0; iField < count; iField++) { - var key = self.readUnrealString(reader,true); - var value = self.readUnrealString(reader,true); + if(player.id === 0) { + const count = reader.uint(1); + for(let iField = 0; iField < count; iField++) { + const key = this.readUnrealString(reader,true); + const value = this.readUnrealString(reader,true); player[key] = value; } } - if(player.id == 0 && player.name == 'Player') { + if(player.id === 0 && player.name === 'Player') { // these show up in ut2004 queries, but aren't real // not even really sure why they're there continue; @@ -83,12 +80,12 @@ module.exports = require('./core').extend({ c(); }); }, - function(c) { - self.finish(state); + (c) => { + this.finish(state); } ]); - }, - readExtraInfo: function(reader,state) { + } + readExtraInfo(reader,state) { if(this.debug) { console.log("UNREAL2 EXTRA INFO:"); console.log(reader.uint(4)); @@ -97,10 +94,10 @@ module.exports = require('./core').extend({ console.log(reader.uint(4)); console.log(reader.buffer.slice(reader.i)); } - }, - readUnrealString: function(reader, stripColor) { - var length = reader.uint(1); - var out; + } + readUnrealString(reader, stripColor) { + let length = reader.uint(1); + let out; if(length < 0x80) { //out = reader.string({length:length}); out = ''; @@ -114,29 +111,30 @@ module.exports = require('./core').extend({ out = reader.string({encoding:'ucs2',length:length}); } - if(out.charCodeAt(out.length-1) == 0) + if(out.charCodeAt(out.length-1) === 0) out = out.substring(0,out.length-1); if(stripColor) out = out.replace(/\x1b...|[\x00-\x1a]/g,''); return out; - }, - sendPacket: function(type,required,callback) { - var self = this; - var outbuffer = new Buffer([0x79,0,0,0,type]); + } + sendPacket(type,required,callback) { + const outbuffer = Buffer.from([0x79,0,0,0,type]); - var packets = []; - this.udpSend(outbuffer,function(buffer) { - var reader = self.reader(buffer); - var header = reader.uint(4); - var iType = reader.uint(1); - if(iType != type) return; + const packets = []; + this.udpSend(outbuffer,(buffer) => { + const reader = this.reader(buffer); + const header = reader.uint(4); + const iType = reader.uint(1); + if(iType !== type) return; packets.push(reader.rest()); - },function() { + }, () => { if(!packets.length && required) return; callback(Buffer.concat(packets)); return true; }); } -}); +} + +module.exports = Unreal2; diff --git a/protocols/ut2004.js b/protocols/ut2004.js index e1e32e2..b4544e6 100644 --- a/protocols/ut2004.js +++ b/protocols/ut2004.js @@ -1,7 +1,9 @@ -module.exports = require('./unreal2').extend({ - readExtraInfo: function(reader,state) { +class Ut2004 extends require('./unreal2') { + readExtraInfo(reader,state) { state.raw.ping = reader.uint(4); state.raw.flags = reader.uint(4); state.raw.skill = reader.uint(2); } -}); +} + +module.exports = Ut2004; diff --git a/protocols/ut3.js b/protocols/ut3.js index 9a605d0..61ce938 100644 --- a/protocols/ut3.js +++ b/protocols/ut3.js @@ -1,6 +1,6 @@ -module.exports = require('./gamespy3').extend({ - finalizeState: function(state) { - this._super(state); +class Ut3 extends require('./gamespy3') { + finalizeState(state) { + super.finalizeState(state); this.translate(state.raw,{ 'mapname': false, @@ -31,13 +31,15 @@ module.exports = require('./gamespy3').extend({ 'p268435969': false }); - function split(a) { - var s = a.split('\x1c'); - s = s.filter(function(e) { return e }); + const split = (a) => { + let s = a.split('\x1c'); + s = s.filter((e) => { return e }); return s; - } + }; if('custom_mutators' in state.raw) state.raw['custom_mutators'] = split(state.raw['custom_mutators']); if('stock_mutators' in state.raw) state.raw['stock_mutators'] = split(state.raw['stock_mutators']); if('map' in state.raw) state.map = state.raw.map; } -}); +} + +module.exports = Ut3; diff --git a/protocols/valve.js b/protocols/valve.js index 4fe6f65..349506e 100644 --- a/protocols/valve.js +++ b/protocols/valve.js @@ -1,9 +1,9 @@ -var async = require('async'), +const async = require('async'), Bzip2 = require('compressjs').Bzip2; -module.exports = require('./core').extend({ - init: function() { - this._super(); +class Valve extends require('./core') { + constructor() { + super(); this.options.port = 27015; @@ -29,26 +29,26 @@ module.exports = require('./core').extend({ this._skipSizeInSplitHeader = false; this._challenge = ''; - }, - run: function(state) { - var self = this; + } + + run(state) { async.series([ - function(c) { self.queryInfo(state,c); }, - function(c) { self.queryChallenge(state,c); }, - function(c) { self.queryPlayers(state,c); }, - function(c) { self.queryRules(state,c); }, - function(c) { self.finish(state); } + (c) => { this.queryInfo(state,c); }, + (c) => { this.queryChallenge(state,c); }, + (c) => { this.queryPlayers(state,c); }, + (c) => { this.queryRules(state,c); }, + (c) => { this.finish(state); } ]); - }, - queryInfo: function(state,c) { - var self = this; - self.sendPacket( + } + + queryInfo(state,c) { + this.sendPacket( 0x54,false,'Source Engine Query\0', - self.goldsrcInfo ? 0x6D : 0x49, - function(b) { - var reader = self.reader(b); + this.goldsrcInfo ? 0x6D : 0x49, + (b) => { + const reader = this.reader(b); - if(self.goldsrcInfo) state.raw.address = reader.string(); + if(this.goldsrcInfo) state.raw.address = reader.string(); else state.raw.protocol = reader.uint(1); state.name = reader.string(); @@ -59,18 +59,18 @@ module.exports = require('./core').extend({ state.raw.numplayers = reader.uint(1); state.maxplayers = reader.uint(1); - if(self.goldsrcInfo) state.raw.protocol = reader.uint(1); + if(this.goldsrcInfo) state.raw.protocol = reader.uint(1); else state.raw.numbots = reader.uint(1); state.raw.listentype = reader.uint(1); state.raw.environment = reader.uint(1); - if(!self.goldsrcInfo) { + if(!this.goldsrcInfo) { state.raw.listentype = String.fromCharCode(state.raw.listentype); state.raw.environment = String.fromCharCode(state.raw.environment); } state.password = !!reader.uint(1); - if(self.goldsrcInfo) { + if(this.goldsrcInfo) { state.raw.ismod = reader.uint(1); if(state.raw.ismod) { state.raw.modlink = reader.string(); @@ -84,16 +84,16 @@ module.exports = require('./core').extend({ } state.raw.secure = reader.uint(1); - if(self.goldsrcInfo) { + if(this.goldsrcInfo) { state.raw.numbots = reader.uint(1); } else { - if(state.raw.folder == 'ship') { + if(state.raw.folder === 'ship') { state.raw.shipmode = reader.uint(1); state.raw.shipwitnesses = reader.uint(1); state.raw.shipduration = reader.uint(1); } state.raw.version = reader.string(); - var extraFlag = reader.uint(1); + const extraFlag = reader.uint(1); if(extraFlag & 0x80) state.raw.port = reader.uint(2); if(extraFlag & 0x10) state.raw.steamid = reader.uint(8); if(extraFlag & 0x40) { @@ -106,32 +106,32 @@ module.exports = require('./core').extend({ // from https://developer.valvesoftware.com/wiki/Server_queries if( - state.raw.protocol == 7 && ( - state.raw.steamappid == 215 - || state.raw.steamappid == 17550 - || state.raw.steamappid == 17700 - || state.raw.steamappid == 240 + state.raw.protocol === 7 && ( + state.raw.steamappid === 215 + || state.raw.steamappid === 17550 + || state.raw.steamappid === 17700 + || state.raw.steamappid === 240 ) ) { - self._skipSizeInSplitHeader = true; + this._skipSizeInSplitHeader = true; } - if(self.debug) { + if(this.debug) { console.log("STEAM APPID: "+state.raw.steamappid); console.log("PROTOCOL: "+state.raw.protocol); } - if(state.raw.protocol == 48) { - if(self.debug) console.log("GOLDSRC DETECTED - USING MODIFIED SPLIT FORMAT"); - self.goldsrcSplits = true; + if(state.raw.protocol === 48) { + if(this.debug) console.log("GOLDSRC DETECTED - USING MODIFIED SPLIT FORMAT"); + this.goldsrcSplits = true; } c(); } ); - }, - queryChallenge: function(state,c) { - var self = this; + } + + queryChallenge(state,c) { if(this.legacyChallenge) { - self.sendPacket(0x57,false,false,0x41,function(b) { + this.sendPacket(0x57,false,false,0x41,(b) => { // sendPacket will catch the response packet and // save the challenge for us c(); @@ -139,46 +139,47 @@ module.exports = require('./core').extend({ } else { c(); } - }, - queryPlayers: function(state,c) { - var self = this; - self.sendPacket(0x55,true,false,0x44,function(b) { - var reader = self.reader(b); - var num = reader.uint(1); - var csgoHiddenPlayers = false; - for(var i = 0; i < num; i++) { - reader.skip(1); - var name = reader.string(); - var score = reader.int(4); - var time = reader.float(); + } - if(self.debug) console.log("Found player: "+name+" "+score+" "+time); + queryPlayers(state,c) { + this.sendPacket(0x55,true,false,0x44,(b) => { + const reader = this.reader(b); + const num = reader.uint(1); + for(let i = 0; i < num; i++) { + reader.skip(1); + const name = reader.string(); + const score = reader.int(4); + const time = reader.float(); + + if(this.debug) console.log("Found player: "+name+" "+score+" "+time); // connecting players don't count as players. if(!name) continue; - (time == -1 ? state.bots : state.players).push({ + (time === -1 ? state.bots : state.players).push({ name:name, score:score, time:time }); } - if(self.isCsGo && state.players.length == 1 && state.players[0].name == 'Max Players') { - if(self.debug) console.log("CSGO server using limited player details"); + if(this.isCsGo && state.players.length === 1 && state.players[0].name === 'Max Players') { + if(this.debug) console.log("CSGO server using limited player details"); state.players = []; - for(var i = 0; i < state.raw.numplayers; i++) { state.players.push({}); } + for(let i = 0; i < state.raw.numplayers; i++) { + state.players.push({}); + } } // if we didn't find the bots, iterate // through and guess which ones they are if(!state.bots.length && state.raw.numbots) { - var maxTime = 0; - state.players.forEach(function(player) { + let maxTime = 0; + for (const player of state.players) { maxTime = Math.max(player.time,maxTime); - }); - for(var i = 0; i < state.players.length; i++) { - var player = state.players[i]; + } + for(let i = 0; i < state.players.length; i++) { + const player = state.players[i]; if(state.bots.length >= state.raw.numbots) continue; - if(player.time != maxTime) continue; + if(player.time !== maxTime) continue; state.bots.push(player); state.players.splice(i, 1); i--; @@ -187,139 +188,141 @@ module.exports = require('./core').extend({ c(); }); - }, - queryRules: function(state,c) { - var self = this; - self.sendPacket(0x56,true,false,0x45,function(b) { - var reader = self.reader(b); - var num = reader.uint(2); + } + + queryRules(state,c) { + this.sendPacket(0x56,true,false,0x45,(b) => { + const reader = this.reader(b); + const num = reader.uint(2); state.raw.rules = {}; - for(var i = 0; i < num; i++) { - var key = reader.string(); - var value = reader.string(); + for(let i = 0; i < num; i++) { + const key = reader.string(); + const value = reader.string(); state.raw.rules[key] = value; } c(); - }, function() { + }, () => { // no rules were returned after timeout -- // the server probably has them disabled // ignore the timeout c(); return true; }); - }, - sendPacket: function(type,sendChallenge,payload,expect,callback,ontimeout) { - var self = this; - var packetStorage = {}; - - send(); + } - function send(c) { - if(typeof payload == 'string') payload = new Buffer(payload,'binary'); - var challengeLength = sendChallenge ? 4 : 0; - var payloadLength = payload ? payload.length : 0; + sendPacket(type,sendChallenge,payload,expect,callback,ontimeout) { + const packetStorage = {}; - var b = new Buffer(5 + challengeLength + payloadLength); + const receivedFull = (reader) => { + const type = reader.uint(1); + + if(type === 0x41) { + if(this.debug) console.log('Received challenge key'); + if(this._challenge) return this.fatal('Received more than one challenge key'); + this._challenge = reader.uint(4); + + if(sendChallenge) { + if(this.debug) console.log('Restarting query'); + send(); + return true; + } + } + + if(this.debug) console.log("Received "+type.toString(16)+" expected "+expect.toString(16)); + if(type !== expect) return; + callback(reader.rest()); + return true; + }; + + const receivedOne = (buffer) => { + const reader = this.reader(buffer); + + const header = reader.int(4); + if(header === -1) { + // full package + if(this.debug) console.log("Received full packet"); + return receivedFull(reader); + } + if(header === -2) { + // partial package + const uid = reader.uint(4); + if(!(uid in packetStorage)) packetStorage[uid] = {}; + const packets = packetStorage[uid]; + + let bzip = false; + if(!this.goldsrcSplits && uid & 0x80000000) bzip = true; + + let packetNum,payload,numPackets; + if(this.goldsrcSplits) { + packetNum = reader.uint(1); + numPackets = packetNum & 0x0f; + packetNum = (packetNum & 0xf0) >> 4; + payload = reader.rest(); + } else { + numPackets = reader.uint(1); + packetNum = reader.uint(1); + if(!this._skipSizeInSplitHeader) reader.skip(2); + if(packetNum === 0 && bzip) reader.skip(8); + payload = reader.rest(); + } + + packets[packetNum] = payload; + + if(this.debug) { + console.log("Received partial packet uid:"+uid+" num:"+packetNum); + console.log("Received "+Object.keys(packets).length+'/'+numPackets+" packets for this UID"); + } + + if(Object.keys(packets).length !== numPackets) return; + + // assemble the parts + const list = []; + for(let i = 0; i < numPackets; i++) { + if(!(i in packets)) { + this.fatal('Missing packet #'+i); + return true; + } + list.push(packets[i]); + } + + let assembled = Buffer.concat(list); + if(bzip) { + if(this.debug) console.log("BZIP DETECTED - Extracing packet..."); + try { + assembled = Buffer.from(Bzip2.decompressFile(assembled)); + } catch(e) { + this.fatal('Invalid bzip packet'); + return true; + } + } + const assembledReader = this.reader(assembled); + assembledReader.skip(4); // header + return receivedFull(assembledReader); + } + }; + + const send = (c) => { + if(typeof payload === 'string') payload = Buffer.from(payload,'binary'); + const challengeLength = sendChallenge ? 4 : 0; + const payloadLength = payload ? payload.length : 0; + + const b = Buffer.alloc(5 + challengeLength + payloadLength); b.writeInt32LE(-1, 0); b.writeUInt8(type, 4); if(sendChallenge) { - var challenge = self._challenge; + let challenge = this._challenge; if(!challenge) challenge = 0xffffffff; - if(self.byteorder == 'le') b.writeUInt32LE(challenge, 5); + if(this.byteorder === 'le') b.writeUInt32LE(challenge, 5); else b.writeUInt32BE(challenge, 5); } if(payloadLength) payload.copy(b, 5+challengeLength); - self.udpSend(b,receivedOne,ontimeout); - } + this.udpSend(b,receivedOne,ontimeout); + }; - function receivedOne(buffer) { - var reader = self.reader(buffer); - - var header = reader.int(4); - if(header == -1) { - // full package - if(self.debug) console.log("Received full packet"); - return receivedFull(reader); - } - if(header == -2) { - // partial package - var uid = reader.uint(4); - if(!(uid in packetStorage)) packetStorage[uid] = {}; - var packets = packetStorage[uid]; - - var bzip = false; - if(!self.goldsrcSplits && uid & 0x80000000) bzip = true; - - var packetNum,payload,numPackets; - if(self.goldsrcSplits) { - packetNum = reader.uint(1); - numPackets = packetNum & 0x0f; - packetNum = (packetNum & 0xf0) >> 4; - payload = reader.rest(); - } else { - numPackets = reader.uint(1); - packetNum = reader.uint(1); - if(!self._skipSizeInSplitHeader) reader.skip(2); - if(packetNum == 0 && bzip) reader.skip(8); - payload = reader.rest(); - } - - packets[packetNum] = payload; - - if(self.debug) { - console.log("Received partial packet uid:"+uid+" num:"+packetNum); - console.log("Received "+Object.keys(packets).length+'/'+numPackets+" packets for this UID"); - } - - if(Object.keys(packets).length != numPackets) return; - - // assemble the parts - var list = []; - for(var i = 0; i < numPackets; i++) { - if(!(i in packets)) { - self.fatal('Missing packet #'+i); - return true; - } - list.push(packets[i]); - } - - var assembled = Buffer.concat(list); - if(bzip) { - if(self.debug) console.log("BZIP DETECTED - Extracing packet..."); - try { - assembled = new Buffer(Bzip2.decompressFile(assembled)); - } catch(e) { - self.fatal('Invalid bzip packet'); - return true; - } - } - var assembledReader = self.reader(assembled); - assembledReader.skip(4); // header - return receivedFull(assembledReader); - } - } - - function receivedFull(reader) { - var type = reader.uint(1); - - if(type == 0x41) { - if(self.debug) console.log('Received challenge key'); - if(self._challenge) return self.fatal('Received more than one challenge key'); - self._challenge = reader.uint(4); - - if(sendChallenge) { - if(self.debug) console.log('Restarting query'); - send(); - return true; - } - } - - if(self.debug) console.log("Received "+type.toString(16)+" expected "+expect.toString(16)); - if(type != expect) return; - callback(reader.rest()); - return true; - } + send(); } -}); +} + +module.exports = Valve; diff --git a/protocols/ventrilo.js b/protocols/ventrilo.js index 101924b..1fadd48 100644 --- a/protocols/ventrilo.js +++ b/protocols/ventrilo.js @@ -1,58 +1,53 @@ -var async = require('async'); - -module.exports = require('./core').extend({ - init: function() { - this._super(); +class Ventrilo extends require('./core') { + constructor() { + super(); this.byteorder = 'be'; - }, - run: function(state) { - var self = this; - - this.sendCommand(2,'',function(data) { + } + run(state) { + this.sendCommand(2,'',(data) => { state.raw = splitFields(data.toString()); - state.raw.CLIENTS.forEach(function(client) { + for (const client of state.raw.CLIENTS) { client.name = client.NAME; delete client.NAME; client.ping = parseInt(client.PING); delete client.PING; state.players.push(client); - }); + } delete state.raw.CLIENTS; if('NAME' in state.raw) state.name = state.raw.NAME; if('MAXCLIENTS' in state.raw) state.maxplayers = state.raw.MAXCLIENTS; - if(self.trueTest(state.raw.AUTH)) state.password = true; - self.finish(state); + if(this.trueTest(state.raw.AUTH)) state.password = true; + this.finish(state); }); - }, - sendCommand: function(cmd,password,c) { - var self = this; - var body = new Buffer(16); + } + sendCommand(cmd,password,c) { + const body = Buffer.alloc(16); body.write(password,0,15,'utf8'); - var encrypted = encrypt(cmd,body); + const encrypted = encrypt(cmd,body); - var packets = {}; - this.udpSend(encrypted, function(buffer) { + const packets = {}; + this.udpSend(encrypted, (buffer) => { if(buffer.length < 20) return; - var data = decrypt(buffer); + const data = decrypt(buffer); - if(data.zero != 0) return; + if(data.zero !== 0) return; packets[data.packetNum] = data.body; - if(Object.keys(packets).length != data.packetTotal) return; - - var out = []; - for(var i = 0; i < data.packetTotal; i++) { - if(!(i in packets)) return self.fatal('Missing packet #'+i); + if(Object.keys(packets).length !== data.packetTotal) return; + + const out = []; + for(let i = 0; i < data.packetTotal; i++) { + if(!(i in packets)) return this.fatal('Missing packet #'+i); out.push(packets[i]); } c(Buffer.concat(out)); return true; }); } -}); +} function splitFields(str,subMode) { - var splitter,delim; + let splitter,delim; if(subMode) { splitter = '='; delim = ','; @@ -61,21 +56,21 @@ function splitFields(str,subMode) { delim = '\n'; } - var split = str.split(delim); - var out = {}; + const split = str.split(delim); + const out = {}; if(!subMode) { out.CHANNELS = []; out.CLIENTS = []; } - split.forEach(function(one) { - var equal = one.indexOf(splitter); - var key = equal == -1 ? one : one.substr(0,equal); - if(!key || key == '\0') return; - var value = equal == -1 ? '' : one.substr(equal+splitter.length); - if(!subMode && key == 'CHANNEL') out.CHANNELS.push(splitFields(value,true)); - else if(!subMode && key == 'CLIENT') out.CLIENTS.push(splitFields(value,true)); + for (const one of split) { + const equal = one.indexOf(splitter); + const key = equal === -1 ? one : one.substr(0,equal); + if(!key || key === '\0') continue; + const value = equal === -1 ? '' : one.substr(equal+splitter.length); + if(!subMode && key === 'CHANNEL') out.CHANNELS.push(splitFields(value,true)); + else if(!subMode && key === 'CLIENT') out.CLIENTS.push(splitFields(value,true)); else out[key] = value; - }); + } return out; } @@ -84,8 +79,8 @@ function randInt(min,max) { } function crc(body) { - var crc = 0; - for(var i = 0; i < body.length; i++) { + let crc = 0; + for(let i = 0; i < body.length; i++) { crc = crc_table[crc>>8] ^ body.readUInt8(i) ^ (crc<<8); crc &= 0xffff; } @@ -93,12 +88,12 @@ function crc(body) { } function encrypt(cmd,body) { - var headerKeyStart = randInt(0,0xff); - var headerKeyAdd = randInt(1,0xff); - var bodyKeyStart = randInt(0,0xff); - var bodyKeyAdd = randInt(1,0xff); + const headerKeyStart = randInt(0,0xff); + const headerKeyAdd = randInt(1,0xff); + const bodyKeyStart = randInt(0,0xff); + const bodyKeyAdd = randInt(1,0xff); - var header = new Buffer(20); + const header = Buffer.alloc(20); header.writeUInt8(headerKeyStart,0); header.writeUInt8(headerKeyAdd,1); header.writeUInt16BE(cmd,4); @@ -110,9 +105,9 @@ function encrypt(cmd,body) { header.writeUInt8(bodyKeyAdd,17); header.writeUInt16BE(crc(body),18); - var offset = headerKeyStart; - for(var i = 2; i < header.length; i++) { - var val = header.readUInt8(i); + let offset = headerKeyStart; + for(let i = 2; i < header.length; i++) { + let val = header.readUInt8(i); val += code_head.charCodeAt(offset) + ((i-2) % 5); val = val & 0xff; header.writeUInt8(val,i); @@ -120,8 +115,8 @@ function encrypt(cmd,body) { } offset = bodyKeyStart; - for(var i = 0; i < body.length; i++) { - var val = body.readUInt8(i); + for(let i = 0; i < body.length; i++) { + let val = body.readUInt8(i); val += code_body.charCodeAt(offset) + (i % 72); val = val & 0xff; body.writeUInt8(val,i); @@ -131,25 +126,25 @@ function encrypt(cmd,body) { return Buffer.concat([header,body]); } function decrypt(data) { - var header = data.slice(0,20); - var body = data.slice(20); - var headerKeyStart = header.readUInt8(0); - var headerKeyAdd = header.readUInt8(1); + const header = data.slice(0,20); + const body = data.slice(20); + const headerKeyStart = header.readUInt8(0); + const headerKeyAdd = header.readUInt8(1); - var offset = headerKeyStart; - for(var i = 2; i < header.length; i++) { - var val = header.readUInt8(i); + let offset = headerKeyStart; + for(let i = 2; i < header.length; i++) { + let val = header.readUInt8(i); val -= code_head.charCodeAt(offset) + ((i-2) % 5); val = val & 0xff; header.writeUInt8(val,i); offset = (offset+headerKeyAdd) & 0xff; } - var bodyKeyStart = header.readUInt8(16); - var bodyKeyAdd = header.readUInt8(17); + const bodyKeyStart = header.readUInt8(16); + const bodyKeyAdd = header.readUInt8(17); offset = bodyKeyStart; - for(var i = 0; i < body.length; i++) { - var val = body.readUInt8(i); + for(let i = 0; i < body.length; i++) { + let val = body.readUInt8(i); val -= code_body.charCodeAt(offset) + (i % 72); val = val & 0xff; body.writeUInt8(val,i); @@ -168,7 +163,7 @@ function decrypt(data) { }; } -var code_head = +const code_head = '\x80\xe5\x0e\x38\xba\x63\x4c\x99\x88\x63\x4c\xd6\x54\xb8\x65\x7e'+ '\xbf\x8a\xf0\x17\x8a\xaa\x4d\x0f\xb7\x23\x27\xf6\xeb\x12\xf8\xea'+ '\x17\xb7\xcf\x52\x57\xcb\x51\xcf\x1b\x14\xfd\x6f\x84\x38\xb5\x24'+ @@ -186,7 +181,7 @@ var code_head = '\xb6\xac\x77\xc4\xbf\x59\x5e\x80\x74\xbb\xf2\xde\x57\x62\x4c\x1a'+ '\xff\x95\x6d\xc7\x04\xa2\x3b\xc4\x1b\x72\xc7\x6c\x82\x60\xd1\x0d'; -var code_body = +const code_body = '\x82\x8b\x7f\x68\x90\xe0\x44\x09\x19\x3b\x8e\x5f\xc2\x82\x38\x23'+ '\x6d\xdb\x62\x49\x52\x6e\x21\xdf\x51\x6c\x76\x37\x86\x50\x7d\x48'+ '\x1f\x65\xe7\x52\x6a\x88\xaa\xc1\x32\x2f\xf7\x54\x4c\xaa\x6d\x7e'+ @@ -204,7 +199,7 @@ var code_body = '\x52\x23\xa7\x20\xd2\xd7\x28\x07\x23\x14\x24\x3d\x45\xa5\xc7\x90'+ '\xdb\x77\xdd\xea\x38\x59\x89\x32\xbc\x00\x3a\x6d\x61\x4e\xdb\x29'; -var crc_table = [ +const crc_table = [ 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, @@ -238,3 +233,5 @@ var crc_table = [ 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 ]; + +module.exports = Ventrilo; diff --git a/protocols/warsow.js b/protocols/warsow.js index 9f43ca2..2c70196 100644 --- a/protocols/warsow.js +++ b/protocols/warsow.js @@ -1,12 +1,13 @@ -module.exports = require('./quake3').extend({ - finalizeState: function(state) { - this._super(state); +class Warsow extends require('./quake3') { + finalizeState(state) { + super.finalizeState(state); if(state.players) { - for(var i = 0; i < state.players.length; i++) { - var player = state.players[i]; + for(const player of state.players) { player.team = player.address; delete player.address; } } } -}); +} + +module.exports = Warsow; diff --git a/test.sh b/test.sh new file mode 100644 index 0000000..777e552 --- /dev/null +++ b/test.sh @@ -0,0 +1 @@ +bin/gamedig.js --debug --type "$1" --host "$2" --port "$3"