initial commit
This commit is contained in:
commit
8552d0674f
|
@ -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;
|
|
@ -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.
|
|
@ -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.
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
|
@ -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<len) out += String.fromCharCode(lo);
|
||||||
|
if(i+2<len) out += String.fromCharCode(hi);
|
||||||
|
}
|
||||||
|
|
||||||
|
out = out.replace(/0x[0-9a-f]{6}/g,''); // strip color codes
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,136 @@
|
||||||
|
var EventEmitter = require('events').EventEmitter,
|
||||||
|
dns = require('dns'),
|
||||||
|
async = require('async'),
|
||||||
|
Class = require('../Class'),
|
||||||
|
Bignum = require('bignum'),
|
||||||
|
Reader = require('../reader');
|
||||||
|
|
||||||
|
module.exports = Class.extend(EventEmitter,{
|
||||||
|
init: function() {
|
||||||
|
this._super();
|
||||||
|
this.options = {};
|
||||||
|
this.maxAttempts = 1;
|
||||||
|
this.attempt = 1;
|
||||||
|
this.finished = false;
|
||||||
|
this.encoding = 'utf8';
|
||||||
|
this.byteorder = 'le';
|
||||||
|
this.delimiter = '\0';
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
this.globalTimeoutTimer = setTimeout(function() {
|
||||||
|
self.fatal('timeout');
|
||||||
|
},10000);
|
||||||
|
},
|
||||||
|
|
||||||
|
fatal: function(err) {
|
||||||
|
this.error(err,true);
|
||||||
|
},
|
||||||
|
error: function(err,fatal) {
|
||||||
|
if(!fatal && this.attempt < this.maxAttempts) {
|
||||||
|
this.attempt++;
|
||||||
|
this.start();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.done({error: err.toString()});
|
||||||
|
},
|
||||||
|
finish: function(result) {
|
||||||
|
this.done(result);
|
||||||
|
},
|
||||||
|
done: function(result) {
|
||||||
|
if(this.finished) return;
|
||||||
|
|
||||||
|
clearTimeout(this.globalTimeoutTimer);
|
||||||
|
|
||||||
|
if(this.options.notes)
|
||||||
|
result.notes = this.options.notes;
|
||||||
|
|
||||||
|
this.reset();
|
||||||
|
this.finished = true;
|
||||||
|
this.emit('finished',result);
|
||||||
|
},
|
||||||
|
|
||||||
|
reset: function() {
|
||||||
|
if(this.timers) {
|
||||||
|
this.timers.forEach(function(timer) {
|
||||||
|
clearTimeout(timer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.timers = [];
|
||||||
|
|
||||||
|
this.udpTimeoutTimer = false;
|
||||||
|
this.udpCallback = false;
|
||||||
|
},
|
||||||
|
start: function() {
|
||||||
|
var self = this;
|
||||||
|
this.reset();
|
||||||
|
|
||||||
|
async.series([
|
||||||
|
function(c) {
|
||||||
|
// resolve host names
|
||||||
|
if(!('host' in self.options)) return c();
|
||||||
|
if(self.options.host.match(/\d+\.\d+\.\d+\.\d+/)) {
|
||||||
|
self.options.address = self.options.host;
|
||||||
|
c();
|
||||||
|
} else {
|
||||||
|
dns.lookup(self.options.host, function(err,address,family) {
|
||||||
|
if(err) return self.error(err);
|
||||||
|
self.options.address = address;
|
||||||
|
c();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, function(c) {
|
||||||
|
self.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
|
reader: function(buffer) {
|
||||||
|
return new Reader(this,buffer);
|
||||||
|
},
|
||||||
|
|
||||||
|
setTimeout: function(c,t) {
|
||||||
|
if(this.finished) return 0;
|
||||||
|
var id = setTimeout(c,t);
|
||||||
|
this.timers.push(id);
|
||||||
|
return id;
|
||||||
|
},
|
||||||
|
|
||||||
|
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);
|
||||||
|
if(!onpacket) return;
|
||||||
|
|
||||||
|
self.udpTimeoutTimer = self.setTimeout(function() {
|
||||||
|
self.udpCallback = false;
|
||||||
|
var timeout = false;
|
||||||
|
if(!ontimeout || ontimeout() !== true) timeout = true;
|
||||||
|
if(timeout) self.error('timeout');
|
||||||
|
},1000);
|
||||||
|
self.udpCallback = onpacket;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
_udpSendNow: function(buffer) {
|
||||||
|
if(!('port' 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');
|
||||||
|
this.udpSocket.send(buffer,0,buffer.length,this.options.port,this.options.address);
|
||||||
|
},
|
||||||
|
_udpResponse: function(buffer) {
|
||||||
|
if(this.udpCallback) {
|
||||||
|
var result = this.udpCallback(buffer);
|
||||||
|
if(result === true) {
|
||||||
|
// we're done with this udp session
|
||||||
|
clearTimeout(this.udpTimeoutTimer);
|
||||||
|
this.udpCallback = false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.udpResponse(buffer);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
udpResponse: function() {}
|
||||||
|
});
|
|
@ -0,0 +1,101 @@
|
||||||
|
module.exports = require('./core').extend({
|
||||||
|
init: function() {
|
||||||
|
this._super();
|
||||||
|
this.sessionId = 1;
|
||||||
|
this.encoding = 'latin1';
|
||||||
|
this.byteorder = 'be';
|
||||||
|
},
|
||||||
|
run: function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this.sendPacket(9,false,false,false,function(buffer) {
|
||||||
|
var reader = self.reader(buffer);
|
||||||
|
reader.skip(5);
|
||||||
|
var challenge = reader.string();
|
||||||
|
|
||||||
|
self.sendPacket(0,challenge,new Buffer([0,0,0,0]),true,function(buffer) {
|
||||||
|
|
||||||
|
var reader = self.reader(buffer);
|
||||||
|
var state = {
|
||||||
|
players:[]
|
||||||
|
};
|
||||||
|
|
||||||
|
while(!reader.done()) {
|
||||||
|
var key = reader.string();
|
||||||
|
if(!key) break;
|
||||||
|
var value = reader.string();
|
||||||
|
state[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
var mode = '';
|
||||||
|
while(!reader.done()) {
|
||||||
|
var mode = reader.string();
|
||||||
|
reader.skip(1);
|
||||||
|
|
||||||
|
while(!reader.done()) {
|
||||||
|
var item = reader.string();
|
||||||
|
if(!item) break;
|
||||||
|
|
||||||
|
if(mode.substr(-1) == '_') {
|
||||||
|
// players
|
||||||
|
state.players.push({name:item})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.finish(state);
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
sendPacket: function(type,challenge,payload,assemble,c) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var challengeLength = challenge === false ? 0 : 4;
|
||||||
|
var payloadLength = payload ? payload.length : 0;
|
||||||
|
|
||||||
|
var b = new Buffer(7 + challengeLength + payloadLength);
|
||||||
|
b.writeUInt8(0xFE, 0);
|
||||||
|
b.writeUInt8(0xFD, 1);
|
||||||
|
b.writeUInt8(type, 2);
|
||||||
|
b.writeUInt32BE(this.sessionId, 3);
|
||||||
|
if(challengeLength) b.writeUInt32BE(challenge, 7);
|
||||||
|
if(payloadLength) payload.copy(b, 7+challengeLength);
|
||||||
|
|
||||||
|
var numPackets = 0;
|
||||||
|
var packets = {};
|
||||||
|
this.udpSend(b,function(buffer) {
|
||||||
|
var iType = buffer.readUInt8(0);
|
||||||
|
if(iType != type) return;
|
||||||
|
var iSessionId = buffer.readUInt32BE(1);
|
||||||
|
if(iSessionId != self.sessionId) return;
|
||||||
|
|
||||||
|
if(!assemble) {
|
||||||
|
c(buffer);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var id = buffer.readUInt16LE(14);
|
||||||
|
var last = (id & 0x80);
|
||||||
|
id = id & 0x7f;
|
||||||
|
if(last) numPackets = id+1;
|
||||||
|
|
||||||
|
packets[id] = buffer;
|
||||||
|
|
||||||
|
if(!numPackets || Object.keys(packets).length != numPackets) return;
|
||||||
|
|
||||||
|
// assemble the parts
|
||||||
|
var list = [];
|
||||||
|
for(var i = 0; i < numPackets; i++) {
|
||||||
|
if(!(i in packets)) {
|
||||||
|
self.error('Missing packet #'+i);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
list.push(packets[i].slice(16));
|
||||||
|
}
|
||||||
|
var assembled = Buffer.concat(list);
|
||||||
|
c(assembled);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,6 @@
|
||||||
|
module.exports = require('./unreal2').extend({
|
||||||
|
init: function() {
|
||||||
|
this._super();
|
||||||
|
this.port = 7708;
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,7 @@
|
||||||
|
module.exports = require('./gamespy3').extend({
|
||||||
|
init: function() {
|
||||||
|
this._super();
|
||||||
|
this.maxAttempts = 2;
|
||||||
|
this.port = 25565;
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,50 @@
|
||||||
|
module.exports = require('./core').extend({
|
||||||
|
init: function() {
|
||||||
|
this._super();
|
||||||
|
this.port = 27910;
|
||||||
|
this.encoding = 'latin1';
|
||||||
|
this.delimiter = '\n';
|
||||||
|
this.sendHeader = 'status';
|
||||||
|
this.responseHeader = 'print';
|
||||||
|
},
|
||||||
|
run: function() {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
this.udpSend('\xff\xff\xff\xff'+this.sendHeader+'\x00',function(buffer) {
|
||||||
|
console.log(buffer);
|
||||||
|
var reader = self.reader(buffer);
|
||||||
|
var header = reader.string();
|
||||||
|
if(header != '\xff\xff\xff\xff'+this.responseHeader) return;
|
||||||
|
|
||||||
|
var state = {};
|
||||||
|
|
||||||
|
var info = reader.string().split('\\');
|
||||||
|
if(info[0] == '') info.shift();
|
||||||
|
while(true) {
|
||||||
|
var key = info.shift();
|
||||||
|
var value = info.shift();
|
||||||
|
if(typeof value == 'undefined') break;
|
||||||
|
state[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.players = [];
|
||||||
|
while(!reader.done()) {
|
||||||
|
var player = reader.string();
|
||||||
|
var split = player.split('"');
|
||||||
|
var split1 = split[0].split(' ');
|
||||||
|
|
||||||
|
var frags = parseInt(split1[0]);
|
||||||
|
var ping = parseInt(split1[1]);
|
||||||
|
var name = split[1] || '';
|
||||||
|
var address = split[3] || '';
|
||||||
|
|
||||||
|
state.players.push({
|
||||||
|
frags:frags, ping:ping, name:name, address:address
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
self.finish(state);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,8 @@
|
||||||
|
module.exports = require('./quake2').extend({
|
||||||
|
init: function() {
|
||||||
|
this._super();
|
||||||
|
this.port = 27960;
|
||||||
|
this.sendHeader = 'getstatus';
|
||||||
|
this.responseHeader = 'statusResponse';
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,147 @@
|
||||||
|
var async = require('async');
|
||||||
|
|
||||||
|
module.exports = require('./core').extend({
|
||||||
|
init: function() {
|
||||||
|
this._super();
|
||||||
|
this.goldsrc = false;
|
||||||
|
},
|
||||||
|
run: function() {
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
var challenge;
|
||||||
|
var state = {};
|
||||||
|
|
||||||
|
async.series([
|
||||||
|
function(c) {
|
||||||
|
self.sendPacket(
|
||||||
|
0x54,false,new Buffer('Source Engine Query'),
|
||||||
|
self.goldsrc ? 0x6D : 0x49,
|
||||||
|
function(b) {
|
||||||
|
var reader = self.reader(b);
|
||||||
|
|
||||||
|
if(self.goldsrc) state.address = reader.string();
|
||||||
|
else state.protocol = reader.uint(1);
|
||||||
|
|
||||||
|
state.name = reader.string();
|
||||||
|
state.map = reader.string();
|
||||||
|
state.folder = reader.string();
|
||||||
|
state.game = reader.string();
|
||||||
|
state.steamappid = reader.uint(2);
|
||||||
|
state.numplayers = reader.uint(1);
|
||||||
|
state.maxplayers = reader.uint(1);
|
||||||
|
|
||||||
|
if(self.goldsrc) state.protocol = reader.uint(1);
|
||||||
|
else state.numbots = reader.uint(1);
|
||||||
|
|
||||||
|
state.listentype = String.fromCharCode(reader.uint(1));
|
||||||
|
state.environment = String.fromCharCode(reader.uint(1));
|
||||||
|
state.passworded = reader.uint(1);
|
||||||
|
if(self.goldsrc) {
|
||||||
|
state.ismod = reader.uint(1);
|
||||||
|
if(state.ismod) {
|
||||||
|
state.modlink = reader.string();
|
||||||
|
state.moddownload = reader.string();
|
||||||
|
reader.skip(1);
|
||||||
|
state.modversion = reader.uint(4);
|
||||||
|
state.modsize = reader.uint(4);
|
||||||
|
state.modtype = reader.uint(1);
|
||||||
|
state.moddll = reader.uint(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
state.secure = reader.uint(1);
|
||||||
|
|
||||||
|
if(self.goldsrc) {
|
||||||
|
state.numbots = reader.uint(1);
|
||||||
|
} else {
|
||||||
|
if(state.folder == 'ship') {
|
||||||
|
state.shipmode = reader.uint(1);
|
||||||
|
state.shipwitnesses = reader.uint(1);
|
||||||
|
state.shipduration = reader.uint(1);
|
||||||
|
}
|
||||||
|
state.version = reader.string();
|
||||||
|
var extraFlag = reader.uint(1);
|
||||||
|
if(extraFlag & 0x80) state.port = reader.uint(2);
|
||||||
|
if(extraFlag & 0x10) state.steamid = reader.uint(8);
|
||||||
|
if(extraFlag & 0x40) {
|
||||||
|
state.sourcetvport = reader.uint(2);
|
||||||
|
state.sourcetvname = reader.string();
|
||||||
|
}
|
||||||
|
if(extraFlag & 0x20) state.tags = reader.string();
|
||||||
|
if(extraFlag & 0x01) state.gameid = reader.uint(8);
|
||||||
|
}
|
||||||
|
|
||||||
|
c();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
},
|
||||||
|
function(c) {
|
||||||
|
self.sendPacket(0x55,0xffffffff,false,0x41,function(b) {
|
||||||
|
var reader = self.reader(b);
|
||||||
|
challenge = reader.uint(4);
|
||||||
|
c();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function(c) {
|
||||||
|
self.sendPacket(0x55,challenge,false,0x44,function(b) {
|
||||||
|
var reader = self.reader(b);
|
||||||
|
var num = reader.uint(1);
|
||||||
|
state.players = [];
|
||||||
|
for(var i = 0; i < num; i++) {
|
||||||
|
reader.skip(1);
|
||||||
|
var name = reader.string();
|
||||||
|
var score = reader.uint(4);
|
||||||
|
var time = reader.float();
|
||||||
|
state.players.push({
|
||||||
|
name:name, score:score, time:time
|
||||||
|
});
|
||||||
|
}
|
||||||
|
c();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function(c) {
|
||||||
|
self.sendPacket(0x56,challenge,false,0x45,function(b) {
|
||||||
|
var reader = self.reader(b);
|
||||||
|
var num = reader.uint(2);
|
||||||
|
state.rules = [];
|
||||||
|
for(var i = 0; i < num; i++) {
|
||||||
|
var key = reader.string();
|
||||||
|
var value = reader.string();
|
||||||
|
state.rules[key] = value;
|
||||||
|
}
|
||||||
|
c();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function(c) {
|
||||||
|
self.finish(state);
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
sendPacket: function(type,challenge,payload,expect,callback) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
var challengeLength = challenge === false ? 0 : 4;
|
||||||
|
var payloadLength = payload ? payload.length : 0;
|
||||||
|
|
||||||
|
var b = new Buffer(5 + challengeLength + payloadLength);
|
||||||
|
b.writeInt32LE(-1, 0);
|
||||||
|
b.writeUInt8(type, 4);
|
||||||
|
if(challengeLength) b.writeUInt32LE(challenge, 5);
|
||||||
|
if(payloadLength) payload.copy(b, 5+challengeLength);
|
||||||
|
|
||||||
|
function received(payload) {
|
||||||
|
var type = payload.readUInt8(0);
|
||||||
|
if(type != expect) return;
|
||||||
|
callback(payload.slice(1));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var packets = [];
|
||||||
|
this.udpSend(b,function(buffer) {
|
||||||
|
var header = buffer.readInt32LE(0);
|
||||||
|
if(header == -1) return received(buffer.slice(4));
|
||||||
|
|
||||||
|
// partial pack
|
||||||
|
console.log(buffer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,81 @@
|
||||||
|
var async = require('async');
|
||||||
|
|
||||||
|
module.exports = require('./core').extend({
|
||||||
|
init: function() {
|
||||||
|
this._super();
|
||||||
|
this.encoding = 'latin1';
|
||||||
|
},
|
||||||
|
run: function() {
|
||||||
|
|
||||||
|
var self = this;
|
||||||
|
var state = {};
|
||||||
|
|
||||||
|
async.series([
|
||||||
|
function(c) {
|
||||||
|
self.sendPacket(0,true,function(b) {
|
||||||
|
var reader = self.reader(b);
|
||||||
|
state.serverid = reader.uint(4);
|
||||||
|
state.ip = reader.pascal();
|
||||||
|
state.port = reader.uint(4);
|
||||||
|
state.queryport = reader.uint(4);
|
||||||
|
state.name = reader.pascal();
|
||||||
|
state.map = reader.pascal();
|
||||||
|
state.gametype = reader.pascal();
|
||||||
|
state.numplayers = reader.uint(4);
|
||||||
|
state.maxplayers = reader.uint(4);
|
||||||
|
state.ping = reader.uint(4);
|
||||||
|
c();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function(c) {
|
||||||
|
self.sendPacket(1,true,function(b) {
|
||||||
|
var reader = self.reader(b);
|
||||||
|
state.mutators = [];
|
||||||
|
state.rules = {};
|
||||||
|
while(!reader.done()) {
|
||||||
|
var key = reader.pascal();
|
||||||
|
var value = reader.pascal();
|
||||||
|
if(key == 'Mutator') state.mutators.push(value);
|
||||||
|
else state.rules[key] = value;
|
||||||
|
}
|
||||||
|
c();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function(c) {
|
||||||
|
self.sendPacket(2,false,function(b) {
|
||||||
|
var reader = self.reader(b);
|
||||||
|
state.players = [];
|
||||||
|
while(!reader.done()) {
|
||||||
|
var id = reader.uint(4);
|
||||||
|
console.log(b.slice(reader.offset()));
|
||||||
|
var name = reader.pascal();
|
||||||
|
var ping = reader.uint(4);
|
||||||
|
var score = reader.uint(4);
|
||||||
|
reader.skip(4);
|
||||||
|
state.players.push({
|
||||||
|
id: id, name: name, ping: ping, score: score
|
||||||
|
});
|
||||||
|
}
|
||||||
|
c();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
function(c) {
|
||||||
|
self.finish(state);
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
sendPacket: function(type,required,callback) {
|
||||||
|
var outbuffer = new Buffer([0x79,0,0,0,type]);
|
||||||
|
|
||||||
|
var packets = [];
|
||||||
|
this.udpSend(outbuffer,function(buffer) {
|
||||||
|
var iType = buffer.readUInt8(4);
|
||||||
|
if(iType != type) return;
|
||||||
|
packets.push(buffer.slice(5));
|
||||||
|
},function() {
|
||||||
|
if(!packets.length && required) return;
|
||||||
|
callback(Buffer.concat(packets));
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,111 @@
|
||||||
|
var Iconv = require('iconv').Iconv;
|
||||||
|
var Iconv_converters = {};
|
||||||
|
function getIconv(from) {
|
||||||
|
var to = 'utf-8';
|
||||||
|
var key = from+'---'+to;
|
||||||
|
if(!(key in Iconv_converters)) {
|
||||||
|
Iconv_converters[key] = new Iconv(from, to);
|
||||||
|
}
|
||||||
|
return Iconv_converters[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function Reader(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) {
|
||||||
|
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 options = args[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
options.encoding = options.encoding || this.query.encoding;
|
||||||
|
if(options.encoding == 'latin1') options.encoding = 'windows-1252';
|
||||||
|
|
||||||
|
var start = this.i+0;
|
||||||
|
var 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);
|
||||||
|
while(true) {
|
||||||
|
if(end >= 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;
|
Loading…
Reference in New Issue