initial commit

This commit is contained in:
Michael Morrison 2013-07-10 05:02:48 -05:00
commit 8552d0674f
14 changed files with 907 additions and 0 deletions

62
protocols/armagetron.js Normal file
View file

@ -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;
}
});

136
protocols/core.js Normal file
View file

@ -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() {}
});

101
protocols/gamespy3.js Normal file
View file

@ -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;
});
}
});

View file

@ -0,0 +1,6 @@
module.exports = require('./unreal2').extend({
init: function() {
this._super();
this.port = 7708;
}
});

7
protocols/minecraft.js Normal file
View file

@ -0,0 +1,7 @@
module.exports = require('./gamespy3').extend({
init: function() {
this._super();
this.maxAttempts = 2;
this.port = 25565;
}
});

50
protocols/quake2.js Normal file
View file

@ -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;
});
}
});

8
protocols/quake3.js Normal file
View file

@ -0,0 +1,8 @@
module.exports = require('./quake2').extend({
init: function() {
this._super();
this.port = 27960;
this.sendHeader = 'getstatus';
this.responseHeader = 'statusResponse';
}
});

147
protocols/source.js Normal file
View file

@ -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);
});
}
});

81
protocols/unreal2.js Normal file
View file

@ -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;
});
}
});