From 8552d0674f8734c76a2c2455c796ed34c21e554b Mon Sep 17 00:00:00 2001 From: Michael Morrison Date: Wed, 10 Jul 2013 05:02:48 -0500 Subject: [PATCH] initial commit --- Class.js | 74 +++++++++++++++++++ LICENSE | 21 ++++++ README.md | 58 +++++++++++++++ index.js | 45 ++++++++++++ protocols/armagetron.js | 62 ++++++++++++++++ protocols/core.js | 136 +++++++++++++++++++++++++++++++++++ protocols/gamespy3.js | 101 ++++++++++++++++++++++++++ protocols/killingfloor.js | 6 ++ protocols/minecraft.js | 7 ++ protocols/quake2.js | 50 +++++++++++++ protocols/quake3.js | 8 +++ protocols/source.js | 147 ++++++++++++++++++++++++++++++++++++++ protocols/unreal2.js | 81 +++++++++++++++++++++ reader.js | 111 ++++++++++++++++++++++++++++ 14 files changed, 907 insertions(+) create mode 100644 Class.js create mode 100644 LICENSE create mode 100644 README.md create mode 100644 index.js create mode 100644 protocols/armagetron.js create mode 100644 protocols/core.js create mode 100644 protocols/gamespy3.js create mode 100644 protocols/killingfloor.js create mode 100644 protocols/minecraft.js create mode 100644 protocols/quake2.js create mode 100644 protocols/quake3.js create mode 100644 protocols/source.js create mode 100644 protocols/unreal2.js create mode 100644 reader.js diff --git a/Class.js b/Class.js new file mode 100644 index 0000000..6e8efbc --- /dev/null +++ b/Class.js @@ -0,0 +1,74 @@ +/* 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/LICENSE b/LICENSE new file mode 100644 index 0000000..8abda23 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) node-gamedig developers + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9f6a418 --- /dev/null +++ b/README.md @@ -0,0 +1,58 @@ +node-GameDig - Game Server Query Library +=== + +Usage +--- + +```javascript +var Gamedig = require('gamedig'); +Gamedig.query({ + type: 'minecraft', + host: 'mc.example.com', + success: function(state) { + if(state.error) console.log("Server is offline"); + else console.log(state); + } +}); +``` + +State Object +--- +The returned state object may contain some or all of the following keys, depending on +what is available from the queried server: + +* error +* numplayers +* maxplayers +* players +** name +** ping +** score +* map +* gametype +* name + +Supported Games +--- +* Armagetron +* Gamespy 3 Protocol +** +* Unreal 2 Protocol +** Killing Floor +* Quake 2 +* Quake 3 +* Source Engine +** Counter-Strike: Source +** Counter-Strike: Global Offensive +** Team Fortress 2 +** + others +* GoldSrc Engine +** Half Life: Death Match +** Ricochet +** Counter-Strike: 1.6 +** + others + +Unstable API +--- +The contents of the returned state object may change slightly from build to build, as the API +is still being formed. diff --git a/index.js b/index.js new file mode 100644 index 0000000..f4543f7 --- /dev/null +++ b/index.js @@ -0,0 +1,45 @@ +var dgram = require('dgram'), + EventEmitter = require('events').EventEmitter, + util = require('util'), + dns = require('dns'); + +var activeQueries = []; + +var udpSocket = dgram.createSocket('udp4'); +udpSocket.unref(); +udpSocket.bind(21943); +udpSocket.on('message', function(buffer, rinfo) { + for(var i = 0; i < activeQueries.length; i++) { + var query = activeQueries[i]; + if(query.address != rinfo.address) continue; + if(query.port != rinfo.port) continue; + query._udpResponse(buffer); + break; + } +}); + +module.exports = { + + query: function(options) { + var type = (options.type || '').replace(/\W/g,''); + + var protocol = require('./protocols/'+type); + var query = new protocol(); + query.udpSocket = udpSocket; + query.type = type; + query.options = options; + activeQueries.push(query); + + query.on('finished', function() { + var i = activeQueries.indexOf(query); + if(i >= 0) activeQueries.splice(i, 1); + }); + + process.nextTick(function() { + query.start(); + }); + + return query; + } + +}; diff --git a/protocols/armagetron.js b/protocols/armagetron.js new file mode 100644 index 0000000..3ee6652 --- /dev/null +++ b/protocols/armagetron.js @@ -0,0 +1,62 @@ +module.exports = require('./core').extend({ + init: function() { + this._super(); + this.encoding = 'latin1'; + this.byteorder = 'be'; + }, + run: function() { + var self = this; + + var b = new Buffer([0,0x35,0,0,0,0,0,0x11]); + + this.udpSend(b,function(buffer) { + var state = {}; + var reader = self.reader(buffer); + + reader.skip(6); + + state.port = self.readUInt(reader); + state.hostname = self.readString(reader,buffer); + state.name = self.readString(reader,buffer); + state.numplayers = self.readUInt(reader); + state.versionmin = self.readUInt(reader); + state.versionmax = self.readUInt(reader); + state.version = self.readString(reader,buffer); + state.maxplayers = self.readUInt(reader); + + var players = self.readString(reader,buffer); + var list = players.split('\n'); + state.players = []; + for(var i = 0; i < list.length; i++) { + if(!list[i]) continue; + state.players.push({name:list[i]}); + } + + state.options = self.readString(reader,buffer); + state.uri = self.readString(reader,buffer); + state.globalids = self.readString(reader,buffer); + self.finish(state); + return true; + }); + }, + readUInt: function(reader) { + var a = reader.uint(2); + var b = reader.uint(2); + return (b<<16) + a; + }, + readString: function(reader,b) { + var 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); + if(i+1= this.buffer.length) return ''; + if(this.buffer.readUInt8(end) == delim) break; + end++; + } + this.i = end+1; + } else { + end = start+options.length; + if(end > this.buffer.length) return ''; + this.i = end; + if(options.stripnull && this.buffer.readUInt8(end-1) == 0) end--; + } + + var out = this.buffer.slice(start, end); + var enc = options.encoding; + if(enc == 'utf8' || enc == 'ucs2' || enc == 'binary') { + out = out.toString(enc); + } else { + var converter = getIconv(enc); + out = converter.convert(out).toString(); + } + return out; + }, + uint: function(bytes) { + var r = 0; + if(this.i+bytes <= this.buffer.length) { + 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 = Bignum.fromBuffer(this.buffer.slice(this.i,this.i+8),{endian:'big',size:'auto'}); + } 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 = Bignum.fromBuffer(this.buffer.slice(this.i,this.i+8),{endian:'little',size:'auto'}); + } + } + this.i += bytes; + return r; + }, + float: function() { + var r = 0; + if(this.i+4 <= this.buffer.length) { + if(this.query.byteorder == 'be') r = this.buffer.readFloatBE(this.i); + else r = this.buffer.readFloatLE(this.i); + } + this.i += 4; + return r; + }, + pascal: function(enc) { + if(this.i >= this.buffer.length) return ''; + var length = this.buffer.readUInt8(this.i); + this.i++; + return this.string({ + encoding: enc, + length: length, + stripnull: true + }); + }, + done: function() { + return this.i >= this.buffer.length; + } +}; + +module.exports = Reader;