Convert tabs to spaces

This commit is contained in:
mmorrison 2017-08-09 05:32:09 -05:00
parent 0dd25bfcda
commit 3674d384d0
39 changed files with 2414 additions and 2414 deletions

View File

@ -11,27 +11,27 @@ delete argv.output;
const options = {};
for(const key of Object.keys(argv)) {
const value = argv[key];
if(
key === '_'
|| key.charAt(0) === '$'
|| (typeof value !== 'string' && typeof value !== 'number')
)
continue;
options[key] = value;
if(
key === '_'
|| key.charAt(0) === '$'
|| (typeof value !== 'string' && typeof value !== 'number')
)
continue;
options[key] = value;
}
if(debug) Gamedig.debug = true;
Gamedig.isCommandLine = true;
Gamedig.query(options)
.then((state) => {
if(outputFormat === 'pretty') {
console.log(JSON.stringify(state,null,' '));
} else {
console.log(JSON.stringify(state));
}
})
.catch((error) => {
.then((state) => {
if(outputFormat === 'pretty') {
console.log(JSON.stringify(state,null,' '));
} else {
console.log(JSON.stringify(state));
}
})
.catch((error) => {
if (debug) {
if (error instanceof Error) {
console.log(error.stack);
@ -48,4 +48,4 @@ Gamedig.query(options)
console.log(JSON.stringify({error: error}));
}
}
});
});

View File

@ -7,94 +7,94 @@ const udpSocket = dgram.createSocket('udp4');
udpSocket.unref();
udpSocket.bind(21943);
udpSocket.on('message', (buffer, rinfo) => {
if(Gamedig.debug) console.log(rinfo.address+':'+rinfo.port+" <--UDP "+buffer.toString('hex'));
for(const query of activeQueries) {
if(
query.options.address !== rinfo.address
&& query.options.altaddress !== rinfo.address
) continue;
if(query.options.port_query !== rinfo.port) continue;
query._udpResponse(buffer);
break;
}
if(Gamedig.debug) console.log(rinfo.address+':'+rinfo.port+" <--UDP "+buffer.toString('hex'));
for(const query of activeQueries) {
if(
query.options.address !== rinfo.address
&& query.options.altaddress !== rinfo.address
) continue;
if(query.options.port_query !== rinfo.port) continue;
query._udpResponse(buffer);
break;
}
});
udpSocket.on('error', (e) => {
if(Gamedig.debug) console.log("UDP ERROR: "+e);
if(Gamedig.debug) console.log("UDP ERROR: "+e);
});
class Gamedig {
static query(options,callback) {
const promise = new Promise((resolve,reject) => {
static query(options,callback) {
const promise = new Promise((resolve,reject) => {
for (const key of Object.keys(options)) {
if (['port_query', 'port'].includes(key)) {
options[key] = parseInt(options[key]);
}
}
options.callback = (state) => {
if (state.error) reject(state.error);
else resolve(state);
};
let query;
try {
query = TypeResolver.lookup(options.type);
} catch(e) {
process.nextTick(() => {
options.callback({error:e});
});
return;
}
query.debug = Gamedig.debug;
query.udpSocket = udpSocket;
query.type = options.type;
if(!('port' in query.options) && ('port_query' in query.options)) {
if(Gamedig.isCommandLine) {
process.stderr.write(
"Warning! This game is so old, that we don't know"
+" what the server's connection port is. We've guessed that"
+" the query port for "+query.type+" is "+query.options.port_query+"."
+" If you know the connection port for this type of server, please let"
+" us know on the GameDig issue tracker, thanks!\n"
);
}
query.options.port = query.options.port_query;
delete query.options.port_query;
}
// copy over options
for(const key of Object.keys(options)) {
query.options[key] = options[key];
if (['port_query', 'port'].includes(key)) {
options[key] = parseInt(options[key]);
}
}
activeQueries.push(query);
options.callback = (state) => {
if (state.error) reject(state.error);
else resolve(state);
};
query.on('finished',() => {
const i = activeQueries.indexOf(query);
if(i >= 0) activeQueries.splice(i, 1);
});
let query;
try {
query = TypeResolver.lookup(options.type);
} catch(e) {
process.nextTick(() => {
options.callback({error:e});
});
return;
}
query.debug = Gamedig.debug;
query.udpSocket = udpSocket;
query.type = options.type;
process.nextTick(() => {
query.start();
});
});
if(!('port' in query.options) && ('port_query' in query.options)) {
if(Gamedig.isCommandLine) {
process.stderr.write(
"Warning! This game is so old, that we don't know"
+" what the server's connection port is. We've guessed that"
+" the query port for "+query.type+" is "+query.options.port_query+"."
+" If you know the connection port for this type of server, please let"
+" us know on the GameDig issue tracker, thanks!\n"
);
}
query.options.port = query.options.port_query;
delete query.options.port_query;
}
if (callback && callback instanceof Function) {
if(callback.length === 2) {
promise
.then((state) => callback(null,state))
.catch((error) => callback(error));
} else if (callback.length === 1) {
promise
.then((state) => callback(state))
.catch((error) => callback({error:error}));
}
}
// copy over options
for(const key of Object.keys(options)) {
query.options[key] = options[key];
}
return promise;
}
activeQueries.push(query);
query.on('finished',() => {
const i = activeQueries.indexOf(query);
if(i >= 0) activeQueries.splice(i, 1);
});
process.nextTick(() => {
query.start();
});
});
if (callback && callback instanceof Function) {
if(callback.length === 2) {
promise
.then((state) => callback(null,state))
.catch((error) => callback(error));
} else if (callback.length === 1) {
promise
.then((state) => callback(state))
.catch((error) => callback({error:error}));
}
}
return promise;
}
}

View File

@ -1,147 +1,147 @@
const Iconv = require('iconv-lite'),
Long = require('long');
Long = require('long');
function readUInt64BE(buffer,offset) {
const high = buffer.readUInt32BE(offset);
const low = buffer.readUInt32BE(offset+4);
return new Long(low,high,true);
return new Long(low,high,true);
}
function readUInt64LE(buffer,offset) {
const low = buffer.readUInt32LE(offset);
const high = buffer.readUInt32LE(offset+4);
return new Long(low,high,true);
return new Long(low,high,true);
}
class Reader {
constructor(query,buffer) {
this.query = query;
this.buffer = buffer;
this.i = 0;
}
constructor(query,buffer) {
this.query = query;
this.buffer = buffer;
this.i = 0;
}
offset() {
return this.i;
}
offset() {
return this.i;
}
skip(i) {
this.i += i;
}
skip(i) {
this.i += i;
}
string(...args) {
string(...args) {
let 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];
}
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 = 'win1252';
options.encoding = options.encoding || this.query.encoding;
if(options.encoding === 'latin1') options.encoding = 'win1252';
const start = this.i+0;
let end = start;
if(!('length' in options)) {
// terminated by the delimiter
let delim = options.delimiter || this.query.delimiter;
if(typeof delim === 'string') delim = delim.charCodeAt(0);
while(true) {
if(end >= this.buffer.length) {
end = this.buffer.length;
break;
}
if(this.buffer.readUInt8(end) === delim) break;
end++;
}
this.i = end+1;
} else {
end = start+options.length;
if(end >= this.buffer.length) {
end = this.buffer.length;
}
this.i = end;
}
const start = this.i+0;
let end = start;
if(!('length' in options)) {
// terminated by the delimiter
let delim = options.delimiter || this.query.delimiter;
if(typeof delim === 'string') delim = delim.charCodeAt(0);
while(true) {
if(end >= this.buffer.length) {
end = this.buffer.length;
break;
}
if(this.buffer.readUInt8(end) === delim) break;
end++;
}
this.i = end+1;
} else {
end = start+options.length;
if(end >= this.buffer.length) {
end = this.buffer.length;
}
this.i = end;
}
let out = this.buffer.slice(start, end);
const enc = options.encoding;
if(enc === 'utf8' || enc === 'ucs2' || enc === 'binary') {
out = out.toString(enc);
} else {
out = Iconv.decode(out,enc);
}
return out;
}
let out = this.buffer.slice(start, end);
const enc = options.encoding;
if(enc === 'utf8' || enc === 'ucs2' || enc === 'binary') {
out = out.toString(enc);
} else {
out = Iconv.decode(out,enc);
}
return out;
}
int(bytes) {
let r = 0;
if(this.remaining() >= bytes) {
if(this.query.byteorder === 'be') {
if(bytes === 1) r = this.buffer.readInt8(this.i);
else if(bytes === 2) r = this.buffer.readInt16BE(this.i);
else if(bytes === 4) r = this.buffer.readInt32BE(this.i);
} else {
if(bytes === 1) r = this.buffer.readInt8(this.i);
else if(bytes === 2) r = this.buffer.readInt16LE(this.i);
else if(bytes === 4) r = this.buffer.readInt32LE(this.i);
}
}
this.i += bytes;
return r;
}
int(bytes) {
let r = 0;
if(this.remaining() >= bytes) {
if(this.query.byteorder === 'be') {
if(bytes === 1) r = this.buffer.readInt8(this.i);
else if(bytes === 2) r = this.buffer.readInt16BE(this.i);
else if(bytes === 4) r = this.buffer.readInt32BE(this.i);
} else {
if(bytes === 1) r = this.buffer.readInt8(this.i);
else if(bytes === 2) r = this.buffer.readInt16LE(this.i);
else if(bytes === 4) r = this.buffer.readInt32LE(this.i);
}
}
this.i += bytes;
return r;
}
/** @returns {number} */
uint(bytes) {
let r = 0;
if(this.remaining() >= bytes) {
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 = readUInt64BE(this.buffer,this.i).toString();
} 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 = readUInt64LE(this.buffer,this.i).toString();
}
}
this.i += bytes;
return r;
}
/** @returns {number} */
uint(bytes) {
let r = 0;
if(this.remaining() >= bytes) {
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 = readUInt64BE(this.buffer,this.i).toString();
} 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 = readUInt64LE(this.buffer,this.i).toString();
}
}
this.i += bytes;
return r;
}
float() {
let r = 0;
if(this.remaining() >= 4) {
if(this.query.byteorder === 'be') r = this.buffer.readFloatBE(this.i);
else r = this.buffer.readFloatLE(this.i);
}
this.i += 4;
return r;
}
float() {
let r = 0;
if(this.remaining() >= 4) {
if(this.query.byteorder === 'be') r = this.buffer.readFloatBE(this.i);
else r = this.buffer.readFloatLE(this.i);
}
this.i += 4;
return r;
}
part(bytes) {
let r;
if(this.remaining() >= bytes) {
r = this.buffer.slice(this.i,this.i+bytes);
} else {
r = Buffer.from([]);
}
this.i += bytes;
return r;
}
part(bytes) {
let r;
if(this.remaining() >= bytes) {
r = this.buffer.slice(this.i,this.i+bytes);
} else {
r = Buffer.from([]);
}
this.i += bytes;
return r;
}
remaining() {
return this.buffer.length-this.i;
}
remaining() {
return this.buffer.length-this.i;
}
rest() {
return this.buffer.slice(this.i);
}
rest() {
return this.buffer.slice(this.i);
}
done() {
return this.i >= this.buffer.length;
}
done() {
return this.i >= this.buffer.length;
}
}
module.exports = Reader;

View File

