mirror of
https://github.com/tribufu/node-gamedig
synced 2026-06-01 09:42:41 +00:00
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:
parent
a89fb7bbdf
commit
c82554ad1a
31 changed files with 573 additions and 135 deletions
29
games/aliases.txt
Normal file
29
games/aliases.txt
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# id | pretty | protocol | port?
|
||||
|
||||
alienswarm|Alien Swarm|valve
|
||||
csgo|Counter-Strike: Global Offensive|valve
|
||||
css|Counter-Strike: Source|valve
|
||||
cs16|Counter-Strike 1.6|valvegold
|
||||
dinodday|Dino D-Day|valve
|
||||
garrysmod|Garry's Mod|valve
|
||||
hidden|The Hidden: Source|valve
|
||||
kzmod|KzMod|valve
|
||||
left4dead|Left 4 Dead|valve
|
||||
left4dead2|Left 4 Dead 2|valve
|
||||
nmrih|No More Room in Hell|valve
|
||||
ns|Natural Selection|valvegold
|
||||
ns2|Natural Selection 2|valve|27016
|
||||
nucleardawn|Nuclear Dawn|valve
|
||||
quake2|Quake 2|quake2
|
||||
quake3|Quake 3|quake3
|
||||
ricochet|Ricochet|valvegold
|
||||
rust|Rust|valve|28016
|
||||
ship|The Ship|valve
|
||||
shootmania|Shootmania|nadeo
|
||||
starbound|Starbound|valve
|
||||
suicidesurvival|Suicide Survival|valve
|
||||
svencoop|Sven Coop|valvegold
|
||||
synergy|Synergy|valve
|
||||
tf2|Team Fortress 2|valve
|
||||
trackmania2|Trackmania 2|nadeo
|
||||
trackmaniaforever|Trackmania Forever|nadeo
|
||||
66
games/armagetron.js
Normal file
66
games/armagetron.js
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
module.exports = require('./protocols/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.stripColorCodes(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:self.stripColorCodes(list[i])
|
||||
});
|
||||
}
|
||||
|
||||
state.raw.options = self.stripColorCodes(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);
|
||||
}
|
||||
|
||||
return out;
|
||||
},
|
||||
stripColorCodes: function(str) {
|
||||
return str.replace(/0x[0-9a-f]{6}/g,'');
|
||||
}
|
||||
});
|
||||
60
games/buildandshoot.js
Normal file
60
games/buildandshoot.js
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
var request = require('request');
|
||||
|
||||
module.exports = require('./protocols/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);
|
||||
});
|
||||
}
|
||||
});
|
||||
30
games/jcmp.js
Normal file
30
games/jcmp.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
module.exports = require('./protocols/valve').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.options.port = 7777;
|
||||
this.pretty = 'Just Cause 2 Multiplayer';
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
// supposedly, gamespy3 is the "official" query protocol for jcmp,
|
||||
// but it's broken (requires singlePacketSplits), and doesn't include player names
|
||||
module.exports = require('./protocols/gamespy3').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.options.port = 7777;
|
||||
this.pretty = 'Just Cause 2 Multiplayer';
|
||||
this._singlePacketSplits = true;
|
||||
},
|
||||
finalizeState: function(state) {
|
||||
this._super(state);
|
||||
console.log(state.players.length);
|
||||
console.log(state.raw.numplayers);
|
||||
if(!state.players.length && parseInt(state.raw.numplayers)) {
|
||||
for(var i = 0; i < parseInt(state.raw.numplayers); i++) {
|
||||
state.players.push({});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
13
games/killingfloor.js
Normal file
13
games/killingfloor.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
module.exports = require('./protocols/unreal2').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.options.port = 7708;
|
||||
this.pretty = 'Killing Floor';
|
||||
},
|
||||
readExtraInfo: function(reader,state) {
|
||||
state.raw.numplayers = reader.uint(4);
|
||||
state.maxplayers = reader.uint(4);
|
||||
state.raw.wavecurrent = reader.uint(4);
|
||||
state.raw.wavetotal = reader.uint(4);
|
||||
}
|
||||
});
|
||||
34
games/minecraft.js
Normal file
34
games/minecraft.js
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
var dns = require('dns');
|
||||
|
||||
module.exports = require('./protocols/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);
|
||||
});
|
||||
}
|
||||
});
|
||||
127
games/minecraftping.js
Normal file
127
games/minecraftping.js
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
var dns = require('dns'),
|
||||
net = require('net'),
|
||||
varint = require('varint');
|
||||
|
||||
function varIntBuffer(num) {
|
||||
return new Buffer(varint.encode(num));
|
||||
}
|
||||
|
||||
module.exports = require('./protocols/core').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.pretty = 'Minecraft';
|
||||
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);
|
||||
});
|
||||
},
|
||||
reset: function() {
|
||||
this._super();
|
||||
if(this.socket) {
|
||||
this.socket.destroy();
|
||||
delete this.socket;
|
||||
}
|
||||
},
|
||||
run: function(state) {
|
||||
var self = this;
|
||||
|
||||
var socket = this.socket = net.connect(
|
||||
this.options.port,
|
||||
this.options.address,
|
||||
function() {
|
||||
|
||||
var portBuf = new Buffer(2);
|
||||
portBuf.writeUInt16BE(self.options.port,0);
|
||||
|
||||
var addressBuf = new Buffer(self.options.address,'utf8');
|
||||
|
||||
var bufs = [
|
||||
varIntBuffer(4),
|
||||
varIntBuffer(addressBuf.length),
|
||||
addressBuf,
|
||||
portBuf,
|
||||
varIntBuffer(1)
|
||||
];
|
||||
self.sendPacket(0,Buffer.concat(bufs));
|
||||
self.sendPacket(0);
|
||||
});
|
||||
socket.setTimeout(10000);
|
||||
socket.setNoDelay(true);
|
||||
|
||||
var received = new Buffer(0);
|
||||
var expectedBytes = 0;
|
||||
socket.on('data', function(data) {
|
||||
received = Buffer.concat([received,data]);
|
||||
if(expectedBytes) {
|
||||
if(received.length >= expectedBytes) {
|
||||
self.allReceived(received,state);
|
||||
}
|
||||
} else if(received.length > 10) {
|
||||
expectedBytes = varint.decode(received);
|
||||
received = received.slice(varint.decode.bytesRead);
|
||||
}
|
||||
});
|
||||
},
|
||||
sendPacket: function(id,data) {
|
||||
if(!data) data = new Buffer(0);
|
||||
var idBuffer = varIntBuffer(id);
|
||||
var out = Buffer.concat([
|
||||
varIntBuffer(data.length+idBuffer.length),
|
||||
idBuffer,
|
||||
data
|
||||
]);
|
||||
this.socket.write(out);
|
||||
},
|
||||
allReceived: function(received,state) {
|
||||
var packetId = varint.decode(received);
|
||||
received = received.slice(varint.decode.bytesRead);
|
||||
|
||||
var strLen = varint.decode(received);
|
||||
received = received.slice(varint.decode.bytesRead);
|
||||
|
||||
var str = received.toString('utf8');
|
||||
var json;
|
||||
try {
|
||||
json = JSON.parse(str);
|
||||
delete json.favicon;
|
||||
} catch(e) {
|
||||
return this.fatal('Invalid JSON');
|
||||
}
|
||||
|
||||
state.raw.version = json.version.name;
|
||||
state.maxplayers = json.players.max;
|
||||
state.raw.description = json.description.text;
|
||||
for(var i = 0; i < json.players.sample.length; i++) {
|
||||
state.players.push({
|
||||
id: json.players.sample[i].id,
|
||||
name: json.players.sample[i].name
|
||||
});
|
||||
}
|
||||
while(state.players.length < json.players.online) {
|
||||
state.players.push({});
|
||||
}
|
||||
|
||||
this.finish(state);
|
||||
}
|
||||
});
|
||||
59
games/mutantfactions.js
Normal file
59
games/mutantfactions.js
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
var request = require('request');
|
||||
|
||||
module.exports = require('./protocols/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 == self.options.address && port == self.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);
|
||||
});
|
||||
}
|
||||
});
|
||||
174
games/protocols/core.js
Normal file
174
games/protocols/core.js
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
var EventEmitter = require('events').EventEmitter,
|
||||
dns = require('dns'),
|
||||
async = require('async'),
|
||||
Class = require('../../lib/Class'),
|
||||
Reader = require('../../lib/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');
|
||||
|
||||
if(this.debug) console.log("Sent",buffer,this.options.address,this.options.port);
|
||||
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() {}
|
||||
});
|
||||
122
games/protocols/gamespy3.js
Normal file
122
games/protocols/gamespy3.js
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
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();
|
||||
|
||||
// reread the next line if we hit the weird ut3 bug
|
||||
if(value == 'p1073741829') value = reader.string();
|
||||
|
||||
state.raw[key] = value;
|
||||
}
|
||||
|
||||
while(!reader.done()) {
|
||||
var mode = reader.string();
|
||||
if(mode.charCodeAt(0) <= 2) mode = mode.substring(1);
|
||||
if(!mode) continue;
|
||||
var offset = 0;
|
||||
reader.skip(1);
|
||||
|
||||
while(!reader.done()) {
|
||||
var item = reader.string();
|
||||
if(!item) break;
|
||||
|
||||
if(
|
||||
mode == 'player_'
|
||||
|| mode == 'score_'
|
||||
|| mode == 'ping_'
|
||||
|| mode == 'team_'
|
||||
|| mode == 'deaths_'
|
||||
|| mode == 'pid_'
|
||||
) {
|
||||
if(state.players.length <= offset)
|
||||
state.players.push({});
|
||||
}
|
||||
if(mode == 'player_') state.players[offset].name = item;
|
||||
if(mode == 'score_') state.players[offset].score = item;
|
||||
if(mode == 'ping_') state.players[offset].ping = item;
|
||||
if(mode == 'team_') state.players[offset].team = item;
|
||||
if(mode == 'deaths_') state.players[offset].deaths = item;
|
||||
if(mode == 'pid_') state.players[offset].pid = item;
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
|
||||
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 = parseInt(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 || self._singlePacketSplits) 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;
|
||||
});
|
||||
}
|
||||
});
|
||||
74
games/protocols/nadeo.js
Normal file
74
games/protocols/nadeo.js
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
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,'');
|
||||
}
|
||||
});
|
||||
65
games/protocols/quake2.js
Normal file
65
games/protocols/quake2.js
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
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;
|
||||
});
|
||||
}
|
||||
});
|
||||
21
games/protocols/quake3.js
Normal file
21
games/protocols/quake3.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
module.exports = require('./quake2').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.pretty = 'Quake 3';
|
||||
this.options.port = 27960;
|
||||
this.sendHeader = 'getstatus';
|
||||
this.responseHeader = 'statusResponse';
|
||||
},
|
||||
finalizeState: function(state) {
|
||||
state.name = this.stripColors(state.name);
|
||||
for(var i in state.raw) {
|
||||
state.raw[i] = this.stripColors(state.raw[i]);
|
||||
}
|
||||
for(var i = 0; i < state.players.length; i++) {
|
||||
state.players[i].name = this.stripColors(state.players[i].name);
|
||||
}
|
||||
},
|
||||
stripColors: function(str) {
|
||||
return str.replace(/\^(X.{6}|.)/g,'');
|
||||
}
|
||||
});
|
||||
110
games/protocols/unreal2.js
Normal file
110
games/protocols/unreal2.js
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
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 = self.readUnrealString(reader);
|
||||
state.raw.port = reader.uint(4);
|
||||
state.raw.queryport = reader.uint(4);
|
||||
state.name = self.readUnrealString(reader,true);
|
||||
self.readUnrealString(reader); // unknown?
|
||||
state.map = self.readUnrealString(reader,true);
|
||||
state.raw.gametype = self.readUnrealString(reader,true);
|
||||
self.readExtraInfo(reader,state);
|
||||
|
||||
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 = self.readUnrealString(reader,true);
|
||||
var value = self.readUnrealString(reader,true);
|
||||
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 = self.readUnrealString(reader,true);
|
||||
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);
|
||||
}
|
||||
]);
|
||||
},
|
||||
readExtraInfo: function(reader,state) {
|
||||
if(this.debug) {
|
||||
console.log("UNREAL2 EXTRA INFO:");
|
||||
console.log(reader.uint(4));
|
||||
console.log(reader.uint(4));
|
||||
console.log(reader.uint(4));
|
||||
console.log(reader.uint(4));
|
||||
console.log(reader.buffer.slice(reader.i));
|
||||
}
|
||||
},
|
||||
readUnrealString: function(reader, stripColor) {
|
||||
var length = reader.uint(1);
|
||||
var out;
|
||||
if(length < 0x80) {
|
||||
out = reader.string({length:length});
|
||||
} else {
|
||||
length = (length&0x7f)*2;
|
||||
out = length+reader.string({encoding:'ucs2',length:length});
|
||||
}
|
||||
|
||||
if(out.charCodeAt(out.length-1) == 0)
|
||||
out = out.substring(0,out.length-1);
|
||||
|
||||
if(stripColor)
|
||||
out = out.replace(/\x1b...|[\x00-\x1a]/g,'');
|
||||
|
||||
return out;
|
||||
},
|
||||
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;
|
||||
});
|
||||
}
|
||||
});
|
||||
233
games/protocols/valve.js
Normal file
233
games/protocols/valve.js
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
var async = require('async'),
|
||||
Bzip2 = require('compressjs').Bzip2;
|
||||
|
||||
module.exports = require('./core').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.goldsrc = false;
|
||||
this.options.port = 27015;
|
||||
|
||||
// 2006 engines don't pass packet switching size in split packet header
|
||||
// while all others do
|
||||
this._skipSizeInSplitHeader = false;
|
||||
},
|
||||
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 = reader.uint(1);
|
||||
state.raw.environment = reader.uint(1);
|
||||
if(!self.goldsrc) {
|
||||
state.raw.listentype = String.fromCharCode(state.raw.listentype);
|
||||
state.raw.environment = String.fromCharCode(state.raw.environment);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if(state.raw.protocol == 7 && state.raw.steamappid == 215) {
|
||||
self._skipSizeInSplitHeader = true;
|
||||
}
|
||||
|
||||
c();
|
||||
}
|
||||
);
|
||||
},
|
||||
function(c) {
|
||||
self.sendPacket(self.goldsrc?0x56: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.int(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() {
|
||||
// no rules were returned after timeout --
|
||||
// the server probably has them disabled
|
||||
// ignore the timeout
|
||||
c();
|
||||
return true;
|
||||
});
|
||||
},
|
||||
function(c) {
|
||||
self.finish(state);
|
||||
}
|
||||
]);
|
||||
},
|
||||
sendPacket: function(type,challenge,payload,expect,callback,ontimeout) {
|
||||
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(self.debug) console.log("Received "+type+" expected "+expect);
|
||||
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);
|
||||
var sizeOffset = self._skipSizeInSplitHeader ? 0 : 2;
|
||||
if(id == 0 && bzip) payload = buffer.slice(18+sizeOffset);
|
||||
else payload = buffer.slice(10+sizeOffset);
|
||||
}
|
||||
|
||||
packets[id] = payload;
|
||||
|
||||
if(self.debug) {
|
||||
console.log("Received partial packet id: "+id);
|
||||
console.log("Expecting "+numPackets+" packets, have "+Object.keys(packets).length);
|
||||
}
|
||||
|
||||
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);
|
||||
if(bzip) assembled = new Buffer(Bzip2.decompressFile(assembled));
|
||||
|
||||
return received(assembled.slice(4));
|
||||
}
|
||||
},ontimeout);
|
||||
}
|
||||
});
|
||||
6
games/protocols/valvegold.js
Normal file
6
games/protocols/valvegold.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
module.exports = require('./valve').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.goldsrc = true;
|
||||
}
|
||||
});
|
||||
40
games/terraria.js
Normal file
40
games/terraria.js
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
var request = require('request');
|
||||
|
||||
module.exports = require('./protocols/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);
|
||||
});
|
||||
}
|
||||
});
|
||||
12
games/ut2004.js
Normal file
12
games/ut2004.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
module.exports = require('./protocols/unreal2').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.options.port = 7778;
|
||||
this.pretty = 'Unreal Tournament 2004';
|
||||
},
|
||||
readExtraInfo: function(reader,state) {
|
||||
reader.skip(18);
|
||||
state.raw.numplayers = reader.uint(4);
|
||||
state.maxplayers = reader.uint(4);
|
||||
}
|
||||
});
|
||||
51
games/ut3.js
Normal file
51
games/ut3.js
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
module.exports = require('./protocols/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;
|
||||
}
|
||||
});
|
||||
17
games/warsow.js
Normal file
17
games/warsow.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
module.exports = require('./protocols/quake3').extend({
|
||||
init: function() {
|
||||
this._super();
|
||||
this.pretty = 'Warsow';
|
||||
this.options.port = 44400;
|
||||
},
|
||||
finalizeState: 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue