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