@ -1,94 +1,94 @@
const Path = require('path'),
fs = require('fs');
fs = require('fs');
const protocolDir = Path.normalize(__dirname+'/../protocols');
const gamesFile = Path.normalize(__dirname+'/../games.txt');
function parseList(str) {
if(!str) return {};
if(!str) return {};
const out = {};
for (const one of str.split(',')) {
for (const one of str.split(',')) {
const equals = one.indexOf('=');
const key = equals === -1 ? one : one.substr(0,equals);
let value = equals === -1 ? '' : one.substr(equals+1);
let value = equals === -1 ? '' : one.substr(equals+1);
if(value === 'true' || value === '') value = true;
else if(value === 'false') value = false;
else if(!isNaN(value)) value = parseInt(value);
if(value === 'true' || value === '') value = true;
else if(value === 'false') value = false;
else if(!isNaN(value)) value = parseInt(value);
out[key] = value;
}
return out;
out[key] = value;
}
return out;
}
function readGames() {
const lines = fs.readFileSync(gamesFile,'utf8').split('\n');
const games = {};
for (let line of lines) {
// strip comments
const comment = line.indexOf('#');
if(comment !== -1) line = line.substr(0,comment);
line = line.trim();
if(!line) continue;
for (let line of lines) {
// strip comments
const comment = line.indexOf('#');
if(comment !== -1) line = line.substr(0,comment);
line = line.trim();
if(!line) continue;
const split = line.split('|');
const split = line.split('|');
games[split[0].trim()] = {
pretty: split[1].trim(),
protocol: split[2].trim(),
options: parseList(split[3]),
params: parseList(split[4])
};
}
return games;
games[split[0].trim()] = {
pretty: split[1].trim(),
protocol: split[2].trim(),
options: parseList(split[3]),
params: parseList(split[4])
};
}
return games;
}
const games = readGames();
function createProtocolInstance(type) {
type = Path.basename(type);
type = Path.basename(type);
const path = protocolDir+'/'+type;
if(!fs.existsSync(path+'.js')) throw Error('Protocol definition file missing: '+type);
const path = protocolDir+'/'+type;
if(!fs.existsSync(path+'.js')) throw Error('Protocol definition file missing: '+type);
const protocol = require(path);
return new protocol();
return new protocol();
}
class TypeResolver {
static lookup(type) {
if(!type) throw Error('No game specified');
static lookup(type) {
if(!type) throw Error('No game specified');
if(type.substr(0,9) === 'protocol-') {
return createProtocolInstance(type.substr(9));
}
if(type.substr(0,9) === 'protocol-') {
return createProtocolInstance(type.substr(9));
}
const game = games[type];
if(!game) throw Error('Invalid game: '+type);
if(!game) throw Error('Invalid game: '+type);
const query = createProtocolInstance(game.protocol);
query.pretty = game.pretty;
for(const key of Object.keys(game.options)) {
query.pretty = game.pretty;
for(const key of Object.keys(game.options)) {
query.options[key] = game.options[key];
}
for(const key of Object.keys(game.params)) {
for(const key of Object.keys(game.params)) {
query[key] = game.params[key];
}
return query;
}
static printReadme() {
let out = '';
for(const key of Object.keys(games)) {
const game = games[key];
out += "* "+game.pretty+" ("+key+")";
if(game.options.port_query_offset || game.options.port_query)
out += " [[Separate Query Port](#separate-query-port)]";
if(game.params.doc_notes)
out += " [[Additional Notes](#"+game.params.doc_notes+")]";
out += "\n";
}
return out;
}
return query;
}
static printReadme() {
let out = '';
for(const key of Object.keys(games)) {
const game = games[key];
out += "* "+game.pretty+" ("+key+")";
if(game.options.port_query_offset || game.options.port_query)
out += " [[Separate Query Port](#separate-query-port)]";
if(game.params.doc_notes)
out += " [[Additional Notes](#"+game.params.doc_notes+")]";
out += "\n";
}
return out;
}
}
module.exports = TypeResolver;

View File

@ -1,23 +1,23 @@
class AmericasArmy extends require('./gamespy2') {
finalizeState(state) {
super.finalizeState(state);
state.name = this.stripColor(state.name);
state.map = this.stripColor(state.map);
for(const key of Object.keys(state.raw)) {
if(typeof state.raw[key] === 'string') {
finalizeState(state) {
super.finalizeState(state);
state.name = this.stripColor(state.name);
state.map = this.stripColor(state.map);
for(const key of Object.keys(state.raw)) {
if(typeof state.raw[key] === 'string') {
state.raw[key] = this.stripColor(state.raw[key]);
}
}
for(const player of state.players) {
if(!('name' in player)) continue;
player.name = this.stripColor(player.name);
}
}
}
for(const player of state.players) {
if(!('name' in player)) continue;
player.name = this.stripColor(player.name);
}
}
stripColor(str) {
// uses unreal 2 color codes
return str.replace(/\x1b...|[\x00-\x1a]/g,'');
}
stripColor(str) {
// uses unreal 2 color codes
return str.replace(/\x1b...|[\x00-\x1a]/g,'');
}
}
module.exports = AmericasArmy;

View File

@ -1,66 +1,66 @@
class Armagetron extends require('./core') {
constructor() {
super();
this.encoding = 'latin1';
this.byteorder = 'be';
}
constructor() {
super();
this.encoding = 'latin1';
this.byteorder = 'be';
}
run(state) {
run(state) {
const b = Buffer.from([0,0x35,0,0,0,0,0,0x11]);
this.udpSend(b,(buffer) => {
const reader = this.reader(buffer);
this.udpSend(b,(buffer) => {
const reader = this.reader(buffer);
reader.skip(6);
reader.skip(6);
state.raw.port = this.readUInt(reader);
state.raw.hostname = this.readString(reader);
state.name = this.stripColorCodes(this.readString(reader));
state.raw.numplayers = this.readUInt(reader);
state.raw.versionmin = this.readUInt(reader);
state.raw.versionmax = this.readUInt(reader);
state.raw.version = this.readString(reader);
state.maxplayers = this.readUInt(reader);
state.raw.port = this.readUInt(reader);
state.raw.hostname = this.readString(reader);
state.name = this.stripColorCodes(this.readString(reader));
state.raw.numplayers = this.readUInt(reader);
state.raw.versionmin = this.readUInt(reader);
state.raw.versionmax = this.readUInt(reader);
state.raw.version = this.readString(reader);
state.maxplayers = this.readUInt(reader);
const players = this.readString(reader);
const list = players.split('\n');
for(const name of list) {
if(!name) continue;
state.players.push({
name: this.stripColorCodes(name)
});
}
for(const name of list) {
if(!name) continue;
state.players.push({
name: this.stripColorCodes(name)
});
}
state.raw.options = this.stripColorCodes(this.readString(reader));
state.raw.uri = this.readString(reader);
state.raw.globalids = this.readString(reader);
state.raw.options = this.stripColorCodes(this.readString(reader));
state.raw.uri = this.readString(reader);
state.raw.globalids = this.readString(reader);
this.finish(state);
return true;
});
}
return true;
});
}
readUInt(reader) {
const a = reader.uint(2);
readUInt(reader) {
const a = reader.uint(2);
const b = reader.uint(2);
return (b<<16) + a;
}
readString(reader) {
return (b<<16) + a;
}
readString(reader) {
const len = reader.uint(2);
if(!len) return '';
if(!len) return '';
let out = '';
for(let i = 0; i < len; i += 2) {
let out = '';
for(let i = 0; i < len; i += 2) {
const hi = reader.uint(1);
const lo = reader.uint(1);
if(i+1<len) out += String.fromCharCode(lo);
if(i+2<len) out += String.fromCharCode(hi);
}
if(i+1<len) out += String.fromCharCode(lo);
if(i+2<len) out += String.fromCharCode(hi);
}
return out;
}
stripColorCodes(str) {
return str.replace(/0x[0-9a-f]{6}/g,'');
}
return out;
}
stripColorCodes(str) {
return str.replace(/0x[0-9a-f]{6}/g,'');
}
}
module.exports = Armagetron;

View File

@ -1,48 +1,48 @@
class Ase extends require('./core') {
run(state) {
run(state) {
this.udpSend('s',(buffer) => {
const reader = this.reader(buffer);
const reader = this.reader(buffer);
const header = reader.string({length:4});
if(header !== 'EYE1') return;
if(header !== 'EYE1') return;
state.raw.gamename = this.readString(reader);
state.raw.port = parseInt(this.readString(reader));
state.name = this.readString(reader);
state.raw.gametype = this.readString(reader);
state.map = this.readString(reader);
state.raw.version = this.readString(reader);
state.password = this.readString(reader) === '1';
state.raw.numplayers = parseInt(this.readString(reader));
state.maxplayers = parseInt(this.readString(reader));
state.raw.gamename = this.readString(reader);
state.raw.port = parseInt(this.readString(reader));
state.name = this.readString(reader);
state.raw.gametype = this.readString(reader);
state.map = this.readString(reader);
state.raw.version = this.readString(reader);
state.password = this.readString(reader) === '1';
state.raw.numplayers = parseInt(this.readString(reader));
state.maxplayers = parseInt(this.readString(reader));
while(!reader.done()) {
const key = this.readString(reader);
if(!key) break;
while(!reader.done()) {
const key = this.readString(reader);
if(!key) break;
const value = this.readString(reader);
state.raw[key] = value;
}
state.raw[key] = value;
}
while(!reader.done()) {
while(!reader.done()) {
const flags = reader.uint(1);
const player = {};
if(flags & 1) player.name = this.readString(reader);
if(flags & 2) player.team = this.readString(reader);
if(flags & 4) player.skin = this.readString(reader);
if(flags & 8) player.score = parseInt(this.readString(reader));
if(flags & 16) player.ping = parseInt(this.readString(reader));
if(flags & 32) player.time = parseInt(this.readString(reader));
state.players.push(player);
}
this.finish(state);
});
}
if(flags & 1) player.name = this.readString(reader);
if(flags & 2) player.team = this.readString(reader);
if(flags & 4) player.skin = this.readString(reader);
if(flags & 8) player.score = parseInt(this.readString(reader));
if(flags & 16) player.ping = parseInt(this.readString(reader));
if(flags & 32) player.time = parseInt(this.readString(reader));
state.players.push(player);
}
readString(reader) {
this.finish(state);
});
}
readString(reader) {
const len = reader.uint(1);
return reader.string({length:len-1});
}
return reader.string({length:len-1});
}
}
module.exports = Ase;

View File

@ -2,159 +2,159 @@ const async = require('async');
class Battlefield extends require('./core') {
constructor() {
super();
this.encoding = 'latin1';
}
super();
this.encoding = 'latin1';
}
run(state) {
async.series([
(c) => {
this.query(['serverInfo'], (data) => {
if(this.debug) console.log(data);
if(data.shift() !== 'OK') return this.fatal('Missing OK');
run(state) {
async.series([
(c) => {
this.query(['serverInfo'], (data) => {
if(this.debug) console.log(data);
if(data.shift() !== 'OK') return this.fatal('Missing OK');
state.raw.name = data.shift();
state.raw.numplayers = parseInt(data.shift());
state.maxplayers = parseInt(data.shift());
state.raw.gametype = data.shift();
state.map = data.shift();
state.raw.roundsplayed = parseInt(data.shift());
state.raw.roundstotal = parseInt(data.shift());
const teamCount = data.shift();
state.raw.teams = [];
for(let i = 0; i < teamCount; i++) {
state.raw.name = data.shift();
state.raw.numplayers = parseInt(data.shift());
state.maxplayers = parseInt(data.shift());
state.raw.gametype = data.shift();
state.map = data.shift();
state.raw.roundsplayed = parseInt(data.shift());
state.raw.roundstotal = parseInt(data.shift());
const teamCount = data.shift();
state.raw.teams = [];
for(let i = 0; i < teamCount; i++) {
const tickets = parseFloat(data.shift());
state.raw.teams.push({
tickets:tickets
});
}
state.raw.targetscore = parseInt(data.shift());
data.shift();
state.raw.ranked = (data.shift() === 'true');
state.raw.punkbuster = (data.shift() === 'true');
state.password = (data.shift() === 'true');
state.raw.uptime = parseInt(data.shift());
state.raw.roundtime = parseInt(data.shift());
if(this.isBadCompany2) {
data.shift();
data.shift();
}
state.raw.ip = data.shift();
state.raw.punkbusterversion = data.shift();
state.raw.joinqueue = (data.shift() === 'true');
state.raw.region = data.shift();
if(!this.isBadCompany2) {
state.raw.pingsite = data.shift();
state.raw.country = data.shift();
state.raw.quickmatch = (data.shift() === 'true');
}
c();
});
},
(c) => {
this.query(['version'], (data) => {
if(this.debug) console.log(data);
if(data[0] !== 'OK') return this.fatal('Missing OK');
state.raw.version = data[2];
c();
});
},
(c) => {
this.query(['listPlayers','all'], (data) => {
if(this.debug) console.log(data);
if(data.shift() !== 'OK') return this.fatal('Missing OK');
state.raw.teams.push({
tickets:tickets
});
}
state.raw.targetscore = parseInt(data.shift());
data.shift();
state.raw.ranked = (data.shift() === 'true');
state.raw.punkbuster = (data.shift() === 'true');
state.password = (data.shift() === 'true');
state.raw.uptime = parseInt(data.shift());
state.raw.roundtime = parseInt(data.shift());
if(this.isBadCompany2) {
data.shift();
data.shift();
}
state.raw.ip = data.shift();
state.raw.punkbusterversion = data.shift();
state.raw.joinqueue = (data.shift() === 'true');
state.raw.region = data.shift();
if(!this.isBadCompany2) {
state.raw.pingsite = data.shift();
state.raw.country = data.shift();
state.raw.quickmatch = (data.shift() === 'true');
}
c();
});
},
(c) => {
this.query(['version'], (data) => {
if(this.debug) console.log(data);
if(data[0] !== 'OK') return this.fatal('Missing OK');
state.raw.version = data[2];
c();
});
},
(c) => {
this.query(['listPlayers','all'], (data) => {
if(this.debug) console.log(data);
if(data.shift() !== 'OK') return this.fatal('Missing OK');
const fieldCount = parseInt(data.shift());
const fields = [];
for(let i = 0; i < fieldCount; i++) {
fields.push(data.shift());
}
for(let i = 0; i < fieldCount; i++) {
fields.push(data.shift());
}
const numplayers = data.shift();
for(let i = 0; i < numplayers; i++) {
for(let i = 0; i < numplayers; i++) {
const player = {};
for (let key of fields) {
for (let key of fields) {
let value = data.shift();
if(key === 'teamId') key = 'team';
else if(key === 'squadId') key = 'squad';
if(key === 'teamId') key = 'team';
else if(key === 'squadId') key = 'squad';
if(
key === 'kills'
|| key === 'deaths'
|| key === 'score'
|| key === 'rank'
|| key === 'team'
|| key === 'squad'
|| key === 'ping'
|| key === 'type'
) {
value = parseInt(value);
}
if(
key === 'kills'
|| key === 'deaths'
|| key === 'score'
|| key === 'rank'
|| key === 'team'
|| key === 'squad'
|| key === 'ping'
|| key === 'type'
) {
value = parseInt(value);
}
player[key] = value;
}
state.players.push(player);
}
player[key] = value;
}
state.players.push(player);
}
this.finish(state);
});
}
]);
}
query(params,c) {
this.tcpSend(buildPacket(params), (data) => {
this.finish(state);
});
}
]);
}
query(params,c) {
this.tcpSend(buildPacket(params), (data) => {
const decoded = this.decodePacket(data);
if(!decoded) return false;
c(decoded);
return true;
});
}
decodePacket(buffer) {
if(buffer.length < 8) return false;
if(!decoded) return false;
c(decoded);
return true;
});
}
decodePacket(buffer) {
if(buffer.length < 8) return false;
const reader = this.reader(buffer);
const header = reader.uint(4);
const totalLength = reader.uint(4);
if(buffer.length < totalLength) return false;
if(buffer.length < totalLength) return false;
const paramCount = reader.uint(4);
const params = [];
for(let i = 0; i < paramCount; i++) {
for(let i = 0; i < paramCount; i++) {
const len = reader.uint(4);
params.push(reader.string({length:len}));
params.push(reader.string({length:len}));
const strNull = reader.uint(1);
}
return params;
}
}
return params;
}
}
function buildPacket(params) {
const paramBuffers = [];
for (const param of params) {
paramBuffers.push(Buffer.from(param,'utf8'));
}
for (const param of params) {
paramBuffers.push(Buffer.from(param,'utf8'));
}
let totalLength = 12;
for (const paramBuffer of paramBuffers) {
totalLength += paramBuffer.length+1+4;
}
for (const paramBuffer of paramBuffers) {
totalLength += paramBuffer.length+1+4;
}
const b = Buffer.alloc(totalLength);
b.writeUInt32LE(0,0);
b.writeUInt32LE(totalLength,4);
b.writeUInt32LE(params.length,8);
b.writeUInt32LE(0,0);
b.writeUInt32LE(totalLength,4);
b.writeUInt32LE(params.length,8);
let offset = 12;
for (const paramBuffer of paramBuffers) {
b.writeUInt32LE(paramBuffer.length, offset); offset += 4;
paramBuffer.copy(b, offset); offset += paramBuffer.length;
b.writeUInt8(0, offset); offset += 1;
}
for (const paramBuffer of paramBuffers) {
b.writeUInt32LE(paramBuffer.length, offset); offset += 4;
paramBuffer.copy(b, offset); offset += paramBuffer.length;
b.writeUInt8(0, offset); offset += 1;
}
return b;
return b;
}
module.exports = Battlefield;

View File

@ -1,59 +1,59 @@
const request = require('request');
class BuildAndShoot extends require('./core') {
run(state) {
request({
uri: 'http://'+this.options.address+':'+this.options.port_query+'/',
timeout: 3000,
}, (e,r,body) => {
if(e) return this.fatal('HTTP error');
run(state) {
request({
uri: 'http://'+this.options.address+':'+this.options.port_query+'/',
timeout: 3000,
}, (e,r,body) => {
if(e) return this.fatal('HTTP error');
let m;
let m;
m = body.match(/status server for (.*?)\r|\n/);
if(m) state.name = m[1];
m = body.match(/status server for (.*?)\r|\n/);
if(m) state.name = m[1];
m = body.match(/Current uptime: (\d+)/);
if(m) state.raw.uptime = m[1];
m = body.match(/currently running (.*?) by /);
if(m) state.map = m[1];
m = body.match(/Current players: (\d+)\/(\d+)/);
if(m) {
state.raw.numplayers = m[1];
state.maxplayers = m[2];
}
m = body.match(/Current uptime: (\d+)/);
if(m) state.raw.uptime = m[1];
m = body.match(/class="playerlist"([^]+?)\/table/);
if(m) {
const table = m[1];
m = body.match(/currently running (.*?) by /);
if(m) state.map = m[1];
m = body.match(/Current players: (\d+)\/(\d+)/);
if(m) {
state.raw.numplayers = m[1];
state.maxplayers = m[2];
}
m = body.match(/class="playerlist"([^]+?)\/table/);
if(m) {
const table = m[1];
const pre = /<tr>[^]*<td>([^]*)<\/td>[^]*<td>([^]*)<\/td>[^]*<td>([^]*)<\/td>[^]*<td>([^]*)<\/td>/g;
let pm;
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;
}
*/
this.finish(state);
});
}
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;
}
*/
this.finish(state);
});
}
}
module.exports = BuildAndShoot;

View File

