Super mega-commit

Organize files
Rewrite readme for new game IDs and command line
Add command line access
Replace some dependencies that required binaries with simpler alternatives
Switch gbxremote back to upstream, Closes #2
Moved simple aliases into an alias file, rather than seperate files for each
Patched nearly every protocol variant with tons of bug fixes
Re-tested every combination of server and protocol types except nadeo
Added alternative minecraft query check (minecraftping)
Fixed mutant factions query
Fixed valve gold not working at all
Stripped colors more reliably from protocols that support colors
Added a couple more fields to ut2004 and killing floor
and more that I probably forgot.

This shouldn't break compatibility too bad -- at the most, some game IDs may have changed.
This commit is contained in:
Michael Morrison 2014-01-31 16:27:52 -06:00
parent a89fb7bbdf
commit c82554ad1a
31 changed files with 573 additions and 135 deletions

View file

@ -1,62 +0,0 @@
module.exports = require('./core').extend({
init: function() {
this._super();
this.pretty = 'Armagetron';
this.encoding = 'latin1';
this.byteorder = 'be';
this.options.port = 4534;
},
run: function(state) {
var self = this;
var b = new Buffer([0,0x35,0,0,0,0,0,0x11]);
this.udpSend(b,function(buffer) {
var reader = self.reader(buffer);
reader.skip(6);
state.raw.port = self.readUInt(reader);
state.raw.hostname = self.readString(reader,buffer);
state.name = 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);
var players = self.readString(reader,buffer);
var list = players.split('\n');
for(var i = 0; i < list.length; i++) {
if(!list[i]) continue;
state.players.push({name:list[i]});
}
state.raw.options = self.readString(reader,buffer);
state.raw.uri = self.readString(reader,buffer);
state.raw.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;
}
});

View file

@ -1,60 +0,0 @@
var request = require('request');
module.exports = require('./core').extend({
init: function() {
this._super();
this.pretty = 'Build and Shoot';
this.options.port = 32886;
},
run: function(state) {
var self = this;
request({
uri: 'http://'+this.options.address+':'+this.options.port+'/',
timeout: 3000,
}, function(e,r,body) {
if(e) return self.error('HTTP error');
var m = body.match(/status server for (.*?)\r|\n/);
if(m) state.name = m[1];
var m = body.match(/Current uptime: (\d+)/);
if(m) state.raw.uptime = m[1];
var m = body.match(/currently running (.*?) by /);
if(m) state.map = m[1];
var 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/);
if(m) {
var table = m[1];
var pre = /<tr>[^]*<td>([^]*)<\/td>[^]*<td>([^]*)<\/td>[^]*<td>([^]*)<\/td>[^]*<td>([^]*)<\/td>/g;
while(pm = pre.exec(table)) {
if(pm[2] == 'Ping') continue;
state.players.push({
name: pm[1],
ping: pm[2],
team: pm[3],
score: pm[4]
});
}
}
/*
var m = this.options.address.match(/(\d+)\.(\d+)\.(\d+)\.(\d+)/);
if(m) {
var o1 = parseInt(m[1]);
var o2 = parseInt(m[2]);
var o3 = parseInt(m[3]);
var o4 = parseInt(m[4]);
var addr = o1+(o2<<8)+(o3<<16)+(o4<<24);
state.raw.url = 'aos://'+addr;
}
*/
self.finish(state);
});
}
});

View file

@ -1,172 +0,0 @@
var EventEmitter = require('events').EventEmitter,
dns = require('dns'),
async = require('async'),
Class = require('../Class'),
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()});
},
initState: function() {
return {
name: '',
map: '',
password: false,
raw: {},
maxplayers: 0,
players: [],
bots: []
};
},
finalizeState: function(state) {},
finish: function(state) {
this.finalizeState(state);
this.done(state);
},
done: function(state) {
if(this.finished) return;
clearTimeout(this.globalTimeoutTimer);
if(this.options.notes)
state.notes = this.options.notes;
state.query = {};
if('host' in this.options) state.query.host = this.options.host;
if('address' in this.options) state.query.address = this.options.address;
if('port' in this.options) state.query.port = this.options.port;
state.query.type = this.type;
if('pretty' in this) state.query.pretty = this.pretty;
this.reset();
this.finished = true;
this.emit('finished',state);
if(this.options.callback) this.options.callback(state);
},
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 {
self.parseDns(self.options.host,c);
}
}, function(c) {
self.run(self.initState());
}
]);
},
parseDns: function(host,c) {
var self = this;
dns.lookup(host, function(err,address,family) {
if(err) return self.error(err);
self.options.address = address;
c();
});
},
// utils
reader: function(buffer) {
return new Reader(this,buffer);
},
translate: function(obj,trans) {
for(var from in trans) {
var to = trans[from];
if(from in obj) {
if(to) obj[to] = obj[from];
delete obj[from];
}
}
},
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() {}
});

View file

@ -1,102 +0,0 @@
module.exports = require('./core').extend({
init: function() {
this._super();
this.sessionId = 1;
this.encoding = 'latin1';
this.byteorder = 'be';
},
run: function(state) {
var self = this;
this.sendPacket(9,false,false,false,function(buffer) {
var reader = self.reader(buffer);
reader.skip(5);
var challenge = parseInt(reader.string());
self.sendPacket(0,challenge,new Buffer([0xff,0xff,0xff,0x01]),true,function(buffer) {
var reader = self.reader(buffer);
while(!reader.done()) {
var key = reader.string();
if(!key) break;
var value = reader.string();
state.raw[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})
}
}
}
if('hostname' in state.raw) state.name = state.raw.hostname;
if('map' in state.raw) state.map = state.raw.map;
if('maxplayers' in state.raw) state.maxplayers = state.raw.maxplayers;
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.writeInt32BE(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.slice(16);
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]);
}
var assembled = Buffer.concat(list);
c(assembled);
return true;
});
}
});

View file

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

View file

@ -1,34 +0,0 @@
var dns = require('dns');
module.exports = require('./gamespy3').extend({
init: function() {
this._super();
this.pretty = 'Minecraft';
this.maxAttempts = 2;
this.options.port = 25565;
},
parseDns: function(host,c) {
var self = this;
var _super = this._super;
function fallback(h) { _super.call(self,h,c); }
dns.resolve('_minecraft._tcp.'+host, 'SRV', function(err,addresses) {
if(err) return fallback(host);
if(addresses.length >= 1) {
var line = addresses[0];
self.options.port = line.port;
var srvhost = line.name;
if(srvhost.match(/\d+\.\d+\.\d+\.\d+/)) {
self.options.address = srvhost;
c();
} else {
// resolve yet again
fallback(srvhost);
}
return;
}
return fallback(host);
});
}
});

View file

@ -1,59 +0,0 @@
var request = require('request');
module.exports = require('./core').extend({
init: function() {
this._super();
this.pretty = 'Mutant Factions';
this.options.port = 11235;
},
run: function(state) {
var self = this;
request({
uri: 'http://mutantfactions.net/game/receiveLobby.php',
timeout: 3000,
}, function(e,r,body) {
if(e) return self.error('Lobby request error');
var split = body.split('<br/>');
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 == this.options.address && port == this.options.port) {
found = fields;
break;
}
}
if(!found) return self.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];
// 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];
// fields[18] is unknown? listen server?
state.raw.version = fields[19];
for(var i = 0; i < state.raw.numplayers; i++) {
state.players.push({});
}
self.finish(state);
});
}
});

View file