@ -1,314 +1,314 @@
const EventEmitter = require('events').EventEmitter,
dns = require('dns'),
net = require('net'),
async = require('async'),
Reader = require('../lib/reader');
dns = require('dns'),
net = require('net'),
async = require('async'),
Reader = require('../lib/reader');
class Core extends EventEmitter {
constructor() {
super();
this.options = {
tcpTimeout: 1000,
udpTimeout: 1000
};
this.maxAttempts = 1;
this.attempt = 1;
this.finished = false;
this.encoding = 'utf8';
this.byteorder = 'le';
this.delimiter = '\0';
constructor() {
super();
this.options = {
tcpTimeout: 1000,
udpTimeout: 1000
};
this.maxAttempts = 1;
this.attempt = 1;
this.finished = false;
this.encoding = 'utf8';
this.byteorder = 'le';
this.delimiter = '\0';
this.globalTimeoutTimer = setTimeout(() => {
this.globalTimeoutTimer = setTimeout(() => {
this.fatal('timeout');
},10000);
}
},10000);
}
fatal(err,noretry) {
if(!noretry && this.attempt < this.maxAttempts) {
this.attempt++;
this.start();
return;
}
fatal(err,noretry) {
if(!noretry && this.attempt < this.maxAttempts) {
this.attempt++;
this.start();
return;
}
this.done({error: err.toString()});
}
this.done({error: err.toString()});
}
initState() {
return {
name: '',
map: '',
password: false,
initState() {
return {
name: '',
map: '',
password: false,
raw: {},
raw: {},
maxplayers: 0,
players: [],
bots: []
};
}
maxplayers: 0,
players: [],
bots: []
};
}
finalizeState(state) {}
finalizeState(state) {}
finish(state) {
this.finalizeState(state);
this.done(state);
}
finish(state) {
this.finalizeState(state);
this.done(state);
}
done(state) {
if(this.finished) return;
clearTimeout(this.globalTimeoutTimer);
done(state) {
if(this.finished) return;
clearTimeout(this.globalTimeoutTimer);
if(this.options.notes)
state.notes = this.options.notes;
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;
if('port_query' in this.options) state.query.port_query = this.options.port_query;
state.query.type = this.type;
if('pretty' in this) state.query.pretty = this.pretty;
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;
if('port_query' in this.options) state.query.port_query = this.options.port_query;
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);
}
this.reset();
this.finished = true;
this.emit('finished',state);
if(this.options.callback) this.options.callback(state);
}
reset() {
if(this.timers) {
for (const timer of this.timers) {
clearTimeout(timer);
}
}
this.timers = [];
reset() {
if(this.timers) {
for (const timer of this.timers) {
clearTimeout(timer);
}
}
this.timers = [];
if(this.tcpSocket) {
this.tcpSocket.destroy();
delete this.tcpSocket;
}
if(this.tcpSocket) {
this.tcpSocket.destroy();
delete this.tcpSocket;
}
this.udpTimeoutTimer = false;
this.udpCallback = false;
}
this.udpTimeoutTimer = false;
this.udpCallback = false;
}
start() {
const options = this.options;
this.reset();
start() {
const options = this.options;
this.reset();
async.series([
(c) => {
// resolve host names
if(!('host' in options)) return c();
if(options.host.match(/\d+\.\d+\.\d+\.\d+/)) {
options.address = options.host;
c();
} else {
async.series([
(c) => {
// resolve host names
if(!('host' in options)) return c();
if(options.host.match(/\d+\.\d+\.\d+\.\d+/)) {
options.address = options.host;
c();
} else {
this.parseDns(options.host,c);
}
},
(c) => {
// calculate query port if needed
if(!('port_query' in options) && 'port' in options) {
const offset = options.port_query_offset || 0;
options.port_query = options.port + offset;
}
c();
},
(c) => {
// run
this.run(this.initState());
}
}
},
(c) => {
// calculate query port if needed
if(!('port_query' in options) && 'port' in options) {
const offset = options.port_query_offset || 0;
options.port_query = options.port + offset;
}
c();
},
(c) => {
// run
this.run(this.initState());
}
]);
}
]);
}
parseDns(host,c) {
const resolveStandard = (host,c) => {
parseDns(host,c) {
const resolveStandard = (host,c) => {
if(this.debug) console.log("Standard DNS Lookup: " + host);
dns.lookup(host, (err,address,family) => {
if(err) return this.fatal(err);
dns.lookup(host, (err,address,family) => {
if(err) return this.fatal(err);
if(this.debug) console.log(address);
this.options.address = address;
c();
});
};
c();
});
};
const resolveSrv = (srv,host,c) => {
const resolveSrv = (srv,host,c) => {
if(this.debug) console.log("SRV DNS Lookup: " + srv+'.'+host);
dns.resolve(srv+'.'+host, 'SRV', (err,addresses) => {
dns.resolve(srv+'.'+host, 'SRV', (err,addresses) => {
if(this.debug) console.log(err, addresses);
if(err) return resolveStandard(host,c);
if(addresses.length >= 1) {
const line = addresses[0];
if(err) return resolveStandard(host,c);
if(addresses.length >= 1) {
const line = addresses[0];
this.options.port = line.port;
const srvhost = line.name;
if(srvhost.match(/\d+\.\d+\.\d+\.\d+/)) {
if(srvhost.match(/\d+\.\d+\.\d+\.\d+/)) {
this.options.address = srvhost;
c();
} else {
// resolve yet again
resolveStandard(srvhost,c);
}
return;
}
return resolveStandard(host,c);
});
};
c();
} else {
// resolve yet again
resolveStandard(srvhost,c);
}
return;
}
return resolveStandard(host,c);
});
};
if(this.srvRecord) resolveSrv(this.srvRecord,host,c);
else resolveStandard(host,c);
}
if(this.srvRecord) resolveSrv(this.srvRecord,host,c);
else resolveStandard(host,c);
}
// utils
/** @returns {Reader} */
reader(buffer) {
return new Reader(this,buffer);
}
translate(obj,trans) {
for(const from of Object.keys(trans)) {
// utils
/** @returns {Reader} */
reader(buffer) {
return new Reader(this,buffer);
}
translate(obj,trans) {
for(const from of Object.keys(trans)) {
const to = trans[from];
if(from in obj) {
if(to) obj[to] = obj[from];
delete obj[from];
}
}
}
setTimeout(c,t) {
if(this.finished) return 0;
const id = setTimeout(c,t);
this.timers.push(id);
return id;
}
if(from in obj) {
if(to) obj[to] = obj[from];
delete obj[from];
}
}
}
setTimeout(c,t) {
if(this.finished) return 0;
const id = setTimeout(c,t);
this.timers.push(id);
return id;
}
trueTest(str) {
if(typeof str === 'boolean') return str;
if(typeof str === 'number') return str !== 0;
if(typeof str === 'string') {
if(str.toLowerCase() === 'true') return true;
if(str === 'yes') return true;
if(str === '1') return true;
}
return false;
}
debugBuffer(buffer) {
let out = '';
trueTest(str) {
if(typeof str === 'boolean') return str;
if(typeof str === 'number') return str !== 0;
if(typeof str === 'string') {
if(str.toLowerCase() === 'true') return true;
if(str === 'yes') return true;
if(str === '1') return true;
}
return false;
}
debugBuffer(buffer) {
let out = '';
let out2 = '';
for(let i = 0; i < buffer.length; i++) {
const sliced = buffer.slice(i,i+1);
out += sliced.toString('hex')+' ';
for(let i = 0; i < buffer.length; i++) {
const sliced = buffer.slice(i,i+1);
out += sliced.toString('hex')+' ';
let chr = sliced.toString();
if(chr < ' ' || chr > '~') chr = ' ';
out2 += chr+' ';
if(out.length > 60) {
console.log(out);
console.log(out2);
out = out2 = '';
}
}
console.log(out);
console.log(out2);
}
if(chr < ' ' || chr > '~') chr = ' ';
out2 += chr+' ';
if(out.length > 60) {
console.log(out);
console.log(out2);
out = out2 = '';
}
}
console.log(out);
console.log(out2);
}
_tcpConnect(c) {
if(this.tcpSocket) return c(this.tcpSocket);
_tcpConnect(c) {
if(this.tcpSocket) return c(this.tcpSocket);
let connected = false;
let received = Buffer.from([]);
const address = this.options.address;
let connected = false;
let received = Buffer.from([]);
const address = this.options.address;
const port = this.options.port_query;
const socket = this.tcpSocket = net.connect(port,address,() => {
if(this.debug) console.log(address+':'+port+" TCPCONNECTED");
connected = true;
c(socket);
});
socket.setTimeout(10000);
socket.setNoDelay(true);
if(this.debug) console.log(address+':'+port+" TCPCONNECT");
if(this.debug) console.log(address+':'+port+" TCPCONNECTED");
connected = true;
c(socket);
});
socket.setTimeout(10000);
socket.setNoDelay(true);
if(this.debug) console.log(address+':'+port+" TCPCONNECT");
const writeHook = socket.write;
socket.write = (...args) => {
if(this.debug) console.log(address+':'+port+" TCP--> "+args[0].toString('hex'));
writeHook.apply(socket,args);
};
const writeHook = socket.write;
socket.write = (...args) => {
if(this.debug) console.log(address+':'+port+" TCP--> "+args[0].toString('hex'));
writeHook.apply(socket,args);
};
socket.on('error', () => {});
socket.on('close', () => {
if(!this.tcpCallback) return;
if(connected) return this.fatal('Socket closed while waiting on TCP');
else return this.fatal('TCP Connection Refused');
});
socket.on('data', (data) => {
if(!this.tcpCallback) return;
if(this.debug) console.log(address+':'+port+" <--TCP "+data.toString('hex'));
received = Buffer.concat([received,data]);
if(this.tcpCallback(received)) {
clearTimeout(this.tcpTimeoutTimer);
socket.on('error', () => {});
socket.on('close', () => {
if(!this.tcpCallback) return;
if(connected) return this.fatal('Socket closed while waiting on TCP');
else return this.fatal('TCP Connection Refused');
});
socket.on('data', (data) => {
if(!this.tcpCallback) return;
if(this.debug) console.log(address+':'+port+" <--TCP "+data.toString('hex'));
received = Buffer.concat([received,data]);
if(this.tcpCallback(received)) {
clearTimeout(this.tcpTimeoutTimer);
this.tcpCallback = false;
received = Buffer.from([]);
}
});
}
tcpSend(buffer,ondata) {
process.nextTick(() => {
if(this.tcpCallback) return this.fatal('Attempted to send TCP packet while still waiting on a managed response');
received = Buffer.from([]);
}
});
}
tcpSend(buffer,ondata) {
process.nextTick(() => {
if(this.tcpCallback) return this.fatal('Attempted to send TCP packet while still waiting on a managed response');
this._tcpConnect((socket) => {
socket.write(buffer);
});
if(!ondata) return;
socket.write(buffer);
});
if(!ondata) return;
this.tcpTimeoutTimer = this.setTimeout(() => {
this.tcpCallback = false;
this.fatal('TCP Watchdog Timeout');
},this.options.tcpTimeout);
},this.options.tcpTimeout);
this.tcpCallback = ondata;
});
}
});
}
udpSend(buffer,onpacket,ontimeout) {
process.nextTick(() => {
if(this.udpCallback) return this.fatal('Attempted to send UDP packet while still waiting on a managed response');
udpSend(buffer,onpacket,ontimeout) {
process.nextTick(() => {
if(this.udpCallback) return this.fatal('Attempted to send UDP packet while still waiting on a managed response');
this._udpSendNow(buffer);
if(!onpacket) return;
if(!onpacket) return;
this.udpTimeoutTimer = this.setTimeout(() => {
this.udpCallback = false;
let timeout = false;
if(!ontimeout || ontimeout() !== true) timeout = true;
if(timeout) this.fatal('UDP Watchdog Timeout');
},this.options.udpTimeout);
let timeout = false;
if(!ontimeout || ontimeout() !== true) timeout = true;
if(timeout) this.fatal('UDP Watchdog Timeout');
},this.options.udpTimeout);
this.udpCallback = onpacket;
});
}
_udpSendNow(buffer) {
if(!('port_query' 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');
});
}
_udpSendNow(buffer) {
if(!('port_query' 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 = Buffer.from(buffer,'binary');
if(typeof buffer === 'string') buffer = Buffer.from(buffer,'binary');
if(this.debug) console.log(this.options.address+':'+this.options.port_query+" UDP--> "+buffer.toString('hex'));
this.udpSocket.send(buffer,0,buffer.length,this.options.port_query,this.options.address);
}
_udpResponse(buffer) {
if(this.udpCallback) {
const 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() {}
if(this.debug) console.log(this.options.address+':'+this.options.port_query+" UDP--> "+buffer.toString('hex'));
this.udpSocket.send(buffer,0,buffer.length,this.options.port_query,this.options.address);
}
_udpResponse(buffer) {
if(this.udpCallback) {
const 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() {}
}
module.exports = Core;

View File

@ -1,95 +1,95 @@
class Doom3 extends require('./core') {
constructor() {
super();
this.pretty = 'Doom 3';
this.encoding = 'latin1';
this.isEtqw = false;
this.hasSpaceBeforeClanTag = false;
this.hasClanTag = false;
this.hasTypeFlag = false;
}
run(state) {
this.udpSend('\xff\xffgetInfo\x00PiNGPoNG\x00', (buffer) => {
const reader = this.reader(buffer);
constructor() {
super();
this.pretty = 'Doom 3';
this.encoding = 'latin1';
this.isEtqw = false;
this.hasSpaceBeforeClanTag = false;
this.hasClanTag = false;
this.hasTypeFlag = false;
}
run(state) {
this.udpSend('\xff\xffgetInfo\x00PiNGPoNG\x00', (buffer) => {
const reader = this.reader(buffer);
const header = reader.uint(2);
if(header !== 0xffff) return;
if(header !== 0xffff) return;
const header2 = reader.string();
if(header2 !== 'infoResponse') return;
if(header2 !== 'infoResponse') return;
if(this.isEtqw) {
if(this.isEtqw) {
const taskId = reader.uint(4);
}
}
const challenge = reader.uint(4);
const protoVersion = reader.uint(4);
state.raw.protocolVersion = (protoVersion>>16)+'.'+(protoVersion&0xffff);
if(this.isEtqw) {
const size = reader.uint(4);
}
state.raw.protocolVersion = (protoVersion>>16)+'.'+(protoVersion&0xffff);
while(!reader.done()) {
if(this.isEtqw) {
const size = reader.uint(4);
}
while(!reader.done()) {
const key = reader.string();
let value = this.stripColors(reader.string());
if(key === 'si_map') {
value = value.replace('maps/','');
value = value.replace('.entities','');
}
if(!key) break;
state.raw[key] = value;
}
if(key === 'si_map') {
value = value.replace('maps/','');
value = value.replace('.entities','');
}
if(!key) break;
state.raw[key] = value;
}
let i = 0;
while(!reader.done()) {
i++;
let i = 0;
while(!reader.done()) {
i++;
const player = {};
player.id = reader.uint(1);
if(player.id === 32) break;
player.ping = reader.uint(2);
if(!this.isEtqw) player.rate = reader.uint(4);
player.name = this.stripColors(reader.string());
if(this.hasClanTag) {
if(this.hasSpaceBeforeClanTag) reader.uint(1);
player.clantag = this.stripColors(reader.string());
}
if(this.hasTypeFlag) player.typeflag = reader.uint(1);
if(!player.ping || player.typeflag)
state.bots.push(player);
else
state.players.push(player);
}
state.raw.osmask = reader.uint(4);
if(this.isEtqw) {
state.raw.ranked = reader.uint(1);
state.raw.timeleft = reader.uint(4);
state.raw.gamestate = reader.uint(1);
state.raw.servertype = reader.uint(1);
// 0 = regular, 1 = tv
if(state.raw.servertype === 0) {
state.raw.interestedClients = reader.uint(1);
} else if(state.raw.servertype === 1) {
state.raw.connectedClients = reader.uint(4);
state.raw.maxClients = reader.uint(4);
}
}
if(state.raw.si_name) state.name = state.raw.si_name;
if(state.raw.si_map) state.map = state.raw.si_map;
if(state.raw.si_maxplayers) state.maxplayers = parseInt(state.raw.si_maxplayers);
if(state.raw.si_usepass === '1') state.password = true;
player.id = reader.uint(1);
if(player.id === 32) break;
player.ping = reader.uint(2);
if(!this.isEtqw) player.rate = reader.uint(4);
player.name = this.stripColors(reader.string());
if(this.hasClanTag) {
if(this.hasSpaceBeforeClanTag) reader.uint(1);
player.clantag = this.stripColors(reader.string());
}
if(this.hasTypeFlag) player.typeflag = reader.uint(1);
this.finish(state);
return true;
});
}
if(!player.ping || player.typeflag)
state.bots.push(player);
else
state.players.push(player);
}
stripColors(str) {
// uses quake 3 color codes
return str.replace(/\^(X.{6}|.)/g,'');
}
state.raw.osmask = reader.uint(4);
if(this.isEtqw) {
state.raw.ranked = reader.uint(1);
state.raw.timeleft = reader.uint(4);
state.raw.gamestate = reader.uint(1);
state.raw.servertype = reader.uint(1);
// 0 = regular, 1 = tv
if(state.raw.servertype === 0) {
state.raw.interestedClients = reader.uint(1);
} else if(state.raw.servertype === 1) {
state.raw.connectedClients = reader.uint(4);
state.raw.maxClients = reader.uint(4);
}
}
if(state.raw.si_name) state.name = state.raw.si_name;
if(state.raw.si_map) state.map = state.raw.si_map;
if(state.raw.si_maxplayers) state.maxplayers = parseInt(state.raw.si_maxplayers);
if(state.raw.si_usepass === '1') state.password = true;
this.finish(state);
return true;
});
}
stripColors(str) {
// uses quake 3 color codes
return str.replace(/\^(X.{6}|.)/g,'');
}
}
module.exports = Doom3;

View File

@ -1,33 +1,33 @@
class Ffow extends require('./valve') {
constructor() {
super();
this.byteorder = 'be';
this.legacyChallenge = true;
}
queryInfo(state,c) {
this.sendPacket(0x46,false,'LSQ',0x49, (b) => {
const reader = this.reader(b);
state.raw.protocol = reader.uint(1);
state.name = reader.string();
state.map = reader.string();
state.raw.mod = reader.string();
state.raw.gamemode = reader.string();
state.raw.description = reader.string();
state.raw.version = reader.string();
state.raw.port = reader.uint(2);
state.raw.numplayers = reader.uint(1);
state.maxplayers = reader.uint(1);
state.raw.listentype = String.fromCharCode(reader.uint(1));
state.raw.environment = String.fromCharCode(reader.uint(1));
state.password = !!reader.uint(1);
state.raw.secure = reader.uint(1);
state.raw.averagefps = reader.uint(1);
state.raw.round = reader.uint(1);
state.raw.maxrounds = reader.uint(1);
state.raw.timeleft = reader.uint(2);
c();
});
}
constructor() {
super();
this.byteorder = 'be';
this.legacyChallenge = true;
}
queryInfo(state,c) {
this.sendPacket(0x46,false,'LSQ',0x49, (b) => {
const reader = this.reader(b);
state.raw.protocol = reader.uint(1);
state.name = reader.string();
state.map = reader.string();
state.raw.mod = reader.string();
state.raw.gamemode = reader.string();
state.raw.description = reader.string();
state.raw.version = reader.string();
state.raw.port = reader.uint(2);
state.raw.numplayers = reader.uint(1);
state.maxplayers = reader.uint(1);
state.raw.listentype = String.fromCharCode(reader.uint(1));
state.raw.environment = String.fromCharCode(reader.uint(1));
state.password = !!reader.uint(1);
state.raw.secure = reader.uint(1);
state.raw.averagefps = reader.uint(1);
state.raw.round = reader.uint(1);
state.raw.maxrounds = reader.uint(1);
state.raw.timeleft = reader.uint(2);
c();
});
}
}
module.exports = Ffow;

View File

@ -1,88 +1,88 @@
const async = require('async');
class Gamespy1 extends require('./core') {
constructor() {
super();
this.sessionId = 1;
this.encoding = 'latin1';
this.byteorder = 'be';
}
constructor() {
super();
this.sessionId = 1;
this.encoding = 'latin1';
this.byteorder = 'be';
}
run(state) {
async.series([
(c) => {
this.sendPacket('info', (data) => {
state.raw = data;
if('hostname' in state.raw) state.name = state.raw.hostname;
if('mapname' in state.raw) state.map = state.raw.mapname;
if(this.trueTest(state.raw.password)) state.password = true;
if('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers);
c();
});
},
(c) => {
this.sendPacket('rules', (data) => {
state.raw.rules = data;
c();
});
},
(c) => {
this.sendPacket('players', (data) => {
const players = {};
run(state) {
async.series([
(c) => {
this.sendPacket('info', (data) => {
state.raw = data;
if('hostname' in state.raw) state.name = state.raw.hostname;
if('mapname' in state.raw) state.map = state.raw.mapname;
if(this.trueTest(state.raw.password)) state.password = true;
if('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers);
c();
});
},
(c) => {
this.sendPacket('rules', (data) => {
state.raw.rules = data;
c();
});
},
(c) => {
this.sendPacket('players', (data) => {
const players = {};
const teams = {};
for(const ident of Object.keys(data)) {
for(const ident of Object.keys(data)) {
const split = ident.split('_');
let key = split[0];
const id = split[1];
let value = data[ident];
if(key === 'teamname') {
teams[id] = value;
} else {
if(!(id in players)) players[id] = {};
if(key === 'playername') key = 'name';
else if(key === 'team') value = parseInt(value);
else if(key === 'score' || key === 'ping' || key === 'deaths') value = parseInt(value);
players[id][key] = value;
}
}
state.raw.teams = teams;
for(const id of Object.keys(players)) {
state.players.push(players[id]);
if(key === 'teamname') {
teams[id] = value;
} else {
if(!(id in players)) players[id] = {};
if(key === 'playername') key = 'name';
else if(key === 'team') value = parseInt(value);
else if(key === 'score' || key === 'ping' || key === 'deaths') value = parseInt(value);
players[id][key] = value;
}
}
this.finish(state);
});
}
]);
}
state.raw.teams = teams;
for(const id of Object.keys(players)) {
state.players.push(players[id]);
}
this.finish(state);
});
}
]);
sendPacket(type,callback) {
const queryId = '';
}
sendPacket(type,callback) {
const queryId = '';
const output = {};
this.udpSend('\\'+type+'\\', (buffer) => {
this.udpSend('\\'+type+'\\', (buffer) => {
const reader = this.reader(buffer);
const str = reader.string({length:buffer.length});
const split = str.split('\\');
split.shift();
split.shift();
const data = {};
while(split.length) {
while(split.length) {
const key = split.shift();
const value = split.shift() || '';
data[key] = value;
}
if(!('queryid' in data)) return;
if(queryId && data.queryid !== queryId) return;
for(const i of Object.keys(data)) output[i] = data[i];
if('final' in output) {
delete output.final;
delete output.queryid;
callback(output);
return true;
}
});
}
data[key] = value;
}
if(!('queryid' in data)) return;
if(queryId && data.queryid !== queryId) return;
for(const i of Object.keys(data)) output[i] = data[i];
if('final' in output) {
delete output.final;
delete output.queryid;
callback(output);
return true;
}
});
}
}
module.exports = Gamespy1;

View File

@ -1,98 +1,98 @@
class Gamespy2 extends require('./core') {
constructor() {
super();
this.sessionId = 1;
this.encoding = 'latin1';
this.byteorder = 'be';
}
constructor() {
super();
this.sessionId = 1;
this.encoding = 'latin1';
this.byteorder = 'be';
}
run(state) {
const request = Buffer.from([0xfe,0xfd,0x00,0x00,0x00,0x00,0x01,0xff,0xff,0xff]);
run(state) {
const request = Buffer.from([0xfe,0xfd,0x00,0x00,0x00,0x00,0x01,0xff,0xff,0xff]);
const packets = [];
this.udpSend(request,
(buffer) => {
if(packets.length && buffer.readUInt8(0) === 0)
buffer = buffer.slice(1);
packets.push(buffer);
},
() => {
this.udpSend(request,
(buffer) => {
if(packets.length && buffer.readUInt8(0) === 0)
buffer = buffer.slice(1);
packets.push(buffer);
},
() => {
const buffer = Buffer.concat(packets);
const reader = this.reader(buffer);
const header = reader.uint(1);
if(header !== 0) return;
if(header !== 0) return;
const pingId = reader.uint(4);
if(pingId !== 1) return;
while(!reader.done()) {
if(pingId !== 1) return;
while(!reader.done()) {
const key = reader.string();
const value = reader.string();
if(!key) break;
state.raw[key] = value;
}
if('hostname' in state.raw) state.name = state.raw.hostname;
if('mapname' in state.raw) state.map = state.raw.mapname;
if(this.trueTest(state.raw.password)) state.password = true;
if('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers);
if(!key) break;
state.raw[key] = value;
}
state.players = this.readFieldData(reader);
state.raw.teams = this.readFieldData(reader);
if('hostname' in state.raw) state.name = state.raw.hostname;
if('mapname' in state.raw) state.map = state.raw.mapname;
if(this.trueTest(state.raw.password)) state.password = true;
if('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers);
this.finish(state);
return true;
}
);
}
state.players = this.readFieldData(reader);
state.raw.teams = this.readFieldData(reader);
readFieldData(reader) {
this.finish(state);
return true;
}
);
}
readFieldData(reader) {
const count = reader.uint(1);
// count is unreliable (often it's wrong), so we don't use it.
// read until we hit an empty first field string
if(this.debug) console.log("Reading fields, starting at: "+reader.rest());
// count is unreliable (often it's wrong), so we don't use it.
// read until we hit an empty first field string
if(this.debug) console.log("Reading fields, starting at: "+reader.rest());
const fields = [];
while(!reader.done()) {
while(!reader.done()) {
let field = reader.string();
if(!field) break;
if(field.charCodeAt(0) <= 2) field = field.substring(1);
fields.push(field);
if(this.debug) console.log("field:"+field);
}
if(!field) break;
if(field.charCodeAt(0) <= 2) field = field.substring(1);
fields.push(field);
if(this.debug) console.log("field:"+field);
}
const units = [];
outer: while(!reader.done()) {
outer: while(!reader.done()) {
const unit = {};
for(let iField = 0; iField < fields.length; iField++) {
for(let iField = 0; iField < fields.length; iField++) {
let key = fields[iField];
let value = reader.string();
if(!value && iField === 0) break outer;
if(this.debug) console.log("value:"+value);
if(key === 'player_') key = 'name';
else if(key === 'score_') key = 'score';
else if(key === 'deaths_') key = 'deaths';
else if(key === 'ping_') key = 'ping';
else if(key === 'team_') key = 'team';
else if(key === 'kills_') key = 'kills';
else if(key === 'team_t') key = 'name';
else if(key === 'tickets_t') key = 'tickets';
if(
key === 'score' || key === 'deaths'
|| key === 'ping' || key === 'team'
|| key === 'kills' || key === 'tickets'
) {
if(value === '') continue;
value = parseInt(value);
}
if(!value && iField === 0) break outer;
if(this.debug) console.log("value:"+value);
if(key === 'player_') key = 'name';
else if(key === 'score_') key = 'score';
else if(key === 'deaths_') key = 'deaths';
else if(key === 'ping_') key = 'ping';
else if(key === 'team_') key = 'team';
else if(key === 'kills_') key = 'kills';
else if(key === 'team_t') key = 'name';
else if(key === 'tickets_t') key = 'tickets';
unit[key] = value;
}
units.push(unit);
}
if(
key === 'score' || key === 'deaths'
|| key === 'ping' || key === 'team'
|| key === 'kills' || key === 'tickets'
) {
if(value === '') continue;
value = parseInt(value);
}
return units;
}
unit[key] = value;
}
units.push(unit);
}
return units;
}
}
module.exports = Gamespy2;

View File

@ -1,209 +1,209 @@
const async = require('async');
class Gamespy3 extends require('./core') {
constructor() {
super();
this.sessionId = 1;
this.encoding = 'latin1';
this.byteorder = 'be';
this.noChallenge = false;
this.useOnlySingleSplit = false;
this.isJc2mp = false;
}
constructor() {
super();
this.sessionId = 1;
this.encoding = 'latin1';
this.byteorder = 'be';
this.noChallenge = false;
this.useOnlySingleSplit = false;
this.isJc2mp = false;
}
run(state) {
let challenge,packets;
run(state) {
let challenge,packets;
async.series([
(c) => {
if(this.noChallenge) return c();
this.sendPacket(9,false,false,false,(buffer) => {
const reader = this.reader(buffer);
challenge = parseInt(reader.string());
c();
});
},
async.series([
(c) => {
if(this.noChallenge) return c();
this.sendPacket(9,false,false,false,(buffer) => {
const reader = this.reader(buffer);
challenge = parseInt(reader.string());
c();
});
},
(c) => {
let requestPayload;
if(this.isJc2mp) {
// they completely alter the protocol. because why not.
requestPayload = Buffer.from([0xff,0xff,0xff,0x02]);
} else {
requestPayload = Buffer.from([0xff,0xff,0xff,0x01]);
}
if(this.isJc2mp) {
// they completely alter the protocol. because why not.
requestPayload = Buffer.from([0xff,0xff,0xff,0x02]);
} else {
requestPayload = Buffer.from([0xff,0xff,0xff,0x01]);
}
this.sendPacket(0,challenge,requestPayload,true,(b) => {
packets = b;
c();
});
},
this.sendPacket(0,challenge,requestPayload,true,(b) => {
packets = b;
c();
});
},
(c) => {
// iterate over the received packets
// the first packet will start off with k/v pairs, followed with data fields
// the following packets will only have data fields
// iterate over the received packets
// the first packet will start off with k/v pairs, followed with data fields
// the following packets will only have data fields
state.raw.playerTeamInfo = {};
state.raw.playerTeamInfo = {};
for(let iPacket = 0; iPacket < packets.length; iPacket++) {
const packet = packets[iPacket];
for(let iPacket = 0; iPacket < packets.length; iPacket++) {
const packet = packets[iPacket];
const reader = this.reader(packet);
if(this.debug) {
console.log("+++"+packet.toString('hex'));
console.log(":::"+packet.toString('ascii'));
}
if(this.debug) {
console.log("+++"+packet.toString('hex'));
console.log(":::"+packet.toString('ascii'));
}
// Parse raw server key/values
// Parse raw server key/values
if(iPacket === 0) {
while(!reader.done()) {
const key = reader.string();
if(!key) break;
if(iPacket === 0) {
while(!reader.done()) {
const key = reader.string();
if(!key) break;
let value = reader.string();
// reread the next line if we hit the weird ut3 bug
if(value === 'p1073741829') value = reader.string();
// reread the next line if we hit the weird ut3 bug
if(value === 'p1073741829') value = reader.string();
state.raw[key] = value;
}
}
state.raw[key] = value;
}
}
// Parse player, team, item array state
// Parse player, team, item array state
if(this.isJc2mp) {
state.raw.numPlayers2 = reader.uint(2);
while(!reader.done()) {
if(this.isJc2mp) {
state.raw.numPlayers2 = reader.uint(2);
while(!reader.done()) {
const player = {};
player.name = reader.string();
player.steamid = reader.string();
player.ping = reader.uint(2);
state.players.push(player);
}
} else {
let firstMode = true;
while(!reader.done()) {
player.name = reader.string();
player.steamid = reader.string();
player.ping = reader.uint(2);
state.players.push(player);
}
} else {
let firstMode = true;
while(!reader.done()) {
let mode = reader.string();
if(mode.charCodeAt(0) <= 2) mode = mode.substring(1);
if(!mode) continue;
let offset = 0;
if(iPacket !== 0 && firstMode) offset = reader.uint(1);
reader.skip(1);
firstMode = false;
if(mode.charCodeAt(0) <= 2) mode = mode.substring(1);
if(!mode) continue;
let offset = 0;
if(iPacket !== 0 && firstMode) offset = reader.uint(1);
reader.skip(1);
firstMode = false;
const modeSplit = mode.split('_');
const modeName = modeSplit[0];
const modeType = modeSplit.length > 1 ? modeSplit[1] : 'no_';
if(!(modeType in state.raw.playerTeamInfo)) {
state.raw.playerTeamInfo[modeType] = [];
}
if(!(modeType in state.raw.playerTeamInfo)) {
state.raw.playerTeamInfo[modeType] = [];
}
const store = state.raw.playerTeamInfo[modeType];
while(!reader.done()) {
while(!reader.done()) {
const item = reader.string();
if(!item) break;
if(!item) break;
while(store.length <= offset) { store.push({}); }
store[offset][modeName] = item;
offset++;
}
}
}
}
while(store.length <= offset) { store.push({}); }
store[offset][modeName] = item;
offset++;
}
}
}
}
c();
},
c();
},
(c) => {
// Turn all that raw state into something useful
// Turn all that raw state into something useful
if('hostname' in state.raw) state.name = state.raw.hostname;
else if('servername' in state.raw) state.name = state.raw.servername;
if('mapname' in state.raw) state.map = state.raw.mapname;
if(state.raw.password === '1') state.password = true;
if('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers);
if('hostname' in state.raw) state.name = state.raw.hostname;
else if('servername' in state.raw) state.name = state.raw.servername;
if('mapname' in state.raw) state.map = state.raw.mapname;
if(state.raw.password === '1') state.password = true;
if('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers);
if('' in state.raw.playerTeamInfo) {
for (const playerInfo of state.raw.playerTeamInfo['']) {
const player = {};
for(const from of Object.keys(playerInfo)) {
let key = from;
if('' in state.raw.playerTeamInfo) {
for (const playerInfo of state.raw.playerTeamInfo['']) {
const player = {};
for(const from of Object.keys(playerInfo)) {
let key = from;
let value = playerInfo[from];
if(key === 'player') key = 'name';
if(key === 'score' || key === 'ping' || key === 'team' || key === 'deaths' || key === 'pid') value = parseInt(value);
player[key] = value;
}
state.players.push(player);
}
}
if(key === 'player') key = 'name';
if(key === 'score' || key === 'ping' || key === 'team' || key === 'deaths' || key === 'pid') value = parseInt(value);
player[key] = value;
}
state.players.push(player);
}
}
this.finish(state);
}
]);
}
this.finish(state);
}
]);
}
sendPacket(type,challenge,payload,assemble,c) {
const challengeLength = (this.noChallenge || challenge === false) ? 0 : 4;
sendPacket(type,challenge,payload,assemble,c) {
const challengeLength = (this.noChallenge || challenge === false) ? 0 : 4;
const payloadLength = payload ? payload.length : 0;
const b = Buffer.alloc(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);
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);
let numPackets = 0;
let numPackets = 0;
const packets = {};
this.udpSend(b,(buffer) => {
this.udpSend(b,(buffer) => {
const reader = this.reader(buffer);
const iType = reader.uint(1);
if(iType !== type) return;
if(iType !== type) return;
const iSessionId = reader.uint(4);
if(iSessionId !== this.sessionId) return;
if(iSessionId !== this.sessionId) return;
if(!assemble) {
c(reader.rest());
return true;
}
if(this.useOnlySingleSplit) {
// has split headers, but they are worthless and only one packet is used
reader.skip(11);
c([reader.rest()]);
return true;
}
if(!assemble) {
c(reader.rest());
return true;
}
if(this.useOnlySingleSplit) {
// has split headers, but they are worthless and only one packet is used
reader.skip(11);
c([reader.rest()]);
return true;
}
reader.skip(9); // filler data -- usually set to 'splitnum\0'
reader.skip(9); // filler data -- usually set to 'splitnum\0'
let id = reader.uint(1);
const last = (id & 0x80);
id = id & 0x7f;
if(last) numPackets = id+1;
id = id & 0x7f;
if(last) numPackets = id+1;
reader.skip(1); // "another 'packet number' byte, but isn't understood."
reader.skip(1); // "another 'packet number' byte, but isn't understood."
packets[id] = reader.rest();
if(this.debug) {
console.log("Received packet #"+id);
if(last) console.log("(last)");
}
packets[id] = reader.rest();
if(this.debug) {
console.log("Received packet #"+id);
if(last) console.log("(last)");
}
if(!numPackets || Object.keys(packets).length !== numPackets) return;
if(!numPackets || Object.keys(packets).length !== numPackets) return;
// assemble the parts
// assemble the parts
const list = [];
for(let i = 0; i < numPackets; i++) {
if(!(i in packets)) {
this.fatal('Missing packet #'+i);
return true;
}
list.push(packets[i]);
}
c(list);
return true;
});
}
for(let i = 0; i < numPackets; i++) {
if(!(i in packets)) {
this.fatal('Missing packet #'+i);
return true;
}
list.push(packets[i]);
}
c(list);
return true;
});
}
}
module.exports = Gamespy3;

View File

@ -1,53 +1,53 @@
const request = require('request');
class GeneShift extends require('./core') {
run(state) {
request({
uri: 'http://geneshift.net/game/receiveLobby.php',
timeout: 3000,
}, (e,r,body) => {
if(e) return this.fatal('Lobby request error');
run(state) {
request({
uri: 'http://geneshift.net/game/receiveLobby.php',
timeout: 3000,
}, (e,r,body) => {
if(e) return this.fatal('Lobby request error');
const split = body.split('<br/>');
let found = false;
for(const line of split) {
let found = false;
for(const line of split) {
const fields = line.split('::');
const ip = fields[2];
const port = fields[3];
if(ip === this.options.address && parseInt(port) === this.options.port) {
found = fields;
break;
}
}
if(ip === this.options.address && parseInt(port) === this.options.port) {
found = fields;
break;
}
}
if(!found) return this.fatal('Server not found in list');
if(!found) return this.fatal('Server not found in list');
state.raw.countrycode = found[0];
state.raw.country = found[1];
state.name = found[4];
state.map = found[5];
state.raw.numplayers = parseInt(found[6]);
state.maxplayers = parseInt(found[7]);
// fields[8] is unknown?
state.raw.rules = found[9];
state.raw.gamemode = parseInt(found[10]);
state.raw.gangsters = parseInt(found[11]);
state.raw.cashrate = parseInt(found[12]);
state.raw.missions = !!parseInt(found[13]);
state.raw.vehicles = !!parseInt(found[14]);
state.raw.customweapons = !!parseInt(found[15]);
state.raw.friendlyfire = !!parseInt(found[16]);
state.raw.mercs = !!parseInt(found[17]);
// fields[18] is unknown? listen server?
state.raw.version = found[19];
state.raw.countrycode = found[0];
state.raw.country = found[1];
state.name = found[4];
state.map = found[5];
state.raw.numplayers = parseInt(found[6]);
state.maxplayers = parseInt(found[7]);
// fields[8] is unknown?
state.raw.rules = found[9];
state.raw.gamemode = parseInt(found[10]);
state.raw.gangsters = parseInt(found[11]);
state.raw.cashrate = parseInt(found[12]);
state.raw.missions = !!parseInt(found[13]);
state.raw.vehicles = !!parseInt(found[14]);
state.raw.customweapons = !!parseInt(found[15]);
state.raw.friendlyfire = !!parseInt(found[16]);
state.raw.mercs = !!parseInt(found[17]);
// fields[18] is unknown? listen server?
state.raw.version = found[19];
for(let i = 0; i < state.raw.numplayers; i++) {
state.players.push({});
}
for(let i = 0; i < state.raw.numplayers; i++) {
state.players.push({});
}
this.finish(state);
});
}
this.finish(state);
});
}
}
module.exports = GeneShift;

View File

@ -1,9 +1,9 @@
class Hexen2 extends require('./quake1') {
constructor() {
super();
this.sendHeader = '\xFFstatus\x0a';
constructor() {
super();
this.sendHeader = '\xFFstatus\x0a';
this.responseHeader = '\xffn';
}
}
}
module.exports = Hexen2;

View File

@ -1,18 +1,18 @@
// supposedly, gamespy3 is the "official" query protocol for jcmp,
// but it's broken (requires useOnlySingleSplit), and doesn't include player names
class Jc2mp extends require('./gamespy3') {
constructor() {
super();
this.useOnlySingleSplit = true;
}
finalizeState(state) {
super.finalizeState(state);
if(!state.players.length && parseInt(state.raw.numplayers)) {
for(let i = 0; i < parseInt(state.raw.numplayers); i++) {
state.players.push({});
}
}
}
constructor() {
super();
this.useOnlySingleSplit = true;
}
finalizeState(state) {
super.finalizeState(state);
if(!state.players.length && parseInt(state.raw.numplayers)) {
for(let i = 0; i < parseInt(state.raw.numplayers); i++) {
state.players.push({});
}
}
}
}
module.exports = Jc2mp;

View File

@ -1,8 +1,8 @@
class KillingFloor extends require('./unreal2') {
readExtraInfo(reader,state) {
state.raw.wavecurrent = reader.uint(4);
state.raw.wavetotal = reader.uint(4);
}
readExtraInfo(reader,state) {
state.raw.wavecurrent = reader.uint(4);
state.raw.wavetotal = reader.uint(4);
}
}
module.exports = KillingFloor;

View File

@ -1,39 +1,39 @@
class M2mp extends require('./core') {
constructor() {
super();
this.encoding = 'latin1';
}
constructor() {
super();
this.encoding = 'latin1';
}
run(state) {
this.udpSend('M2MP',(buffer) => {
run(state) {
this.udpSend('M2MP',(buffer) => {
const reader = this.reader(buffer);
const header = reader.string({length:4});
if(header !== 'M2MP') return;
state.name = this.readString(reader);
state.raw.numplayers = this.readString(reader);
state.maxplayers = this.readString(reader);
state.raw.gamemode = this.readString(reader);
state.password = !!reader.uint(1);
while(!reader.done()) {
const name = this.readString(reader);
if(!name) break;
state.players.push({
name:name
});
}
this.finish(state);
return true;
});
}
if(header !== 'M2MP') return;
readString(reader) {
const length = reader.uint(1);
return reader.string({length:length-1});
}
state.name = this.readString(reader);
state.raw.numplayers = this.readString(reader);
state.maxplayers = this.readString(reader);
state.raw.gamemode = this.readString(reader);
state.password = !!reader.uint(1);
while(!reader.done()) {
const name = this.readString(reader);
if(!name) break;
state.players.push({
name:name
});
}
this.finish(state);
return true;
});
}
readString(reader) {
const length = reader.uint(1);
return reader.string({length:length-1});
}
}
module.exports = M2mp;

View File

@ -1,99 +1,99 @@
const varint = require('varint'),
async = require('async');
async = require('async');
function varIntBuffer(num) {
return Buffer.from(varint.encode(num));
return Buffer.from(varint.encode(num));
}
function buildPacket(id,data) {
if(!data) data = Buffer.from([]);
if(!data) data = Buffer.from([]);
const idBuffer = varIntBuffer(id);
return Buffer.concat([
varIntBuffer(data.length+idBuffer.length),
idBuffer,
data
]);
return Buffer.concat([
varIntBuffer(data.length+idBuffer.length),
idBuffer,
data
]);
}
class MinecraftPing extends require('./core') {
run(state) {
let receivedData;
run(state) {
let receivedData;
async.series([
(c) => {
// build and send handshake and status TCP packet
async.series([
(c) => {
// build and send handshake and status TCP packet
const portBuf = Buffer.alloc(2);
portBuf.writeUInt16BE(this.options.port_query,0);
portBuf.writeUInt16BE(this.options.port_query,0);
const addressBuf = Buffer.from(this.options.address,'utf8');
const bufs = [
varIntBuffer(4),
varIntBuffer(addressBuf.length),
addressBuf,
portBuf,
varIntBuffer(1)
];
varIntBuffer(4),
varIntBuffer(addressBuf.length),
addressBuf,
portBuf,
varIntBuffer(1)
];
const outBuffer = Buffer.concat([
buildPacket(0,Buffer.concat(bufs)),
buildPacket(0)
]);
buildPacket(0,Buffer.concat(bufs)),
buildPacket(0)
]);
this.tcpSend(outBuffer, (data) => {
if(data.length < 10) return false;
this.tcpSend(outBuffer, (data) => {
if(data.length < 10) return false;
const expected = varint.decode(data);
data = data.slice(varint.decode.bytes);
if(data.length < expected) return false;
receivedData = data;
c();
return true;
});
},
(c) => {
// parse response
data = data.slice(varint.decode.bytes);
if(data.length < expected) return false;
receivedData = data;
c();
return true;
});
},
(c) => {
// parse response
let data = receivedData;
const packetId = varint.decode(data);
if(this.debug) console.log("Packet ID: "+packetId);
data = data.slice(varint.decode.bytes);
if(this.debug) console.log("Packet ID: "+packetId);
data = data.slice(varint.decode.bytes);
const strLen = varint.decode(data);
if(this.debug) console.log("String Length: "+strLen);
data = data.slice(varint.decode.bytes);
if(this.debug) console.log("String Length: "+strLen);
data = data.slice(varint.decode.bytes);
const str = data.toString('utf8');
if(this.debug) {
console.log(str);
}
if(this.debug) {
console.log(str);
}
let json;
try {
json = JSON.parse(str);
delete json.favicon;
} catch(e) {
return this.fatal('Invalid 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;
if(json.players.sample) {
for(const player of json.players.sample) {
state.players.push({
id: player.id,
name: player.name
});
}
}
while(state.players.length < json.players.online) {
state.players.push({});
}
state.raw.version = json.version.name;
state.maxplayers = json.players.max;
state.raw.description = json.description.text;
if(json.players.sample) {
for(const player of json.players.sample) {
state.players.push({
id: player.id,
name: player.name
});
}
}
while(state.players.length < json.players.online) {
state.players.push({});
}
this.finish(state);
}
]);
}
this.finish(state);
}
]);
}
}
module.exports = MinecraftPing;

View File

@ -1,43 +1,43 @@
class Mumble extends require('./core') {
constructor() {
super();
this.options.tcpTimeout = 5000;
}
constructor() {
super();
this.options.tcpTimeout = 5000;
}
run(state) {
this.tcpSend('json', (buffer) => {
if(buffer.length < 10) return;
run(state) {
this.tcpSend('json', (buffer) => {
if(buffer.length < 10) return;
const str = buffer.toString();
let json;
try {
json = JSON.parse(str);
} catch(e) {
// probably not all here yet
return;
}
state.raw = json;
state.name = json.name;
let json;
try {
json = JSON.parse(str);
} catch(e) {
// probably not all here yet
return;
}
state.raw = json;
state.name = json.name;
let channelStack = [state.raw.root];
while(channelStack.length) {
while(channelStack.length) {
const channel = channelStack.shift();
channel.description = this.cleanComment(channel.description);
channelStack = channelStack.concat(channel.channels);
for(const user of channel.users) {
user.comment = this.cleanComment(user.comment);
state.players.push(user);
}
}
this.finish(state);
return true;
});
}
channel.description = this.cleanComment(channel.description);
channelStack = channelStack.concat(channel.channels);
for(const user of channel.users) {
user.comment = this.cleanComment(user.comment);
state.players.push(user);
}
}
cleanComment(str) {
return str.replace(/<.*>/g,'');
}
this.finish(state);
return true;
});
}
cleanComment(str) {
return str.replace(/<.*>/g,'');
}
}
module.exports = Mumble;

View File

@ -1,28 +1,28 @@
class MumblePing extends require('./core') {
constructor() {
super();
this.byteorder = 'be';
}
constructor() {
super();
this.byteorder = 'be';
}
run(state) {
this.udpSend('\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08', (buffer) => {
if(buffer.length < 24) return;
run(state) {
this.udpSend('\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08', (buffer) => {
if(buffer.length < 24) return;
const reader = this.reader(buffer);
reader.skip(1);
state.raw.versionMajor = reader.uint(1);
state.raw.versionMinor = reader.uint(1);
state.raw.versionPatch = reader.uint(1);
reader.skip(8);
state.raw.numplayers = reader.uint(4);
state.maxplayers = reader.uint(4);
state.raw.allowedbandwidth = reader.uint(4);
for(let i = 0; i < state.raw.numplayers; i++) {
state.players.push({});
}
this.finish(state);
return true;
});
}
reader.skip(1);
state.raw.versionMajor = reader.uint(1);
state.raw.versionMinor = reader.uint(1);
state.raw.versionPatch = reader.uint(1);
reader.skip(8);
state.raw.numplayers = reader.uint(4);
state.maxplayers = reader.uint(4);
state.raw.allowedbandwidth = reader.uint(4);
for(let i = 0; i < state.raw.numplayers; i++) {
state.players.push({});
}
this.finish(state);
return true;
});
}
}
module.exports = MumblePing;

View File

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

View File

@ -1,147 +1,147 @@
const async = require('async'),
moment = require('moment');
moment = require('moment');
class OpenTtd extends require('./core') {
run(state) {
async.series([
(c) => {
this.query(0,1,1,4,(reader, version) => {
if(version >= 4) {
const numGrf = reader.uint(1);
state.raw.grfs = [];
for(let i = 0; i < numGrf; i++) {
run(state) {
async.series([
(c) => {
this.query(0,1,1,4,(reader, version) => {
if(version >= 4) {
const numGrf = reader.uint(1);
state.raw.grfs = [];
for(let i = 0; i < numGrf; i++) {
const grf = {};
grf.id = reader.part(4).toString('hex');
grf.md5 = reader.part(16).toString('hex');
state.raw.grfs.push(grf);
}
}
if(version >= 3) {
state.raw.date_current = this.readDate(reader);
state.raw.date_start = this.readDate(reader);
}
if(version >= 2) {
state.raw.maxcompanies = reader.uint(1);
state.raw.numcompanies = reader.uint(1);
state.raw.maxspectators = reader.uint(1);
}
grf.id = reader.part(4).toString('hex');
grf.md5 = reader.part(16).toString('hex');
state.raw.grfs.push(grf);
}
}
if(version >= 3) {
state.raw.date_current = this.readDate(reader);
state.raw.date_start = this.readDate(reader);
}
if(version >= 2) {
state.raw.maxcompanies = reader.uint(1);
state.raw.numcompanies = reader.uint(1);
state.raw.maxspectators = reader.uint(1);
}
state.name = reader.string();
state.raw.version = reader.string();
state.name = reader.string();
state.raw.version = reader.string();
state.raw.language = this.decode(
reader.uint(1),
['any','en','de','fr']
);
state.raw.language = this.decode(
reader.uint(1),
['any','en','de','fr']
);
state.password = !!reader.uint(1);
state.maxplayers = reader.uint(1);
state.raw.numplayers = reader.uint(1);
for(let i = 0; i < state.raw.numplayers; i++) {
state.players.push({});
}
state.raw.numspectators = reader.uint(1);
state.map = reader.string();
state.raw.map_width = reader.uint(2);
state.raw.map_height = reader.uint(2);
state.password = !!reader.uint(1);
state.maxplayers = reader.uint(1);
state.raw.numplayers = reader.uint(1);
for(let i = 0; i < state.raw.numplayers; i++) {
state.players.push({});
}
state.raw.numspectators = reader.uint(1);
state.map = reader.string();
state.raw.map_width = reader.uint(2);
state.raw.map_height = reader.uint(2);
state.raw.landscape = this.decode(
reader.uint(1),
['temperate','arctic','desert','toyland']
);
state.raw.landscape = this.decode(
reader.uint(1),
['temperate','arctic','desert','toyland']
);
state.raw.dedicated = !!reader.uint(1);
state.raw.dedicated = !!reader.uint(1);
c();
});
},
c();
});
},
(c) => {
(c) => {
const vehicle_types = ['train','truck','bus','aircraft','ship'];
const station_types = ['station','truckbay','busstation','airport','dock'];
this.query(2,3,-1,-1, (reader,version) => {
// we don't know how to deal with companies outside version 6
if(version !== 6) return c();
this.query(2,3,-1,-1, (reader,version) => {
// we don't know how to deal with companies outside version 6
if(version !== 6) return c();
state.raw.companies = [];
state.raw.companies = [];
const numCompanies = reader.uint(1);
for(let iCompany = 0; iCompany < numCompanies; iCompany++) {
for(let iCompany = 0; iCompany < numCompanies; iCompany++) {
const company = {};
company.id = reader.uint(1);
company.name = reader.string();
company.year_start = reader.uint(4);
company.value = reader.uint(8);
company.money = reader.uint(8);
company.income = reader.uint(8);
company.performance = reader.uint(2);
company.password = !!reader.uint(1);
company.id = reader.uint(1);
company.name = reader.string();
company.year_start = reader.uint(4);
company.value = reader.uint(8);
company.money = reader.uint(8);
company.income = reader.uint(8);
company.performance = reader.uint(2);
company.password = !!reader.uint(1);
company.vehicles = {};
for(const type of vehicle_types) {
company.vehicles[type] = reader.uint(2);
}
company.stations = {};
for(const type of station_types) {
company.stations[type] = reader.uint(2);
}
company.vehicles = {};
for(const type of vehicle_types) {
company.vehicles[type] = reader.uint(2);
}
company.stations = {};
for(const type of station_types) {
company.stations[type] = reader.uint(2);
}
company.clients = reader.string();
state.raw.companies.push(company);
}
company.clients = reader.string();
state.raw.companies.push(company);
}
c();
});
},
c();
});
},
(c) => {
this.finish(state);
}
]);
}
(c) => {
this.finish(state);
}
]);
}
query(type,expected,minver,maxver,done) {
query(type,expected,minver,maxver,done) {
const b = Buffer.from([0x03,0x00,type]);
this.udpSend(b,(buffer) => {
this.udpSend(b,(buffer) => {
const reader = this.reader(buffer);
const packetLen = reader.uint(2);
if(packetLen !== buffer.length) {
this.fatal('Invalid reported packet length: '+packetLen+' '+buffer.length);
return true;
}
if(packetLen !== buffer.length) {
this.fatal('Invalid reported packet length: '+packetLen+' '+buffer.length);
return true;
}
const packetType = reader.uint(1);
if(packetType !== expected) {
this.fatal('Unexpected response packet type: '+packetType);
return true;
}
if(packetType !== expected) {
this.fatal('Unexpected response packet type: '+packetType);
return true;
}
const protocolVersion = reader.uint(1);
if((minver !== -1 && protocolVersion < minver) || (maxver !== -1 && protocolVersion > maxver)) {
this.fatal('Unknown protocol version: '+protocolVersion+' Expected: '+minver+'-'+maxver);
return true;
}
if((minver !== -1 && protocolVersion < minver) || (maxver !== -1 && protocolVersion > maxver)) {
this.fatal('Unknown protocol version: '+protocolVersion+' Expected: '+minver+'-'+maxver);
return true;
}
done(reader,protocolVersion);
return true;
});
}
done(reader,protocolVersion);
return true;
});
}
readDate(reader) {
readDate(reader) {
const daysSinceZero = reader.uint(4);
const temp = new Date(0,0,1);
temp.setFullYear(0);
temp.setDate(daysSinceZero+1);
return moment(temp).format('YYYY-MM-DD');
}
temp.setFullYear(0);
temp.setDate(daysSinceZero+1);
return moment(temp).format('YYYY-MM-DD');
}
decode(num,arr) {
if(num < 0 || num >= arr.length) {
return num;
}
return arr[num];
}
decode(num,arr) {
if(num < 0 || num >= arr.length) {
return num;
}
return arr[num];
}
}
module.exports = OpenTtd;

View File

@ -1,9 +1,9 @@
class Quake1 extends require('./quake2') {
constructor() {
super();
this.responseHeader = 'n';
this.isQuake1 = true;
}
constructor() {
super();
this.responseHeader = 'n';
this.isQuake1 = true;
}
}
module.exports = Quake1;

View File

@ -1,87 +1,87 @@
class Quake2 extends require('./core') {
constructor() {
super();
this.encoding = 'latin1';
this.delimiter = '\n';
this.sendHeader = 'status';
this.responseHeader = 'print';
this.isQuake1 = false;
}
super();
this.encoding = 'latin1';
this.delimiter = '\n';
this.sendHeader = 'status';
this.responseHeader = 'print';
this.isQuake1 = false;
}
run(state) {
this.udpSend('\xff\xff\xff\xff'+this.sendHeader+'\x00', (buffer) => {
run(state) {
this.udpSend('\xff\xff\xff\xff'+this.sendHeader+'\x00', (buffer) => {
const reader = this.reader(buffer);
const header = reader.string({length:4});
if(header !== '\xff\xff\xff\xff') return;
if(header !== '\xff\xff\xff\xff') return;
let response;
if(this.isQuake1) {
response = reader.string({length:this.responseHeader.length});
} else {
response = reader.string();
}
if(response !== this.responseHeader) return;
if(this.isQuake1) {
response = reader.string({length:this.responseHeader.length});
} else {
response = reader.string();
}
if(response !== this.responseHeader) return;
const info = reader.string().split('\\');
if(info[0] === '') info.shift();
if(info[0] === '') info.shift();
while(true) {
while(true) {
const key = info.shift();
const value = info.shift();
if(typeof value === 'undefined') break;
state.raw[key] = value;
}
if(typeof value === 'undefined') break;
state.raw[key] = value;
}
while(!reader.done()) {
while(!reader.done()) {
const line = reader.string();
if(!line || line.charAt(0) === '\0') break;
if(!line || line.charAt(0) === '\0') break;
const args = [];
const split = line.split('"');
split.forEach((part,i) => {
split.forEach((part,i) => {
const inQuote = (i%2 === 1);
if(inQuote) {
args.push(part);
} else {
if(inQuote) {
args.push(part);
} else {
const splitSpace = part.split(' ');
for (const subpart of splitSpace) {
if(subpart) args.push(subpart);
}
}
});
for (const subpart of splitSpace) {
if(subpart) args.push(subpart);
}
}
});
const player = {};
if(this.isQuake1) {
player.id = parseInt(args.shift());
player.score = parseInt(args.shift());
player.time = parseInt(args.shift());
player.ping = parseInt(args.shift());
player.name = args.shift();
player.skin = args.shift();
player.color1 = parseInt(args.shift());
player.color2 = parseInt(args.shift());
} else {
player.frags = parseInt(args.shift());
player.ping = parseInt(args.shift());
player.name = args.shift() || '';
player.address = args.shift() || '';
}
if(this.isQuake1) {
player.id = parseInt(args.shift());
player.score = parseInt(args.shift());
player.time = parseInt(args.shift());
player.ping = parseInt(args.shift());
player.name = args.shift();
player.skin = args.shift();
player.color1 = parseInt(args.shift());
player.color2 = parseInt(args.shift());
} else {
player.frags = parseInt(args.shift());
player.ping = parseInt(args.shift());
player.name = args.shift() || '';
player.address = args.shift() || '';
}
(player.ping ? state.players : state.bots).push(player);
}
(player.ping ? state.players : state.bots).push(player);
}
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('maxclients' in state.raw) state.maxplayers = state.raw.maxclients;
if('sv_hostname' in state.raw) state.name = state.raw.sv_hostname;
if('hostname' in state.raw) state.name = state.raw.hostname;
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('maxclients' in state.raw) state.maxplayers = state.raw.maxclients;
if('sv_hostname' in state.raw) state.name = state.raw.sv_hostname;
if('hostname' in state.raw) state.name = state.raw.hostname;
this.finish(state);
return true;
});
}
this.finish(state);
return true;
});
}
}
module.exports = Quake2;

View File

@ -1,21 +1,21 @@
class Quake3 extends require('./quake2') {
constructor() {
super();
this.sendHeader = 'getstatus';
this.responseHeader = 'statusResponse';
}
finalizeState(state) {
state.name = this.stripColors(state.name);
for(const key of Object.keys(state.raw)) {
state.raw[key] = this.stripColors(state.raw[key]);
}
for(const player of state.players) {
player.name = this.stripColors(player.name);
}
}
stripColors(str) {
return str.replace(/\^(X.{6}|.)/g,'');
}
constructor() {
super();
this.sendHeader = 'getstatus';
this.responseHeader = 'statusResponse';
}
finalizeState(state) {
state.name = this.stripColors(state.name);
for(const key of Object.keys(state.raw)) {
state.raw[key] = this.stripColors(state.raw[key]);
}
for(const player of state.players) {
player.name = this.stripColors(player.name);
}
}
stripColors(str) {
return str.replace(/\^(X.{6}|.)/g,'');
}
}
module.exports = Quake3;

View File

@ -1,88 +1,88 @@
const async = require('async');
class Samp extends require('./core') {
run(state) {
async.series([
(c) => {
this.sendPacket('i',(reader) => {
state.password = !!reader.uint(1);
state.raw.numplayers = reader.uint(2);
state.maxplayers = reader.uint(2);
state.name = this.readString(reader,4);
state.raw.gamemode = this.readString(reader,4);
this.map = this.readString(reader,4);
c();
});
},
(c) => {
this.sendPacket('r',(reader) => {
run(state) {
async.series([
(c) => {
this.sendPacket('i',(reader) => {
state.password = !!reader.uint(1);
state.raw.numplayers = reader.uint(2);
state.maxplayers = reader.uint(2);
state.name = this.readString(reader,4);
state.raw.gamemode = this.readString(reader,4);
this.map = this.readString(reader,4);
c();
});
},
(c) => {
this.sendPacket('r',(reader) => {
const ruleCount = reader.uint(2);
state.raw.rules = {};
for(let i = 0; i < ruleCount; i++) {
state.raw.rules = {};
for(let i = 0; i < ruleCount; i++) {
const key = this.readString(reader,1);
const value = this.readString(reader,1);
state.raw.rules[key] = value;
}
if('mapname' in state.raw.rules)
state.map = state.raw.rules.mapname;
c();
});
},
(c) => {
this.sendPacket('d',(reader) => {
state.raw.rules[key] = value;
}
if('mapname' in state.raw.rules)
state.map = state.raw.rules.mapname;
c();
});
},
(c) => {
this.sendPacket('d',(reader) => {
const playerCount = reader.uint(2);
for(let i = 0; i < playerCount; i++) {
for(let i = 0; i < playerCount; i++) {
const player = {};
player.id = reader.uint(1);
player.name = this.readString(reader,1);
player.score = reader.int(4);
player.ping = reader.uint(4);
state.players.push(player);
}
c();
},() => {
for(let i = 0; i < state.raw.numplayers; i++) {
state.players.push({});
}
c();
});
},
(c) => {
this.finish(state);
}
]);
}
readString(reader,lenBytes) {
player.id = reader.uint(1);
player.name = this.readString(reader,1);
player.score = reader.int(4);
player.ping = reader.uint(4);
state.players.push(player);
}
c();
},() => {
for(let i = 0; i < state.raw.numplayers; i++) {
state.players.push({});
}
c();
});
},
(c) => {
this.finish(state);
}
]);
}
readString(reader,lenBytes) {
const length = reader.uint(lenBytes);
if(!length) return '';
if(!length) return '';
const string = reader.string({length:length});
return string;
}
sendPacket(type,onresponse,ontimeout) {
return string;
}
sendPacket(type,onresponse,ontimeout) {
const outbuffer = Buffer.alloc(11);
outbuffer.writeUInt32BE(0x53414D50,0);
outbuffer.writeUInt32BE(0x53414D50,0);
const ipSplit = this.options.address.split('.');
outbuffer.writeUInt8(parseInt(ipSplit[0]),4);
outbuffer.writeUInt8(parseInt(ipSplit[1]),5);
outbuffer.writeUInt8(parseInt(ipSplit[2]),6);
outbuffer.writeUInt8(parseInt(ipSplit[3]),7);
outbuffer.writeUInt16LE(this.options.port,8);
outbuffer.writeUInt8(type.charCodeAt(0),10);
outbuffer.writeUInt8(parseInt(ipSplit[0]),4);
outbuffer.writeUInt8(parseInt(ipSplit[1]),5);
outbuffer.writeUInt8(parseInt(ipSplit[2]),6);
outbuffer.writeUInt8(parseInt(ipSplit[3]),7);
outbuffer.writeUInt16LE(this.options.port,8);
outbuffer.writeUInt8(type.charCodeAt(0),10);
this.udpSend(outbuffer,(buffer) => {
this.udpSend(outbuffer,(buffer) => {
const reader = this.reader(buffer);
for(let i = 0; i < outbuffer.length; i++) {
if(outbuffer.readUInt8(i) !== reader.uint(1)) return;
}
onresponse(reader);
return true;
},() => {
if(ontimeout) {
ontimeout();
return true;
}
});
}
for(let i = 0; i < outbuffer.length; i++) {
if(outbuffer.readUInt8(i) !== reader.uint(1)) return;
}
onresponse(reader);
return true;
},() => {
if(ontimeout) {
ontimeout();
return true;
}
});
}
}
module.exports = Samp;

View File

@ -1,62 +1,62 @@
class Starmade extends require('./core') {
constructor() {
super();
this.encoding = 'latin1';
this.byteorder = 'be';
}
run(state) {
constructor() {
super();
this.encoding = 'latin1';
this.byteorder = 'be';
}
run(state) {
const b = Buffer.from([0x00,0x00,0x00,0x09,0x2a,0xff,0xff,0x01,0x6f,0x00,0x00,0x00,0x00]);
this.tcpSend(b,(buffer) => {
this.tcpSend(b,(buffer) => {
const reader = this.reader(buffer);
if(buffer.length < 4) return false;
if(buffer.length < 4) return false;
const packetLength = reader.uint(4);
if(buffer.length < packetLength+12) return false;
if(buffer.length < packetLength+12) return false;
const data = [];
state.raw.data = data;
state.raw.data = data;
reader.skip(2);
while(!reader.done()) {
reader.skip(2);
while(!reader.done()) {
const mark = reader.uint(1);
if(mark === 1) {
// signed int
data.push(reader.int(4));
} else if(mark === 3) {
// float
data.push(reader.float());
} else if(mark === 4) {
// string
if(mark === 1) {
// signed int
data.push(reader.int(4));
} else if(mark === 3) {
// float
data.push(reader.float());
} else if(mark === 4) {
// string
const length = reader.uint(2);
data.push(reader.string(length));
} else if(mark === 6) {
// byte
data.push(reader.uint(1));
}
}
data.push(reader.string(length));
} else if(mark === 6) {
// byte
data.push(reader.uint(1));
}
}
if(data.length < 9) {
this.fatal("Not enough units in data packet");
return true;
}
if(data.length < 9) {
this.fatal("Not enough units in data packet");
return true;
}
if(typeof data[3] === 'number') state.raw.version = data[3].toFixed(7).replace(/0+$/, '');
if(typeof data[4] === 'string') state.name = data[4];
if(typeof data[5] === 'string') state.raw.description = data[5];
if(typeof data[7] === 'number') state.raw.numplayers = data[7];
if(typeof data[8] === 'number') state.maxplayers = data[8];
if(typeof data[3] === 'number') state.raw.version = data[3].toFixed(7).replace(/0+$/, '');
if(typeof data[4] === 'string') state.name = data[4];
if(typeof data[5] === 'string') state.raw.description = data[5];
if(typeof data[7] === 'number') state.raw.numplayers = data[7];
if(typeof data[8] === 'number') state.maxplayers = data[8];
if('numplayers' in state.raw) {
for(let i = 0; i < state.raw.numplayers; i++) {
state.players.push({});
}
}
if('numplayers' in state.raw) {
for(let i = 0; i < state.raw.numplayers; i++) {
state.players.push({});
}
}
this.finish(state);
return true;
});
}
this.finish(state);
return true;
});
}
}
module.exports = Starmade;

View File

@ -1,78 +1,78 @@
const async = require('async');
class Teamspeak2 extends require('./core') {
run(state) {
async.series([
(c) => {
this.sendCommand('sel '+this.options.port, (data) => {
if(data !== '[TS]') this.fatal('Invalid header');
c();
});
},
(c) => {
this.sendCommand('si', (data) => {
for (const line of data.split('\r\n')) {
run(state) {
async.series([
(c) => {
this.sendCommand('sel '+this.options.port, (data) => {
if(data !== '[TS]') this.fatal('Invalid header');
c();
});
},
(c) => {
this.sendCommand('si', (data) => {
for (const line of data.split('\r\n')) {
const equals = line.indexOf('=');
const key = equals === -1 ? line : line.substr(0,equals);
const value = equals === -1 ? '' : line.substr(equals+1);
state.raw[key] = value;
}
c();
});
},
(c) => {
this.sendCommand('pl', (data) => {
state.raw[key] = value;
}
c();
});
},
(c) => {
this.sendCommand('pl', (data) => {
const split = data.split('\r\n');
const fields = split.shift().split('\t');
for (const line of split) {
for (const line of split) {
const split2 = line.split('\t');
const player = {};
split2.forEach((value,i) => {
split2.forEach((value,i) => {
let key = fields[i];
if(!key) return;
if(key === 'nick') key = 'name';
const m = value.match(/^"(.*)"$/);
if(m) value = m[1];
player[key] = value;
});
state.players.push(player);
}
c();
});
},
(c) => {
this.sendCommand('cl', (data) => {
if(!key) return;
if(key === 'nick') key = 'name';
const m = value.match(/^"(.*)"$/);
if(m) value = m[1];
player[key] = value;
});
state.players.push(player);
}
c();
});
},
(c) => {
this.sendCommand('cl', (data) => {
const split = data.split('\r\n');
const fields = split.shift().split('\t');
state.raw.channels = [];
for (const line of split) {
state.raw.channels = [];
for (const line of split) {
const split2 = line.split('\t');
const channel = {};
split2.forEach((value,i) => {
split2.forEach((value,i) => {
const key = fields[i];
if(!key) return;
const m = value.match(/^"(.*)"$/);
if(m) value = m[1];
channel[key] = value;
});
state.raw.channels.push(channel);
}
c();
});
},
(c) => {
this.finish(state);
}
]);
}
sendCommand(cmd,c) {
this.tcpSend(cmd+'\x0A', (buffer) => {
if(buffer.length < 6) return;
if(buffer.slice(-6).toString() !== '\r\nOK\r\n') return;
c(buffer.slice(0,-6).toString());
return true;
});
}
if(!key) return;
const m = value.match(/^"(.*)"$/);
if(m) value = m[1];
channel[key] = value;
});
state.raw.channels.push(channel);
}
c();
});
},
(c) => {
this.finish(state);
}
]);
}
sendCommand(cmd,c) {
this.tcpSend(cmd+'\x0A', (buffer) => {
if(buffer.length < 6) return;
if(buffer.slice(-6).toString() !== '\r\nOK\r\n') return;
c(buffer.slice(0,-6).toString());
return true;
});
}
}
module.exports = Teamspeak2;

View File

@ -1,78 +1,78 @@
const async = require('async');
class Teamspeak3 extends require('./core') {
run(state) {
async.series([
(c) => {
this.sendCommand('use port='+this.options.port, (data) => {
const split = data.split('\n\r');
if(split[0] !== 'TS3') this.fatal('Invalid header');
c();
}, true);
},
(c) => {
this.sendCommand('serverinfo', (data) => {
state.raw = data[0];
if('virtualserver_name' in state.raw) state.name = state.raw.virtualserver_name;
run(state) {
async.series([
(c) => {
this.sendCommand('use port='+this.options.port, (data) => {
const split = data.split('\n\r');
if(split[0] !== 'TS3') this.fatal('Invalid header');
c();
}, true);
},
(c) => {
this.sendCommand('serverinfo', (data) => {
state.raw = data[0];
if('virtualserver_name' in state.raw) state.name = state.raw.virtualserver_name;
if('virtualserver_maxclients' in state.raw) state.maxplayers = state.raw.virtualserver_maxclients;
c();
});
},
(c) => {
this.sendCommand('clientlist', (list) => {
for (const client of list) {
c();
});
},
(c) => {
this.sendCommand('clientlist', (list) => {
for (const client of list) {
client.name = client.client_nickname;
delete client.client_nickname;
if(client.client_type === '0') {
state.players.push(client);
}
}
c();
});
},
(c) => {
this.sendCommand('channellist -topic', (data) => {
state.raw.channels = data;
c();
});
},
(c) => {
this.finish(state);
}
]);
}
sendCommand(cmd,c,raw) {
this.tcpSend(cmd+'\x0A', (buffer) => {
if(buffer.length < 21) return;
if(buffer.slice(-21).toString() !== '\n\rerror id=0 msg=ok\n\r') return;
const body = buffer.slice(0,-21).toString();
delete client.client_nickname;
if(client.client_type === '0') {
state.players.push(client);
}
}
c();
});
},
(c) => {
this.sendCommand('channellist -topic', (data) => {
state.raw.channels = data;
c();
});
},
(c) => {
this.finish(state);
}
]);
}
sendCommand(cmd,c,raw) {
this.tcpSend(cmd+'\x0A', (buffer) => {
if(buffer.length < 21) return;
if(buffer.slice(-21).toString() !== '\n\rerror id=0 msg=ok\n\r') return;
const body = buffer.slice(0,-21).toString();
let out;
let out;
if(raw) {
out = body;
} else {
if(raw) {
out = body;
} else {
const segments = body.split('|');
out = [];
for (const line of segments) {
out = [];
for (const line of segments) {
const split = line.split(' ');
const unit = {};
for (const field of split) {
for (const field of split) {
const equals = field.indexOf('=');
const key = equals === -1 ? field : field.substr(0,equals);
const value = equals === -1 ? '' : field.substr(equals+1)
.replace(/\\s/g,' ').replace(/\\\//g,'/');
unit[key] = value;
}
out.push(unit);
}
}
.replace(/\\s/g,' ').replace(/\\\//g,'/');
unit[key] = value;
}
out.push(unit);
}
}
c(out);
c(out);
return true;
});
}
return true;
});
}
}
module.exports = Teamspeak3;

View File

@ -1,36 +1,36 @@
const request = require('request');
class Terraria extends require('./core') {
run(state) {
request({
uri: 'http://'+this.options.address+':'+this.options.port_query+'/v2/server/status',
timeout: 3000,
qs: {
players: 'true',
token: this.options.token
}
}, (e,r,body) => {
if(e) return this.fatal('HTTP error');
let json;
try {
json = JSON.parse(body);
} catch(e) {
return this.fatal('Invalid JSON');
}
if(json.status !== 200) return this.fatal('Invalid status');
run(state) {
request({
uri: 'http://'+this.options.address+':'+this.options.port_query+'/v2/server/status',
timeout: 3000,
qs: {
players: 'true',
token: this.options.token
}
}, (e,r,body) => {
if(e) return this.fatal('HTTP error');
let json;
try {
json = JSON.parse(body);
} catch(e) {
return this.fatal('Invalid JSON');
}
for (const one of json.players) {
state.players.push({name:one.nickname,team:one.team});
}
state.name = json.name;
state.raw.port = json.port;
state.raw.numplayers = json.playercount;
if(json.status !== 200) return this.fatal('Invalid status');
this.finish(state);
});
}
for (const one of json.players) {
state.players.push({name:one.nickname,team:one.team});
}
state.name = json.name;
state.raw.port = json.port;
state.raw.numplayers = json.playercount;
this.finish(state);
});
}
}
module.exports = Terraria;

View File

@ -1,140 +1,140 @@
const async = require('async');
class Unreal2 extends require('./core') {
constructor() {
super();
this.encoding = 'latin1';
}
run(state) {
async.series([
(c) => {
this.sendPacket(0,true,(b) => {
const reader = this.reader(b);
state.raw.serverid = reader.uint(4);
state.raw.ip = this.readUnrealString(reader);
state.raw.port = reader.uint(4);
state.raw.queryport = reader.uint(4);
state.name = this.readUnrealString(reader,true);
state.map = this.readUnrealString(reader,true);
state.raw.gametype = this.readUnrealString(reader,true);
state.raw.numplayers = reader.uint(4);
state.maxplayers = reader.uint(4);
this.readExtraInfo(reader,state);
c();
});
},
(c) => {
this.sendPacket(1,true,(b) => {
constructor() {
super();
this.encoding = 'latin1';
}
run(state) {
async.series([
(c) => {
this.sendPacket(0,true,(b) => {
const reader = this.reader(b);
state.raw.mutators = [];
state.raw.rules = {};
while(!reader.done()) {
state.raw.serverid = reader.uint(4);
state.raw.ip = this.readUnrealString(reader);
state.raw.port = reader.uint(4);
state.raw.queryport = reader.uint(4);
state.name = this.readUnrealString(reader,true);
state.map = this.readUnrealString(reader,true);
state.raw.gametype = this.readUnrealString(reader,true);
state.raw.numplayers = reader.uint(4);
state.maxplayers = reader.uint(4);
this.readExtraInfo(reader,state);
c();
});
},
(c) => {
this.sendPacket(1,true,(b) => {
const reader = this.reader(b);
state.raw.mutators = [];
state.raw.rules = {};
while(!reader.done()) {
const key = this.readUnrealString(reader,true);
const value = this.readUnrealString(reader,true);
if(key === 'Mutator') state.raw.mutators.push(value);
else state.raw.rules[key] = value;
}
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';
if('GamePassword' in state.raw.rules)
state.password = state.raw.rules.GamePassword !== 'True';
c();
});
},
(c) => {
this.sendPacket(2,false,(b) => {
c();
});
},
(c) => {
this.sendPacket(2,false,(b) => {
const reader = this.reader(b);
while(!reader.done()) {
while(!reader.done()) {
const player = {};
player.id = reader.uint(4);
if(!player.id) break;
if(player.id === 0) {
// Unreal2XMP Player (ID is always 0)
reader.skip(4);
}
player.name = this.readUnrealString(reader,true);
player.ping = reader.uint(4);
player.score = reader.int(4);
reader.skip(4); // stats ID
player.id = reader.uint(4);
if(!player.id) break;
if(player.id === 0) {
// Unreal2XMP Player (ID is always 0)
reader.skip(4);
}
player.name = this.readUnrealString(reader,true);
player.ping = reader.uint(4);
player.score = reader.int(4);
reader.skip(4); // stats ID
// Extra data for Unreal2XMP players
if(player.id === 0) {
// Extra data for Unreal2XMP players
if(player.id === 0) {
const count = reader.uint(1);
for(let iField = 0; iField < count; iField++) {
for(let iField = 0; iField < count; iField++) {
const key = this.readUnrealString(reader,true);
const value = this.readUnrealString(reader,true);
player[key] = value;
}
}
if(player.id === 0 && player.name === 'Player') {
// these show up in ut2004 queries, but aren't real
// not even really sure why they're there
continue;
}
player[key] = value;
}
}
(player.ping ? state.players : state.bots).push(player);
}
c();
});
},
(c) => {
this.finish(state);
}
]);
}
readExtraInfo(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(reader, stripColor) {
if(player.id === 0 && player.name === 'Player') {
// these show up in ut2004 queries, but aren't real
// not even really sure why they're there
continue;
}
(player.ping ? state.players : state.bots).push(player);
}
c();
});
},
(c) => {
this.finish(state);
}
]);
}
readExtraInfo(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(reader, stripColor) {
let length = reader.uint(1);
let out;
if(length < 0x80) {
//out = reader.string({length:length});
out = '';
if(length > 0) out = reader.string();
} else {
length = (length&0x7f)*2;
if(this.debug) {
console.log("UCS2 STRING");
console.log(length,reader.buffer.slice(reader.i,reader.i+length));
}
out = 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,'');
if(length < 0x80) {
//out = reader.string({length:length});
out = '';
if(length > 0) out = reader.string();
} else {
length = (length&0x7f)*2;
if(this.debug) {
console.log("UCS2 STRING");
console.log(length,reader.buffer.slice(reader.i,reader.i+length));
}
out = reader.string({encoding:'ucs2',length:length});
}
return out;
}
sendPacket(type,required,callback) {
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(type,required,callback) {
const outbuffer = Buffer.from([0x79,0,0,0,type]);
const packets = [];
this.udpSend(outbuffer,(buffer) => {
this.udpSend(outbuffer,(buffer) => {
const reader = this.reader(buffer);
const header = reader.uint(4);
const iType = reader.uint(1);
if(iType !== type) return;
packets.push(reader.rest());
}, () => {
if(!packets.length && required) return;
callback(Buffer.concat(packets));
return true;
});
}
if(iType !== type) return;
packets.push(reader.rest());
}, () => {
if(!packets.length && required) return;
callback(Buffer.concat(packets));
return true;
});
}
}
module.exports = Unreal2;

View File

@ -1,9 +1,9 @@
class Ut2004 extends require('./unreal2') {
readExtraInfo(reader,state) {
state.raw.ping = reader.uint(4);
state.raw.flags = reader.uint(4);
state.raw.skill = reader.uint(2);
}
readExtraInfo(reader,state) {
state.raw.ping = reader.uint(4);
state.raw.flags = reader.uint(4);
state.raw.skill = reader.uint(2);
}
}
module.exports = Ut2004;

View File

@ -1,45 +1,45 @@
class Ut3 extends require('./gamespy3') {
finalizeState(state) {
super.finalizeState(state);
finalizeState(state) {
super.finalizeState(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
});
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
});
const split = (a) => {
let s = a.split('\x1c');
s = s.filter((e) => { return e });
return s;
};
if('custom_mutators' in state.raw) state.raw['custom_mutators'] = split(state.raw['custom_mutators']);
if('stock_mutators' in state.raw) state.raw['stock_mutators'] = split(state.raw['stock_mutators']);
if('map' in state.raw) state.map = state.raw.map;
}
const split = (a) => {
let s = a.split('\x1c');
s = s.filter((e) => { return e });
return s;
};
if('custom_mutators' in state.raw) state.raw['custom_mutators'] = split(state.raw['custom_mutators']);
if('stock_mutators' in state.raw) state.raw['stock_mutators'] = split(state.raw['stock_mutators']);
if('map' in state.raw) state.map = state.raw.map;
}
}
module.exports = Ut3;

View File

@ -1,217 +1,217 @@
const async = require('async'),
Bzip2 = require('compressjs').Bzip2;
Bzip2 = require('compressjs').Bzip2;
class Valve extends require('./core') {
constructor() {
super();
constructor() {
super();
this.options.port = 27015;
// legacy goldsrc info response -- basically not used by ANYTHING now,
// as most (all?) goldsrc servers respond with the source info reponse
// delete in a few years if nothing ends up using it anymore
this.goldsrcInfo = false;
// unfortunately, the split format from goldsrc is still around, but we
// can detect that during the query
this.goldsrcSplits = false;
this.options.port = 27015;
// some mods require a challenge, but don't provide them in the new format
// at all, use the old dedicated challenge query if needed
this.legacyChallenge = false;
// cs:go provides an annoying additional bot that looks exactly like a player,
// but is always named "Max Players"
this.isCsGo = false;
// 2006 engines don't pass packet switching size in split packet header
// while all others do, this need is detected automatically
this._skipSizeInSplitHeader = false;
// legacy goldsrc info response -- basically not used by ANYTHING now,
// as most (all?) goldsrc servers respond with the source info reponse
// delete in a few years if nothing ends up using it anymore
this.goldsrcInfo = false;
this._challenge = '';
}
// unfortunately, the split format from goldsrc is still around, but we
// can detect that during the query
this.goldsrcSplits = false;
run(state) {
async.series([
(c) => { this.queryInfo(state,c); },
(c) => { this.queryChallenge(state,c); },
(c) => { this.queryPlayers(state,c); },
(c) => { this.queryRules(state,c); },
(c) => { this.finish(state); }
]);
}
// some mods require a challenge, but don't provide them in the new format
// at all, use the old dedicated challenge query if needed
this.legacyChallenge = false;
queryInfo(state,c) {
this.sendPacket(
0x54,false,'Source Engine Query\0',
this.goldsrcInfo ? 0x6D : 0x49,
(b) => {
// cs:go provides an annoying additional bot that looks exactly like a player,
// but is always named "Max Players"
this.isCsGo = false;
// 2006 engines don't pass packet switching size in split packet header
// while all others do, this need is detected automatically
this._skipSizeInSplitHeader = false;
this._challenge = '';
}
run(state) {
async.series([
(c) => { this.queryInfo(state,c); },
(c) => { this.queryChallenge(state,c); },
(c) => { this.queryPlayers(state,c); },
(c) => { this.queryRules(state,c); },
(c) => { this.finish(state); }
]);
}
queryInfo(state,c) {
this.sendPacket(
0x54,false,'Source Engine Query\0',
this.goldsrcInfo ? 0x6D : 0x49,
(b) => {
const reader = this.reader(b);
if(this.goldsrcInfo) 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(this.goldsrcInfo) state.raw.address = reader.string();
else state.raw.protocol = reader.uint(1);
if(this.goldsrcInfo) state.raw.protocol = reader.uint(1);
else state.raw.numbots = 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);
state.raw.listentype = reader.uint(1);
state.raw.environment = reader.uint(1);
if(!this.goldsrcInfo) {
state.raw.listentype = String.fromCharCode(state.raw.listentype);
state.raw.environment = String.fromCharCode(state.raw.environment);
}
if(this.goldsrcInfo) state.raw.protocol = reader.uint(1);
else state.raw.numbots = reader.uint(1);
state.password = !!reader.uint(1);
if(this.goldsrcInfo) {
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);
state.raw.listentype = reader.uint(1);
state.raw.environment = reader.uint(1);
if(!this.goldsrcInfo) {
state.raw.listentype = String.fromCharCode(state.raw.listentype);
state.raw.environment = String.fromCharCode(state.raw.environment);
}
if(this.goldsrcInfo) {
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();
state.password = !!reader.uint(1);
if(this.goldsrcInfo) {
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(this.goldsrcInfo) {
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();
const 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(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);
}
// from https://developer.valvesoftware.com/wiki/Server_queries
if(
state.raw.protocol === 7 && (
state.raw.steamappid === 215
|| state.raw.steamappid === 17550
|| state.raw.steamappid === 17700
|| state.raw.steamappid === 240
)
) {
this._skipSizeInSplitHeader = true;
}
if(this.debug) {
console.log("STEAM APPID: "+state.raw.steamappid);
console.log("PROTOCOL: "+state.raw.protocol);
}
if(state.raw.protocol === 48) {
if(this.debug) console.log("GOLDSRC DETECTED - USING MODIFIED SPLIT FORMAT");
this.goldsrcSplits = true;
}
// from https://developer.valvesoftware.com/wiki/Server_queries
if(
state.raw.protocol === 7 && (
state.raw.steamappid === 215
|| state.raw.steamappid === 17550
|| state.raw.steamappid === 17700
|| state.raw.steamappid === 240
)
) {
this._skipSizeInSplitHeader = true;
}
if(this.debug) {
console.log("STEAM APPID: "+state.raw.steamappid);
console.log("PROTOCOL: "+state.raw.protocol);
}
if(state.raw.protocol === 48) {
if(this.debug) console.log("GOLDSRC DETECTED - USING MODIFIED SPLIT FORMAT");
this.goldsrcSplits = true;
}
c();
}
);
}
c();
}
);
}
queryChallenge(state,c) {
if(this.legacyChallenge) {
this.sendPacket(0x57,false,false,0x41,(b) => {
// sendPacket will catch the response packet and
// save the challenge for us
c();
});
} else {
c();
}
}
queryChallenge(state,c) {
if(this.legacyChallenge) {
this.sendPacket(0x57,false,false,0x41,(b) => {
// sendPacket will catch the response packet and
// save the challenge for us
c();
});
} else {
c();
}
}
queryPlayers(state,c) {
this.sendPacket(0x55,true,false,0x44,(b) => {
queryPlayers(state,c) {
this.sendPacket(0x55,true,false,0x44,(b) => {
const reader = this.reader(b);
const num = reader.uint(1);
for(let i = 0; i < num; i++) {
reader.skip(1);
for(let i = 0; i < num; i++) {
reader.skip(1);
const name = reader.string();
const score = reader.int(4);
const time = reader.float();
if(this.debug) console.log("Found player: "+name+" "+score+" "+time);
if(this.debug) console.log("Found player: "+name+" "+score+" "+time);
// connecting players don't count as players.
if(!name) continue;
// connecting players don't count as players.
if(!name) continue;
(time === -1 ? state.bots : state.players).push({
name:name, score:score, time:time
});
}
(time === -1 ? state.bots : state.players).push({
name:name, score:score, time:time
});
}
if(this.isCsGo && state.players.length === 1 && state.players[0].name === 'Max Players') {
if(this.debug) console.log("CSGO server using limited player details");
state.players = [];
for(let i = 0; i < state.raw.numplayers; i++) {
state.players.push({});
}
}
// if we didn't find the bots, iterate
// through and guess which ones they are
if(!state.bots.length && state.raw.numbots) {
if(this.isCsGo && state.players.length === 1 && state.players[0].name === 'Max Players') {
if(this.debug) console.log("CSGO server using limited player details");
state.players = [];
for(let i = 0; i < state.raw.numplayers; i++) {
state.players.push({});
}
}
// if we didn't find the bots, iterate
// through and guess which ones they are
if(!state.bots.length && state.raw.numbots) {
let maxTime = 0;
for (const player of state.players) {
maxTime = Math.max(player.time,maxTime);
}
for(let i = 0; i < state.players.length; i++) {
const 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--;
}
}
for (const player of state.players) {
maxTime = Math.max(player.time,maxTime);
}
for(let i = 0; i < state.players.length; i++) {
const 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();
});
}
c();
});
}
queryRules(state,c) {
this.sendPacket(0x56,true,false,0x45,(b) => {
queryRules(state,c) {
this.sendPacket(0x56,true,false,0x45,(b) => {
const reader = this.reader(b);
const num = reader.uint(2);
state.raw.rules = {};
for(let i = 0; i < num; i++) {
state.raw.rules = {};
for(let i = 0; i < num; i++) {
const key = reader.string();
const value = reader.string();
state.raw.rules[key] = value;
}
c();
}, () => {
// no rules were returned after timeout --
// the server probably has them disabled
// ignore the timeout
c();
return true;
});
}
state.raw.rules[key] = value;
}
c();
}, () => {
// no rules were returned after timeout --
// the server probably has them disabled
// ignore the timeout
c();
return true;
});
}
sendPacket(type,sendChallenge,payload,expect,callback,ontimeout) {
const packetStorage = {};
sendPacket(type,sendChallenge,payload,expect,callback,ontimeout) {
const packetStorage = {};
const receivedFull = (reader) => {
const type = reader.uint(1);
@ -301,28 +301,28 @@ class Valve extends require('./core') {
}
};
const send = (c) => {
if(typeof payload === 'string') payload = Buffer.from(payload,'binary');
const send = (c) => {
if(typeof payload === 'string') payload = Buffer.from(payload,'binary');
const challengeLength = sendChallenge ? 4 : 0;
const payloadLength = payload ? payload.length : 0;
const b = Buffer.alloc(5 + challengeLength + payloadLength);
b.writeInt32LE(-1, 0);
b.writeUInt8(type, 4);
if(sendChallenge) {
let challenge = this._challenge;
if(!challenge) challenge = 0xffffffff;
if(this.byteorder === 'le') b.writeUInt32LE(challenge, 5);
else b.writeUInt32BE(challenge, 5);
}
if(payloadLength) payload.copy(b, 5+challengeLength);
b.writeInt32LE(-1, 0);
b.writeUInt8(type, 4);
this.udpSend(b,receivedOne,ontimeout);
};
if(sendChallenge) {
let challenge = this._challenge;
if(!challenge) challenge = 0xffffffff;
if(this.byteorder === 'le') b.writeUInt32LE(challenge, 5);
else b.writeUInt32BE(challenge, 5);
}
if(payloadLength) payload.copy(b, 5+challengeLength);
this.udpSend(b,receivedOne,ontimeout);
};
send();
}
}
}
module.exports = Valve;

View File

@ -1,90 +1,90 @@
class Ventrilo extends require('./core') {
constructor() {
super();
this.byteorder = 'be';
}
run(state) {
this.sendCommand(2,'',(data) => {
state.raw = splitFields(data.toString());
for (const client of state.raw.CLIENTS) {
client.name = client.NAME;
delete client.NAME;
client.ping = parseInt(client.PING);
delete client.PING;
state.players.push(client);
}
delete state.raw.CLIENTS;
if('NAME' in state.raw) state.name = state.raw.NAME;
if('MAXCLIENTS' in state.raw) state.maxplayers = state.raw.MAXCLIENTS;
if(this.trueTest(state.raw.AUTH)) state.password = true;
this.finish(state);
});
}
sendCommand(cmd,password,c) {
constructor() {
super();
this.byteorder = 'be';
}
run(state) {
this.sendCommand(2,'',(data) => {
state.raw = splitFields(data.toString());
for (const client of state.raw.CLIENTS) {
client.name = client.NAME;
delete client.NAME;
client.ping = parseInt(client.PING);
delete client.PING;
state.players.push(client);
}
delete state.raw.CLIENTS;
if('NAME' in state.raw) state.name = state.raw.NAME;
if('MAXCLIENTS' in state.raw) state.maxplayers = state.raw.MAXCLIENTS;
if(this.trueTest(state.raw.AUTH)) state.password = true;
this.finish(state);
});
}
sendCommand(cmd,password,c) {
const body = Buffer.alloc(16);
body.write(password,0,15,'utf8');
body.write(password,0,15,'utf8');
const encrypted = encrypt(cmd,body);
const packets = {};
this.udpSend(encrypted, (buffer) => {
if(buffer.length < 20) return;
this.udpSend(encrypted, (buffer) => {
if(buffer.length < 20) return;
const data = decrypt(buffer);
if(data.zero !== 0) return;
packets[data.packetNum] = data.body;
if(Object.keys(packets).length !== data.packetTotal) return;
if(data.zero !== 0) return;
packets[data.packetNum] = data.body;
if(Object.keys(packets).length !== data.packetTotal) return;
const out = [];
for(let i = 0; i < data.packetTotal; i++) {
if(!(i in packets)) return this.fatal('Missing packet #'+i);
out.push(packets[i]);
}
c(Buffer.concat(out));
return true;
});
}
for(let i = 0; i < data.packetTotal; i++) {
if(!(i in packets)) return this.fatal('Missing packet #'+i);
out.push(packets[i]);
}
c(Buffer.concat(out));
return true;
});
}
}
function splitFields(str,subMode) {
let splitter,delim;
if(subMode) {
splitter = '=';
delim = ',';
} else {
splitter = ': ';
delim = '\n';
}
let splitter,delim;
if(subMode) {
splitter = '=';
delim = ',';
} else {
splitter = ': ';
delim = '\n';
}
const split = str.split(delim);
const out = {};
if(!subMode) {
out.CHANNELS = [];
out.CLIENTS = [];
}
for (const one of split) {
if(!subMode) {
out.CHANNELS = [];
out.CLIENTS = [];
}
for (const one of split) {
const equal = one.indexOf(splitter);
const key = equal === -1 ? one : one.substr(0,equal);
if(!key || key === '\0') continue;
if(!key || key === '\0') continue;
const value = equal === -1 ? '' : one.substr(equal+splitter.length);
if(!subMode && key === 'CHANNEL') out.CHANNELS.push(splitFields(value,true));
else if(!subMode && key === 'CLIENT') out.CLIENTS.push(splitFields(value,true));
else out[key] = value;
}
return out;
if(!subMode && key === 'CHANNEL') out.CHANNELS.push(splitFields(value,true));
else if(!subMode && key === 'CLIENT') out.CLIENTS.push(splitFields(value,true));
else out[key] = value;
}
return out;
}
function randInt(min,max) {
return Math.floor(Math.random()*(max-min+1)+min)
return Math.floor(Math.random()*(max-min+1)+min)
}
function crc(body) {
let crc = 0;
for(let i = 0; i < body.length; i++) {
crc = crc_table[crc>>8] ^ body.readUInt8(i) ^ (crc<<8);
crc &= 0xffff;
}
return crc;
let crc = 0;
for(let i = 0; i < body.length; i++) {
crc = crc_table[crc>>8] ^ body.readUInt8(i) ^ (crc<<8);
crc &= 0xffff;
}
return crc;
}
function encrypt(cmd,body) {
@ -94,36 +94,36 @@ function encrypt(cmd,body) {
const bodyKeyAdd = randInt(1,0xff);
const header = Buffer.alloc(20);
header.writeUInt8(headerKeyStart,0);
header.writeUInt8(headerKeyAdd,1);
header.writeUInt16BE(cmd,4);
header.writeUInt16BE(body.length,8);
header.writeUInt16BE(body.length,10);
header.writeUInt16BE(1,12);
header.writeUInt16BE(0,14);
header.writeUInt8(bodyKeyStart,16);
header.writeUInt8(bodyKeyAdd,17);
header.writeUInt16BE(crc(body),18);
header.writeUInt8(headerKeyStart,0);
header.writeUInt8(headerKeyAdd,1);
header.writeUInt16BE(cmd,4);
header.writeUInt16BE(body.length,8);
header.writeUInt16BE(body.length,10);
header.writeUInt16BE(1,12);
header.writeUInt16BE(0,14);
header.writeUInt8(bodyKeyStart,16);
header.writeUInt8(bodyKeyAdd,17);
header.writeUInt16BE(crc(body),18);
let offset = headerKeyStart;
for(let i = 2; i < header.length; i++) {
for(let i = 2; i < header.length; i++) {
let val = header.readUInt8(i);
val += code_head.charCodeAt(offset) + ((i-2) % 5);
val = val & 0xff;
header.writeUInt8(val,i);
offset = (offset+headerKeyAdd) & 0xff;
}
val += code_head.charCodeAt(offset) + ((i-2) % 5);
val = val & 0xff;
header.writeUInt8(val,i);
offset = (offset+headerKeyAdd) & 0xff;
}
offset = bodyKeyStart;
for(let i = 0; i < body.length; i++) {
let val = body.readUInt8(i);
val += code_body.charCodeAt(offset) + (i % 72);
val = val & 0xff;
body.writeUInt8(val,i);
offset = (offset+bodyKeyAdd) & 0xff;
}
return Buffer.concat([header,body]);
offset = bodyKeyStart;
for(let i = 0; i < body.length; i++) {
let val = body.readUInt8(i);
val += code_body.charCodeAt(offset) + (i % 72);
val = val & 0xff;
body.writeUInt8(val,i);
offset = (offset+bodyKeyAdd) & 0xff;
}
return Buffer.concat([header,body]);
}
function decrypt(data) {
const header = data.slice(0,20);
@ -131,107 +131,107 @@ function decrypt(data) {
const headerKeyStart = header.readUInt8(0);
const headerKeyAdd = header.readUInt8(1);
let offset = headerKeyStart;
for(let i = 2; i < header.length; i++) {
let val = header.readUInt8(i);
val -= code_head.charCodeAt(offset) + ((i-2) % 5);
val = val & 0xff;
header.writeUInt8(val,i);
offset = (offset+headerKeyAdd) & 0xff;
}
let offset = headerKeyStart;
for(let i = 2; i < header.length; i++) {
let val = header.readUInt8(i);
val -= code_head.charCodeAt(offset) + ((i-2) % 5);
val = val & 0xff;
header.writeUInt8(val,i);
offset = (offset+headerKeyAdd) & 0xff;
}
const bodyKeyStart = header.readUInt8(16);
const bodyKeyAdd = header.readUInt8(17);
offset = bodyKeyStart;
for(let i = 0; i < body.length; i++) {
let val = body.readUInt8(i);
val -= code_body.charCodeAt(offset) + (i % 72);
val = val & 0xff;
body.writeUInt8(val,i);
offset = (offset+bodyKeyAdd) & 0xff;
}
offset = bodyKeyStart;
for(let i = 0; i < body.length; i++) {
let val = body.readUInt8(i);
val -= code_body.charCodeAt(offset) + (i % 72);
val = val & 0xff;
body.writeUInt8(val,i);
offset = (offset+bodyKeyAdd) & 0xff;
}
// header format:
// key, zero, cmd, echo, totallength, thislength
// totalpacket, packetnum, body key, crc
return {
zero: header.readUInt16BE(2),
cmd: header.readUInt16BE(4),
packetTotal: header.readUInt16BE(12),
packetNum: header.readUInt16BE(14),
body: body
};
// header format:
// key, zero, cmd, echo, totallength, thislength
// totalpacket, packetnum, body key, crc
return {
zero: header.readUInt16BE(2),
cmd: header.readUInt16BE(4),
packetTotal: header.readUInt16BE(12),
packetNum: header.readUInt16BE(14),
body: body
};
}
const code_head =
'\x80\xe5\x0e\x38\xba\x63\x4c\x99\x88\x63\x4c\xd6\x54\xb8\x65\x7e'+
'\xbf\x8a\xf0\x17\x8a\xaa\x4d\x0f\xb7\x23\x27\xf6\xeb\x12\xf8\xea'+
'\x17\xb7\xcf\x52\x57\xcb\x51\xcf\x1b\x14\xfd\x6f\x84\x38\xb5\x24'+
'\x11\xcf\x7a\x75\x7a\xbb\x78\x74\xdc\xbc\x42\xf0\x17\x3f\x5e\xeb'+
'\x74\x77\x04\x4e\x8c\xaf\x23\xdc\x65\xdf\xa5\x65\xdd\x7d\xf4\x3c'+
'\x4c\x95\xbd\xeb\x65\x1c\xf4\x24\x5d\x82\x18\xfb\x50\x86\xb8\x53'+
'\xe0\x4e\x36\x96\x1f\xb7\xcb\xaa\xaf\xea\xcb\x20\x27\x30\x2a\xae'+
'\xb9\x07\x40\xdf\x12\x75\xc9\x09\x82\x9c\x30\x80\x5d\x8f\x0d\x09'+
'\xa1\x64\xec\x91\xd8\x8a\x50\x1f\x40\x5d\xf7\x08\x2a\xf8\x60\x62'+
'\xa0\x4a\x8b\xba\x4a\x6d\x00\x0a\x93\x32\x12\xe5\x07\x01\x65\xf5'+
'\xff\xe0\xae\xa7\x81\xd1\xba\x25\x62\x61\xb2\x85\xad\x7e\x9d\x3f'+
'\x49\x89\x26\xe5\xd5\xac\x9f\x0e\xd7\x6e\x47\x94\x16\x84\xc8\xff'+
'\x44\xea\x04\x40\xe0\x33\x11\xa3\x5b\x1e\x82\xff\x7a\x69\xe9\x2f'+
'\xfb\xea\x9a\xc6\x7b\xdb\xb1\xff\x97\x76\x56\xf3\x52\xc2\x3f\x0f'+
'\xb6\xac\x77\xc4\xbf\x59\x5e\x80\x74\xbb\xf2\xde\x57\x62\x4c\x1a'+
'\xff\x95\x6d\xc7\x04\xa2\x3b\xc4\x1b\x72\xc7\x6c\x82\x60\xd1\x0d';
'\x80\xe5\x0e\x38\xba\x63\x4c\x99\x88\x63\x4c\xd6\x54\xb8\x65\x7e'+
'\xbf\x8a\xf0\x17\x8a\xaa\x4d\x0f\xb7\x23\x27\xf6\xeb\x12\xf8\xea'+
'\x17\xb7\xcf\x52\x57\xcb\x51\xcf\x1b\x14\xfd\x6f\x84\x38\xb5\x24'+
'\x11\xcf\x7a\x75\x7a\xbb\x78\x74\xdc\xbc\x42\xf0\x17\x3f\x5e\xeb'+
'\x74\x77\x04\x4e\x8c\xaf\x23\xdc\x65\xdf\xa5\x65\xdd\x7d\xf4\x3c'+
'\x4c\x95\xbd\xeb\x65\x1c\xf4\x24\x5d\x82\x18\xfb\x50\x86\xb8\x53'+
'\xe0\x4e\x36\x96\x1f\xb7\xcb\xaa\xaf\xea\xcb\x20\x27\x30\x2a\xae'+
'\xb9\x07\x40\xdf\x12\x75\xc9\x09\x82\x9c\x30\x80\x5d\x8f\x0d\x09'+
'\xa1\x64\xec\x91\xd8\x8a\x50\x1f\x40\x5d\xf7\x08\x2a\xf8\x60\x62'+
'\xa0\x4a\x8b\xba\x4a\x6d\x00\x0a\x93\x32\x12\xe5\x07\x01\x65\xf5'+
'\xff\xe0\xae\xa7\x81\xd1\xba\x25\x62\x61\xb2\x85\xad\x7e\x9d\x3f'+
'\x49\x89\x26\xe5\xd5\xac\x9f\x0e\xd7\x6e\x47\x94\x16\x84\xc8\xff'+
'\x44\xea\x04\x40\xe0\x33\x11\xa3\x5b\x1e\x82\xff\x7a\x69\xe9\x2f'+
'\xfb\xea\x9a\xc6\x7b\xdb\xb1\xff\x97\x76\x56\xf3\x52\xc2\x3f\x0f'+
'\xb6\xac\x77\xc4\xbf\x59\x5e\x80\x74\xbb\xf2\xde\x57\x62\x4c\x1a'+
'\xff\x95\x6d\xc7\x04\xa2\x3b\xc4\x1b\x72\xc7\x6c\x82\x60\xd1\x0d';
const code_body =
'\x82\x8b\x7f\x68\x90\xe0\x44\x09\x19\x3b\x8e\x5f\xc2\x82\x38\x23'+
'\x6d\xdb\x62\x49\x52\x6e\x21\xdf\x51\x6c\x76\x37\x86\x50\x7d\x48'+
'\x1f\x65\xe7\x52\x6a\x88\xaa\xc1\x32\x2f\xf7\x54\x4c\xaa\x6d\x7e'+
'\x6d\xa9\x8c\x0d\x3f\xff\x6c\x09\xb3\xa5\xaf\xdf\x98\x02\xb4\xbe'+
'\x6d\x69\x0d\x42\x73\xe4\x34\x50\x07\x30\x79\x41\x2f\x08\x3f\x42'+
'\x73\xa7\x68\xfa\xee\x88\x0e\x6e\xa4\x70\x74\x22\x16\xae\x3c\x81'+
'\x14\xa1\xda\x7f\xd3\x7c\x48\x7d\x3f\x46\xfb\x6d\x92\x25\x17\x36'+
'\x26\xdb\xdf\x5a\x87\x91\x6f\xd6\xcd\xd4\xad\x4a\x29\xdd\x7d\x59'+
'\xbd\x15\x34\x53\xb1\xd8\x50\x11\x83\x79\x66\x21\x9e\x87\x5b\x24'+
'\x2f\x4f\xd7\x73\x34\xa2\xf7\x09\xd5\xd9\x42\x9d\xf8\x15\xdf\x0e'+
'\x10\xcc\x05\x04\x35\x81\xb2\xd5\x7a\xd2\xa0\xa5\x7b\xb8\x75\xd2'+
'\x35\x0b\x39\x8f\x1b\x44\x0e\xce\x66\x87\x1b\x64\xac\xe1\xca\x67'+
'\xb4\xce\x33\xdb\x89\xfe\xd8\x8e\xcd\x58\x92\x41\x50\x40\xcb\x08'+
'\xe1\x15\xee\xf4\x64\xfe\x1c\xee\x25\xe7\x21\xe6\x6c\xc6\xa6\x2e'+
'\x52\x23\xa7\x20\xd2\xd7\x28\x07\x23\x14\x24\x3d\x45\xa5\xc7\x90'+
'\xdb\x77\xdd\xea\x38\x59\x89\x32\xbc\x00\x3a\x6d\x61\x4e\xdb\x29';
'\x82\x8b\x7f\x68\x90\xe0\x44\x09\x19\x3b\x8e\x5f\xc2\x82\x38\x23'+
'\x6d\xdb\x62\x49\x52\x6e\x21\xdf\x51\x6c\x76\x37\x86\x50\x7d\x48'+
'\x1f\x65\xe7\x52\x6a\x88\xaa\xc1\x32\x2f\xf7\x54\x4c\xaa\x6d\x7e'+
'\x6d\xa9\x8c\x0d\x3f\xff\x6c\x09\xb3\xa5\xaf\xdf\x98\x02\xb4\xbe'+
'\x6d\x69\x0d\x42\x73\xe4\x34\x50\x07\x30\x79\x41\x2f\x08\x3f\x42'+
'\x73\xa7\x68\xfa\xee\x88\x0e\x6e\xa4\x70\x74\x22\x16\xae\x3c\x81'+
'\x14\xa1\xda\x7f\xd3\x7c\x48\x7d\x3f\x46\xfb\x6d\x92\x25\x17\x36'+
'\x26\xdb\xdf\x5a\x87\x91\x6f\xd6\xcd\xd4\xad\x4a\x29\xdd\x7d\x59'+
'\xbd\x15\x34\x53\xb1\xd8\x50\x11\x83\x79\x66\x21\x9e\x87\x5b\x24'+
'\x2f\x4f\xd7\x73\x34\xa2\xf7\x09\xd5\xd9\x42\x9d\xf8\x15\xdf\x0e'+
'\x10\xcc\x05\x04\x35\x81\xb2\xd5\x7a\xd2\xa0\xa5\x7b\xb8\x75\xd2'+
'\x35\x0b\x39\x8f\x1b\x44\x0e\xce\x66\x87\x1b\x64\xac\xe1\xca\x67'+
'\xb4\xce\x33\xdb\x89\xfe\xd8\x8e\xcd\x58\x92\x41\x50\x40\xcb\x08'+
'\xe1\x15\xee\xf4\x64\xfe\x1c\xee\x25\xe7\x21\xe6\x6c\xc6\xa6\x2e'+
'\x52\x23\xa7\x20\xd2\xd7\x28\x07\x23\x14\x24\x3d\x45\xa5\xc7\x90'+
'\xdb\x77\xdd\xea\x38\x59\x89\x32\xbc\x00\x3a\x6d\x61\x4e\xdb\x29';
const crc_table = [
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
];
module.exports = Ventrilo;

View File

@ -1,13 +1,13 @@
class Warsow extends require('./quake3') {
finalizeState(state) {
super.finalizeState(state);
if(state.players) {
for(const player of state.players) {
player.team = player.address;
delete player.address;
}
}
}
finalizeState(state) {
super.finalizeState(state);
if(state.players) {
for(const player of state.players) {
player.team = player.address;
delete player.address;
}
}
}
}
module.exports = Warsow;