@ -1,74 +0,0 @@
var gbxremote = require('gbxremote'),
async = require('async');
module.exports = require('./core').extend({
init: function() {
this._super();
this.options.port = 5000;
this.gbxclient = false;
},
reset: function() {
this._super();
if(this.gbxclient) {
this.gbxclient.terminate();
this.gbxclient = false;
}
},
run: function(state) {
var self = this;
var cmds = [
['Connect'],
['Authenticate', this.options.login,this.options.password],
['GetStatus'],
['GetPlayerList',500,0],
['GetServerOptions'],
['GetCurrentChallengeInfo'],
['GetCurrentGameInfo']
];
var results = [];
async.eachSeries(cmds, function(cmdset,c) {
var cmd = cmdset[0];
var params = cmdset.slice(1);
if(cmd == 'Connect') {
var client = self.gbxclient = gbxremote.createClient(self.options.port,self.options.host, function(err) {
if(err) return self.error('GBX error '+JSON.stringify(err));
c();
});
client.on('error',function(){});
} else {
self.gbxclient.methodCall(cmd, params, function(err, value) {
if(err) return self.error('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";
state.name = self.stripColors(results[3].Name);
state.password = (results[3].Password != 'No password');
state.maxplayers = results[3].CurrentMaxPlayers;
state.map = self.stripColors(results[4].Name);
state.raw.gametype = gamemode;
results[2].forEach(function(player) {
state.players.push({name:self.stripColors(player.Name)});
});
self.finish(state);
});
},
stripColors: function(str) {
return str.replace(/\$([0-9a-f][^\$]?[^\$]?|[^\$]?)/g,'');
}
});

View file

@ -1,65 +0,0 @@
module.exports = require('./core').extend({
init: function() {
this._super();
this.pretty = 'Quake 2';
this.options.port = 27910;
this.encoding = 'latin1';
this.delimiter = '\n';
this.sendHeader = 'status';
this.responseHeader = 'print';
},
run: function(state) {
var self = this;
this.udpSend('\xff\xff\xff\xff'+this.sendHeader+'\x00',function(buffer) {
var reader = self.reader(buffer);
var header = reader.string();
if(header != '\xff\xff\xff\xff'+this.responseHeader) return;
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.raw[key] = value;
}
while(!reader.done()) {
var player = reader.string();
var args = [];
var split = player.split('"');
var inQuote = false;
split.forEach(function(part,i) {
var inQuote = (i%2 == 1);
if(inQuote) {
args.push(part);
} else {
var splitSpace = part.split(' ');
splitSpace.forEach(function(subpart) {
if(subpart) args.push(subpart);
});
}
});
var frags = parseInt(args[0]);
var ping = parseInt(args[1]);
var name = args[2] || '';
var address = args[3] || '';
(ping == 0 ? state.bots : state.players).push({
frags:frags, ping:ping, name:name, address:address
});
}
if('g_needpass' in state.raw) state.password = state.raw.g_needpass;
if('mapname' in state.raw) state.map = state.raw.mapname;
if('sv_maxclients' in state.raw) state.maxplayers = state.raw.sv_maxclients;
if('sv_hostname' in state.raw) state.name = state.raw.sv_hostname;
self.finish(state);
return true;
});
}
});

View file

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

View file

@ -1,208 +0,0 @@
var async = require('async'),
Bzip2 = require('compressjs').Bzip2;
module.exports = require('./core').extend({
init: function() {
this._super();
this.goldsrc = false;
this.options.port = 27015;
},
run: function(state) {
var self = this;
var challenge;
async.series([
function(c) {
self.sendPacket(
0x54,false,new Buffer('Source Engine Query\0'),
self.goldsrc ? 0x6D : 0x49,
function(b) {
var reader = self.reader(b);
if(self.goldsrc) state.raw.address = reader.string();
else state.raw.protocol = reader.uint(1);
state.name = reader.string();
state.map = reader.string();
state.raw.folder = reader.string();
state.raw.game = reader.string();
state.raw.steamappid = reader.uint(2);
state.raw.numplayers = reader.uint(1);
state.maxplayers = reader.uint(1);
if(self.goldsrc) state.raw.protocol = reader.uint(1);
else state.raw.numbots = reader.uint(1);
state.raw.listentype = String.fromCharCode(reader.uint(1));
state.raw.environment = String.fromCharCode(reader.uint(1));
state.password = reader.uint(1);
if(self.goldsrc) {
state.raw.ismod = reader.uint(1);
if(state.raw.ismod) {
state.raw.modlink = reader.string();
state.raw.moddownload = reader.string();
reader.skip(1);
state.raw.modversion = reader.uint(4);
state.raw.modsize = reader.uint(4);
state.raw.modtype = reader.uint(1);
state.raw.moddll = reader.uint(1);
}
}
state.raw.secure = reader.uint(1);
if(self.goldsrc) {
state.raw.numbots = reader.uint(1);
} else {
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);
if(extraFlag & 0x80) state.raw.port = reader.uint(2);
if(extraFlag & 0x10) state.raw.steamid = reader.uint(8);
if(extraFlag & 0x40) {
state.raw.sourcetvport = reader.uint(2);
state.raw.sourcetvname = reader.string();
}
if(extraFlag & 0x20) state.raw.tags = reader.string();
if(extraFlag & 0x01) state.raw.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);
for(var i = 0; i < num; i++) {
reader.skip(1);
var name = reader.string();
var score = reader.uint(4);
var time = reader.float();
// connecting players don't could as players.
if(!name) continue;
(time == -1 ? state.bots : state.players).push({
name:name, score:score, time:time
});
}
// if we didn't find the bots, iterate
// through and guess which ones they are
if(!state.bots.length) {
var maxTime = 0;
state.players.forEach(function(player) {
maxTime = Math.max(player.time,maxTime);
});
for(var i = 0; i < state.players.length; i++) {
var player = state.players[i];
if(state.bots.length >= state.raw.numbots) continue;
if(player.time != maxTime) continue;
state.bots.push(player);
state.players.splice(i, 1);
i--;
}
}
c();
});
},
function(c) {
self.sendPacket(0x56,challenge,false,0x45,function(b) {
var reader = self.reader(b);
var num = reader.uint(2);
state.raw.rules = [];
for(var i = 0; i < num; i++) {
var key = reader.string();
var value = reader.string();
state.raw.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 numPackets = 0;
var packets = [];
var bzip = false;
this.udpSend(b,function(buffer) {
var header = buffer.readInt32LE(0);
if(header == -1) {
// full package
return received(buffer.slice(4));
}
if(header == -2) {
// partial package
var uid = buffer.readUInt32LE(4);
if(!self.goldsrc && uid & 0x80000000) bzip = true;
var id,payload;
if(self.goldsrc) {
id = buffer.readUInt8(8);
numPackets = id & 0x0f;
id = id & 0xf0 >> 4;
payload = buffer.slice(9);
} else {
numPackets = buffer.readUInt8(8);
id = buffer.readUInt8(9);
if(id == 0 && bzip) payload = buffer.slice(20);
else payload = buffer.slice(12);
}
packets[id] = payload;
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]);
}
var assembled = Buffer.concat(list);
var payload = assembled.slice(4);
if(bzip) payload = Bzip2.uncompressFile(payload);
return received(payload);
}
});
}
});

View file

@ -1,40 +0,0 @@
var request = require('request');
module.exports = require('./core').extend({
init: function() {
this._super();
this.pretty = 'Terraria';
this.options.port = 7878;
},
run: function(state) {
var self = this;
request({
uri: 'http://'+this.options.address+':'+this.options.port+'/v2/server/status',
timeout: 3000,
qs: {
players: 'true',
token: this.options.token
}
}, function(e,r,body) {
if(e) return self.error('HTTP error');
var json;
try {
json = JSON.parse(body);
} catch(e) {
return self.error('Invalid JSON');
}
if(json.status != 200) return self.error('Invalid status');
json.players.forEach(function(one) {
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);
});
}
});

View file

@ -1,82 +0,0 @@
var async = require('async');
module.exports = require('./core').extend({
init: function() {
this._super();
this.encoding = 'latin1';
},
run: function(state) {
var self = this;
async.series([
function(c) {
self.sendPacket(0,true,function(b) {
var reader = self.reader(b);
state.raw.serverid = reader.uint(4);
state.raw.ip = reader.pascal();
state.raw.port = reader.uint(4);
state.raw.queryport = reader.uint(4);
state.name = reader.pascal();
state.map = reader.pascal();
state.raw.gametype = reader.pascal();
state.raw.numplayers = reader.uint(4);
state.maxplayers = reader.uint(4);
state.raw.ping = reader.uint(4);
c();
});
},
function(c) {
self.sendPacket(1,true,function(b) {
var reader = self.reader(b);
state.raw.mutators = [];
state.raw.rules = {};
while(!reader.done()) {
var key = reader.pascal();
var value = reader.pascal();
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';
c();
});
},
function(c) {
self.sendPacket(2,false,function(b) {
var reader = self.reader(b);
while(!reader.done()) {
var id = reader.uint(4);
var name = reader.pascal();
var ping = reader.uint(4);
var score = reader.uint(4);
reader.skip(4);
(ping == 0 ? state.bots : 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;
});
}
});

View file

@ -1,7 +0,0 @@
module.exports = require('./unreal2').extend({
init: function() {
this._super();
this.pretty = 'Unreal Tournament 2004';
this.options.port = 7778;
}
});

View file

@ -1,51 +0,0 @@
module.exports = require('./gamespy3').extend({
init: function() {
this._super();
this.pretty = 'Unreal Tournament 3';
this.options.port = 6500;
},
finalizeState: function(state) {
this._super(state);
this.translate(state.raw,{
'mapname': false,
'p1073741825': 'map',
'p1073741826': 'gametype',
'p1073741827': 'servername',
'p1073741828': 'custom_mutators',
'gamemode': 'joininprogress',
's32779': 'gamemode',
's0': 'bot_skill',
's6': 'pure_server',
's7': 'password',
's8': 'vs_bots',
's10': 'force_respawn',
'p268435704': 'frag_limit',
'p268435705': 'time_limit',
'p268435703': 'numbots',
'p268435717': 'stock_mutators',
'p1073741829': 'stock_mutators',
's1': false,
's9': false,
's11': false,
's12': false,
's13': false,
's14': false,
'p268435706': false,
'p268435968': false,
'p268435969': false
});
function split(a) {
var s = a.split('\x1c');
s = s.filter(function(e) { return e });
return s;
}
if('custom_mutators' in state) state['custom_mutators'] = split(state['custom_mutators']);
if('stock_mutators' in state) state['stock_mutators'] = split(state['stock_mutators']);
if('map' in state.raw) state.map = state.raw.map;
if('password' in state.raw) state.password = state.raw.password;
if('servername' in state.raw) state.name = state.raw.servername;
}
});

View file

@ -1,17 +0,0 @@
module.exports = require('./quake3').extend({
init: function() {
this._super();
this.pretty = 'Warsow';
this.options.port = 44400;
},
prepState: function(state) {
this._super(state);
if(state.players) {
for(var i = 0; i < state.players.length; i++) {
var player = state.players[i];
player.team = player.address;
delete player.address;
}
}
}
});