More async conversion

This commit is contained in:
mmorrison 2019-01-10 06:03:07 -06:00
parent 484e99b29c
commit efe12a00aa
25 changed files with 774 additions and 858 deletions

View file

@ -1,7 +1,8 @@
const Iconv = require('iconv-lite'), const Iconv = require('iconv-lite'),
Long = require('long'), Long = require('long'),
Core = require('../protocols/core'), Core = require('../protocols/core'),
Buffer = require('buffer'); Buffer = require('buffer'),
Varint = require('varint');
function readUInt64BE(buffer,offset) { function readUInt64BE(buffer,offset) {
const high = buffer.readUInt32BE(offset); const high = buffer.readUInt32BE(offset);
@ -126,6 +127,12 @@ class Reader {
return r; return r;
} }
varint() {
const out = Varint.decode(this.buffer, this.i);
this.i += Varint.decode.bytes;
return out;
}
/** @returns Buffer */ /** @returns Buffer */
part(bytes) { part(bytes) {
let r; let r;

10
package-lock.json generated
View file

@ -38,11 +38,6 @@
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
}, },
"async": {
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz",
"integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0="
},
"asynckit": { "asynckit": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@ -358,11 +353,6 @@
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
}, },
"jquery": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz",
"integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg=="
},
"jsbn": { "jsbn": {
"version": "0.1.1", "version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",

View file

@ -24,12 +24,10 @@
"node": ">=6.0.0" "node": ">=6.0.0"
}, },
"dependencies": { "dependencies": {
"async": "^0.9.2",
"cheerio": "^1.0.0-rc.2", "cheerio": "^1.0.0-rc.2",
"compressjs": "^1.0.2", "compressjs": "^1.0.2",
"gbxremote": "^0.1.4", "gbxremote": "^0.1.4",
"iconv-lite": "^0.4.18", "iconv-lite": "^0.4.18",
"jquery": "^3.3.1",
"long": "^2.4.0", "long": "^2.4.0",
"minimist": "^1.2.0", "minimist": "^1.2.0",
"moment": "^2.21.0", "moment": "^2.21.0",

View file

@ -10,7 +10,7 @@ class Armagetron extends Core {
async run(state) { async run(state) {
const b = Buffer.from([0,0x35,0,0,0,0,0,0x11]); const b = Buffer.from([0,0x35,0,0,0,0,0,0x11]);
await this.udpSend(b,(buffer) => { const buffer = await this.udpSend(b,b => b);
const reader = this.reader(buffer); const reader = this.reader(buffer);
reader.skip(6); reader.skip(6);
@ -36,9 +36,6 @@ class Armagetron extends Core {
state.raw.options = this.stripColorCodes(this.readString(reader)); state.raw.options = this.stripColorCodes(this.readString(reader));
state.raw.uri = this.readString(reader); state.raw.uri = this.readString(reader);
state.raw.globalids = this.readString(reader); state.raw.globalids = this.readString(reader);
this.finish(state);
return null;
});
} }
readUInt(reader) { readUInt(reader) {

View file

@ -92,7 +92,8 @@ class Core extends EventEmitter {
() => reject(new Error(timeoutMsg + " - Timed out after " + timeoutMs + "ms")), () => reject(new Error(timeoutMsg + " - Timed out after " + timeoutMs + "ms")),
timeoutMs timeoutMs
); );
promise.finally(cancelTimeout).then(resolve,reject); promise = promise.finally(cancelTimeout);
promise.then(resolve,reject);
}); });
} }
@ -204,8 +205,9 @@ class Core extends EventEmitter {
} }
/** /**
* @param {function(Socket):Promise} fn * @template T
* @returns {Promise<Socket>} * @param {function(Socket):Promise<T>} fn
* @returns {Promise<T>}
*/ */
async withTcp(fn) { async withTcp(fn) {
const address = this.options.address; const address = this.options.address;
@ -263,10 +265,11 @@ class Core extends EventEmitter {
} }
/** /**
* @template T
* @param {Socket} socket * @param {Socket} socket
* @param {Buffer} buffer * @param {Buffer|string} buffer
* @param {function(Buffer):boolean} ondata * @param {function(Buffer):T} ondata
* @returns {Promise} * @returns Promise<T>
*/ */
async tcpSend(socket,buffer,ondata) { async tcpSend(socket,buffer,ondata) {
return await this.timedPromise( return await this.timedPromise(
@ -354,14 +357,22 @@ class Core extends EventEmitter {
} }
request(params) { request(params) {
const promise = requestAsync({ let promise = requestAsync({
...params, ...params,
timeout: this.options.socketTimeout timeout: this.options.socketTimeout,
resolveWithFullResponse: true
}); });
const cancelAsyncLeak = this.addAsyncLeak(() => { const cancelAsyncLeak = this.addAsyncLeak(() => {
promise.cancel(); promise.cancel();
}); });
promise.finally(cancelAsyncLeak); this.debugLog(log => {
log(() => params.uri+" HTTP-->");
promise
.then((response) => log(params.uri+" <--HTTP " + response.statusCode))
.catch(()=>{});
});
promise = promise.finally(cancelAsyncLeak);
promise = promise.then(response => response.body);
return promise; return promise;
} }

View file

@ -1,16 +1,13 @@
const request = require('request'), const Core = require('./core');
Core = require('./core');
class GeneShift extends Core { class GeneShift extends Core {
run(state) { async run(state) {
request({ const body = await this.request({
uri: 'http://geneshift.net/game/receiveLobby.php', 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/>'); const split = body.split('<br/>');
let found = false; let found = null;
for(const line of split) { for(const line of split) {
const fields = line.split('::'); const fields = line.split('::');
const ip = fields[2]; const ip = fields[2];
@ -21,7 +18,9 @@ class GeneShift extends Core {
} }
} }
if(!found) return this.fatal('Server not found in list'); if(found === null) {
throw new Error('Server not found in list');
}
state.raw.countrycode = found[0]; state.raw.countrycode = found[0];
state.raw.country = found[1]; state.raw.country = found[1];
@ -45,9 +44,6 @@ class GeneShift extends Core {
for(let i = 0; i < state.raw.numplayers; i++) { for(let i = 0; i < state.raw.numplayers; i++) {
state.players.push({}); state.players.push({});
} }
this.finish(state);
});
} }
} }

View file

@ -1,15 +1,16 @@
const Gamespy3 = require('./gamespy3'); const Gamespy3 = require('./gamespy3');
// supposedly, gamespy3 is the "official" query protocol for jcmp, // supposedly, gamespy3 is the "official" query protocol for jcmp,
// but it's broken (requires useOnlySingleSplit), and doesn't include player names // but it's broken (requires useOnlySingleSplit), and may not include some player names
class Jc2mp extends Gamespy3 { class Jc2mp extends Gamespy3 {
constructor() { constructor() {
super(); super();
this.useOnlySingleSplit = true; this.useOnlySingleSplit = true;
this.isJc2mp = true; this.isJc2mp = true;
this.encoding = 'utf8';
} }
async run(state) { async run(state) {
super.run(state); await super.run(state);
if(!state.players.length && parseInt(state.raw.numplayers)) { if(!state.players.length && parseInt(state.raw.numplayers)) {
for(let i = 0; i < parseInt(state.raw.numplayers); i++) { for(let i = 0; i < parseInt(state.raw.numplayers); i++) {
state.players.push({}); state.players.push({});

View file

@ -1,20 +1,12 @@
const request = require('request'), const Core = require('./core');
Core = require('./core');
class Kspdmp extends Core { class Kspdmp extends Core {
run(state) { async run(state) {
request({ const body = await this.request({
uri: 'http://'+this.options.address+':'+this.options.port_query, uri: 'http://'+this.options.address+':'+this.options.port_query
timeout: this.options.socketTimeout });
}, (e,r,body) => {
if(e) return this.fatal('HTTP error');
let json;
try {
json = JSON.parse(body);
} catch(e) {
return this.fatal('Invalid JSON');
}
const json = JSON.parse(body);
for (const one of json.players) { for (const one of json.players) {
state.players.push({name:one.nickname,team:one.team}); state.players.push({name:one.nickname,team:one.team});
} }
@ -30,9 +22,6 @@ class Kspdmp extends Core {
state.players.push({name:name}); state.players.push({name:name});
} }
} }
this.finish(state);
});
} }
} }

View file

@ -6,13 +6,15 @@ class M2mp extends Core {
this.encoding = 'latin1'; this.encoding = 'latin1';
} }
run(state) { async run(state) {
this.udpSend('M2MP',(buffer) => { const body = await this.udpSend('M2MP',(buffer) => {
const reader = this.reader(buffer); const reader = this.reader(buffer);
const header = reader.string({length: 4}); const header = reader.string({length: 4});
if (header !== 'M2MP') return; if (header !== 'M2MP') return;
return reader.rest();
});
const reader = this.reader(body);
state.name = this.readString(reader); state.name = this.readString(reader);
state.raw.numplayers = this.readString(reader); state.raw.numplayers = this.readString(reader);
state.maxplayers = this.readString(reader); state.maxplayers = this.readString(reader);
@ -26,10 +28,6 @@ class M2mp extends Core {
name:name name:name
}); });
} }
this.finish(state);
return true;
});
} }
readString(reader) { readString(reader) {

View file

@ -1,79 +1,49 @@
const varint = require('varint'), const Core = require('./core'),
async = require('async'), Varint = require('varint');
Core = require('./core');
function varIntBuffer(num) {
return Buffer.from(varint.encode(num));
}
function buildPacket(id,data) {
if(!data) data = Buffer.from([]);
const idBuffer = varIntBuffer(id);
return Buffer.concat([
varIntBuffer(data.length+idBuffer.length),
idBuffer,
data
]);
}
class Minecraft extends Core { class Minecraft extends Core {
run(state) { async run(state) {
/** @type Buffer */
let receivedData;
async.series([
(c) => {
// build and send handshake and status TCP packet
const portBuf = Buffer.alloc(2); 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 addressBuf = Buffer.from(this.options.host,'utf8');
const bufs = [ const bufs = [
varIntBuffer(4), this.varIntBuffer(4),
varIntBuffer(addressBuf.length), this.varIntBuffer(addressBuf.length),
addressBuf, addressBuf,
portBuf, portBuf,
varIntBuffer(1) this.varIntBuffer(1)
]; ];
const outBuffer = Buffer.concat([ const outBuffer = Buffer.concat([
buildPacket(0,Buffer.concat(bufs)), this.buildPacket(0,Buffer.concat(bufs)),
buildPacket(0) this.buildPacket(0)
]); ]);
this.tcpSend(outBuffer, (data) => { const data = await this.withTcp(async socket => {
if(data.length < 10) return false; return await this.tcpSend(socket, outBuffer, data => {
const expected = varint.decode(data); if(data.length < 10) return;
data = data.slice(varint.decode.bytes); const reader = this.reader(data);
if(data.length < expected) return false; const length = reader.varint();
receivedData = data; if(data.length < length) return;
c(); return reader.rest();
return true; });
}); });
},
(c) => {
// parse response
let data = receivedData; const reader = this.reader(data);
const packetId = varint.decode(data);
const packetId = reader.varint();
this.debugLog("Packet ID: "+packetId); this.debugLog("Packet ID: "+packetId);
data = data.slice(varint.decode.bytes);
const strLen = varint.decode(data); const strLen = reader.varint();
this.debugLog("String Length: "+strLen); this.debugLog("String Length: "+strLen);
data = data.slice(varint.decode.bytes);
const str = data.toString('utf8'); const str = reader.rest().toString('utf8');
this.debugLog(str); this.debugLog(str);
let json; const json = JSON.parse(str);
try {
json = JSON.parse(str);
delete json.favicon; delete json.favicon;
} catch(e) {
return this.fatal('Invalid JSON');
}
state.raw = json; state.raw = json;
state.maxplayers = json.players.max; state.maxplayers = json.players.max;
@ -88,9 +58,18 @@ class Minecraft extends Core {
while(state.players.length < json.players.online) { while(state.players.length < json.players.online) {
state.players.push({}); state.players.push({});
} }
this.finish(state);
} }
varIntBuffer(num) {
return Buffer.from(Varint.encode(num));
}
buildPacket(id,data) {
if(!data) data = Buffer.from([]);
const idBuffer = this.varIntBuffer(id);
return Buffer.concat([
this.varIntBuffer(data.length+idBuffer.length),
idBuffer,
data
]); ]);
} }
} }

View file

@ -6,8 +6,9 @@ class Mumble extends Core {
this.options.socketTimeout = 5000; this.options.socketTimeout = 5000;
} }
run(state) { async run(state) {
this.tcpSend('json', (buffer) => { const json = await this.withTcp(async socket => {
return await this.tcpSend(socket, 'json', (buffer) => {
if (buffer.length < 10) return; if (buffer.length < 10) return;
const str = buffer.toString(); const str = buffer.toString();
let json; let json;
@ -17,6 +18,9 @@ class Mumble extends Core {
// probably not all here yet // probably not all here yet
return; return;
} }
return json;
});
});
state.raw = json; state.raw = json;
state.name = json.name; state.name = json.name;
@ -31,10 +35,6 @@ class Mumble extends Core {
state.players.push(user); state.players.push(user);
} }
} }
this.finish(state);
return true;
});
} }
cleanComment(str) { cleanComment(str) {

View file

@ -6,10 +6,12 @@ class MumblePing extends Core {
this.byteorder = 'be'; this.byteorder = 'be';
} }
run(state) { async run(state) {
this.udpSend('\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08', (buffer) => { const data = await this.udpSend('\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08', (buffer) => {
if(buffer.length < 24) return; if (buffer.length >= 24) return buffer;
const reader = this.reader(buffer); });
const reader = this.reader(data);
reader.skip(1); reader.skip(1);
state.raw.versionMajor = reader.uint(1); state.raw.versionMajor = reader.uint(1);
state.raw.versionMinor = reader.uint(1); state.raw.versionMinor = reader.uint(1);
@ -21,9 +23,6 @@ class MumblePing extends Core {
for(let i = 0; i < state.raw.numplayers; i++) { for(let i = 0; i < state.raw.numplayers; i++) {
state.players.push({}); state.players.push({});
} }
this.finish(state);
return true;
});
} }
} }

View file

@ -1,5 +1,4 @@
const gbxremote = require('gbxremote'), const gbxremote = require('gbxremote'),
async = require('async'),
Core = require('./core'); Core = require('./core');
class Nadeo extends Core { class Nadeo extends Core {
@ -7,79 +6,91 @@ class Nadeo extends Core {
super(); super();
this.options.port = 2350; this.options.port = 2350;
this.options.port_query = 5000; this.options.port_query = 5000;
this.gbxclient = false;
} }
reset() { async run(state) {
super.reset(); await this.withClient(async client => {
if(this.gbxclient) { await this.methodCall(client, 'Authenticate', this.options.login, this.options.password);
this.gbxclient.terminate(); //const data = this.methodCall(client, 'GetStatus');
this.gbxclient = false;
} {
const results = await this.methodCall(client, 'GetServerOptions');
state.name = this.stripColors(results.Name);
state.password = (results.Password !== 'No password');
state.maxplayers = results.CurrentMaxPlayers;
state.raw.maxspectators = results.CurrentMaxSpectators;
} }
run(state) { {
const cmds = [ const results = await this.methodCall(client, 'GetCurrentMapInfo');
['Connect'], state.map = this.stripColors(results.Name);
['Authenticate', this.options.login,this.options.password], state.raw.mapUid = results.UId;
['GetStatus'], // 1
['GetPlayerList',10000,0], // 2
['GetServerOptions'], // 3
['GetCurrentMapInfo'], // 4
['GetCurrentGameInfo'], // 5
['GetNextMapInfo'] // 6
];
const results = [];
async.eachSeries(cmds, (cmdset,c) => {
const cmd = cmdset[0];
const params = cmdset.slice(1);
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();
});
} }
}, () => {
{
const results = await this.methodCall(client, 'GetCurrentGameInfo');
let gamemode = ''; let gamemode = '';
const igm = results[5].GameMode; const igm = results.GameMode;
if(igm === 0) gamemode="Rounds"; if(igm === 0) gamemode="Rounds";
if(igm === 1) gamemode="Time Attack"; if(igm === 1) gamemode="Time Attack";
if(igm === 2) gamemode="Team"; if(igm === 2) gamemode="Team";
if(igm === 3) gamemode="Laps"; if(igm === 3) gamemode="Laps";
if(igm === 4) gamemode="Stunts"; if(igm === 4) gamemode="Stunts";
if(igm === 5) gamemode="Cup"; 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.raw.maxspectators = results[3].CurrentMaxSpectators;
state.map = this.stripColors(results[4].Name);
state.raw.mapUid = results[4].UId;
state.raw.gametype = gamemode; state.raw.gametype = gamemode;
state.raw.players = results[2]; state.raw.mapcount = results.NbChallenge;
state.raw.mapcount = results[5].NbChallenge; }
state.raw.nextmapName = this.stripColors(results[6].Name);
state.raw.nextmapUid = results[6].UId;
{
const results = await this.methodCall(client, 'GetNextMapInfo');
state.raw.nextmapName = this.stripColors(results.Name);
state.raw.nextmapUid = results.UId;
}
state.raw.players = await this.methodCall(client, 'GetPlayerList', 10000, 0);
for (const player of state.raw.players) { for (const player of state.raw.players) {
state.players.push({ state.players.push({
name:this.stripColors(player.Name || player.NickName) name:this.stripColors(player.Name || player.NickName)
}); });
} }
this.finish(state);
}); });
} }
async withClient(fn) {
const socket = gbxremote.createClient(this.options.port_query, this.options.host);
const cancelAsyncLeak = this.addAsyncLeak(() => socket.terminate());
try {
await this.timedPromise(
new Promise((resolve,reject) => {
socket.on('connect', resolve);
socket.on('error', e => reject(new Error('GBX Remote Connection Error: ' + e)));
socket.on('close', () => reject(new Error('GBX Remote Connection Refused')));
}),
this.options.socketTimeout,
'GBX Remote Opening'
);
return await fn(socket);
} finally {
cancelAsyncLeak();
socket.terminate();
}
}
async methodCall(client, ...cmdset) {
const cmd = cmdset[0];
const params = cmdset.slice(1);
return await this.timedPromise(
new Promise(async (resolve,reject) => {
client.methodCall(cmd, params, (err, value) => {
if (err) reject('XMLRPC error ' + JSON.stringify(err));
resolve(value);
});
}),
this.options.socketTimeout,
'GBX Method Call'
);
}
stripColors(str) { stripColors(str) {
return str.replace(/\$([0-9a-f]{3}|[a-z])/gi,''); return str.replace(/\$([0-9a-f]{3}|[a-z])/gi,'');
} }

View file

@ -1,12 +1,10 @@
const async = require('async'), const moment = require('moment'),
moment = require('moment'),
Core = require('./core'); Core = require('./core');
class OpenTtd extends Core { class OpenTtd extends Core {
run(state) { async run(state) {
async.series([ {
(c) => { const [reader, version] = await this.query(0, 1, 1, 4);
this.query(0,1,1,4,(reader, version) => {
if (version >= 4) { if (version >= 4) {
const numGrf = reader.uint(1); const numGrf = reader.uint(1);
state.raw.grfs = []; state.raw.grfs = [];
@ -52,19 +50,12 @@ class OpenTtd extends Core {
); );
state.raw.dedicated = !!reader.uint(1); state.raw.dedicated = !!reader.uint(1);
}
c(); {
}); const [reader,version] = await this.query(2,3,-1,-1);
},
(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 // we don't know how to deal with companies outside version 6
if(version !== 6) return c(); if(version === 6) {
state.raw.companies = []; state.raw.companies = [];
const numCompanies = reader.uint(1); const numCompanies = reader.uint(1);
for (let iCompany = 0; iCompany < numCompanies; iCompany++) { for (let iCompany = 0; iCompany < numCompanies; iCompany++) {
@ -78,6 +69,9 @@ class OpenTtd extends Core {
company.performance = reader.uint(2); company.performance = reader.uint(2);
company.password = !!reader.uint(1); company.password = !!reader.uint(1);
const vehicle_types = ['train', 'truck', 'bus', 'aircraft', 'ship'];
const station_types = ['station', 'truckbay', 'busstation', 'airport', 'dock'];
company.vehicles = {}; company.vehicles = {};
for (const type of vehicle_types) { for (const type of vehicle_types) {
company.vehicles[type] = reader.uint(2); company.vehicles[type] = reader.uint(2);
@ -90,42 +84,33 @@ class OpenTtd extends Core {
company.clients = reader.string(); company.clients = reader.string();
state.raw.companies.push(company); state.raw.companies.push(company);
} }
c();
});
},
(c) => {
this.finish(state);
} }
]); }
} }
query(type,expected,minver,maxver,done) { async query(type,expected,minver,maxver) {
const b = Buffer.from([0x03,0x00,type]); const b = Buffer.from([0x03,0x00,type]);
this.udpSend(b,(buffer) => { return await this.udpSend(b,(buffer) => {
const reader = this.reader(buffer); const reader = this.reader(buffer);
const packetLen = reader.uint(2); const packetLen = reader.uint(2);
if(packetLen !== buffer.length) { if(packetLen !== buffer.length) {
this.fatal('Invalid reported packet length: '+packetLen+' '+buffer.length); this.debugLog('Invalid reported packet length: '+packetLen+' '+buffer.length);
return true; return;
} }
const packetType = reader.uint(1); const packetType = reader.uint(1);
if(packetType !== expected) { if(packetType !== expected) {
this.fatal('Unexpected response packet type: '+packetType); this.debugLog('Unexpected response packet type: '+packetType);
return true; return;
} }
const protocolVersion = reader.uint(1); const protocolVersion = reader.uint(1);
if((minver !== -1 && protocolVersion < minver) || (maxver !== -1 && protocolVersion > maxver)) { if((minver !== -1 && protocolVersion < minver) || (maxver !== -1 && protocolVersion > maxver)) {
this.fatal('Unknown protocol version: '+protocolVersion+' Expected: '+minver+'-'+maxver); throw new Error('Unknown protocol version: '+protocolVersion+' Expected: '+minver+'-'+maxver);
return true;
} }
done(reader,protocolVersion); return [reader,protocolVersion];
return true;
}); });
} }

View file

@ -68,7 +68,9 @@ class Quake2 extends Core {
player.frags = parseInt(args.shift()); player.frags = parseInt(args.shift());
player.ping = parseInt(args.shift()); player.ping = parseInt(args.shift());
player.name = args.shift() || ''; player.name = args.shift() || '';
if (!player.name) delete player.name;
player.address = args.shift() || ''; player.address = args.shift() || '';
if (!player.address) delete player.address;
} }
(player.ping ? state.players : state.bots).push(player); (player.ping ? state.players : state.bots).push(player);

View file

@ -6,7 +6,8 @@ class Quake3 extends Quake2 {
this.sendHeader = 'getstatus'; this.sendHeader = 'getstatus';
this.responseHeader = 'statusResponse'; this.responseHeader = 'statusResponse';
} }
finalizeState(state) { async run(state) {
await super.run(state);
state.name = this.stripColors(state.name); state.name = this.stripColors(state.name);
for(const key of Object.keys(state.raw)) { for(const key of Object.keys(state.raw)) {
state.raw[key] = this.stripColors(state.raw[key]); state.raw[key] = this.stripColors(state.raw[key]);

View file

@ -6,15 +6,21 @@ class Starmade extends Core {
this.encoding = 'latin1'; this.encoding = 'latin1';
this.byteorder = 'be'; this.byteorder = 'be';
} }
run(state) {
async run(state) {
const b = Buffer.from([0x00,0x00,0x00,0x09,0x2a,0xff,0xff,0x01,0x6f,0x00,0x00,0x00,0x00]); const b = Buffer.from([0x00,0x00,0x00,0x09,0x2a,0xff,0xff,0x01,0x6f,0x00,0x00,0x00,0x00]);
this.tcpSend(b,(buffer) => { const payload = await this.withTcp(async socket => {
return await this.tcpSend(socket, b, buffer => {
if (buffer.length < 4) return;
const reader = this.reader(buffer); const reader = this.reader(buffer);
if(buffer.length < 4) return false;
const packetLength = reader.uint(4); const packetLength = reader.uint(4);
if(buffer.length < packetLength+12) return false; if (buffer.length < packetLength + 12) return;
return reader.rest();
});
});
const reader = this.reader(payload);
const data = []; const data = [];
state.raw.data = data; state.raw.data = data;
@ -39,8 +45,7 @@ class Starmade extends Core {
} }
if(data.length < 9) { if(data.length < 9) {
this.fatal("Not enough units in data packet"); throw new Error("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[3] === 'number') state.raw.version = data[3].toFixed(7).replace(/0+$/, '');
@ -54,10 +59,6 @@ class Starmade extends Core {
state.players.push({}); state.players.push({});
} }
} }
this.finish(state);
return true;
});
} }
} }

View file

@ -1,28 +1,25 @@
const async = require('async'), const Core = require('./core');
Core = require('./core');
class Teamspeak2 extends Core { class Teamspeak2 extends Core {
run(state) { async run(state) {
async.series([ await this.withTcp(async socket => {
(c) => { {
this.sendCommand('sel '+this.options.port, (data) => { const data = await this.sendCommand(socket, 'sel '+this.options.port);
if(data !== '[TS]') this.fatal('Invalid header'); if(data !== '[TS]') throw new Error('Invalid header');
c(); }
});
}, {
(c) => { const data = await this.sendCommand(socket, 'si');
this.sendCommand('si', (data) => {
for (const line of data.split('\r\n')) { for (const line of data.split('\r\n')) {
const equals = line.indexOf('='); const equals = line.indexOf('=');
const key = equals === -1 ? line : line.substr(0,equals); const key = equals === -1 ? line : line.substr(0,equals);
const value = equals === -1 ? '' : line.substr(equals+1); const value = equals === -1 ? '' : line.substr(equals+1);
state.raw[key] = value; state.raw[key] = value;
} }
c(); }
});
}, {
(c) => { const data = await this.sendCommand(socket, 'pl');
this.sendCommand('pl', (data) => {
const split = data.split('\r\n'); const split = data.split('\r\n');
const fields = split.shift().split('\t'); const fields = split.shift().split('\t');
for (const line of split) { for (const line of split) {
@ -38,11 +35,10 @@ class Teamspeak2 extends Core {
}); });
state.players.push(player); state.players.push(player);
} }
c(); }
});
}, {
(c) => { const data = await this.sendCommand(socket, 'cl');
this.sendCommand('cl', (data) => {
const split = data.split('\r\n'); const split = data.split('\r\n');
const fields = split.shift().split('\t'); const fields = split.shift().split('\t');
state.raw.channels = []; state.raw.channels = [];
@ -58,20 +54,15 @@ class Teamspeak2 extends Core {
}); });
state.raw.channels.push(channel); state.raw.channels.push(channel);
} }
c(); }
}); });
},
(c) => {
this.finish(state);
} }
]);
} async sendCommand(socket,cmd) {
sendCommand(cmd,c) { return await this.tcpSend(socket, cmd+'\x0A', buffer => {
this.tcpSend(cmd+'\x0A', (buffer) => {
if(buffer.length < 6) return; if(buffer.length < 6) return;
if(buffer.slice(-6).toString() !== '\r\nOK\r\n') return; if(buffer.slice(-6).toString() !== '\r\nOK\r\n') return;
c(buffer.slice(0,-6).toString()); return buffer.slice(0,-6).toString();
return true;
}); });
} }
} }

View file

@ -1,26 +1,23 @@
const async = require('async'), const Core = require('./core');
Core = require('./core');
class Teamspeak3 extends Core { class Teamspeak3 extends Core {
run(state) { async run(state) {
async.series([ await this.withTcp(async socket => {
(c) => { {
this.sendCommand('use port='+this.options.port, (data) => { const data = await this.sendCommand(socket, 'use port='+this.options.port, true);
const split = data.split('\n\r'); const split = data.split('\n\r');
if(split[0] !== 'TS3') this.fatal('Invalid header'); if(split[0] !== 'TS3') throw new Error('Invalid header');
c(); }
}, true);
}, {
(c) => { const data = await this.sendCommand(socket, 'serverinfo');
this.sendCommand('serverinfo', (data) => {
state.raw = data[0]; state.raw = data[0];
if('virtualserver_name' in state.raw) state.name = state.raw.virtualserver_name; if('virtualserver_name' in state.raw) state.name = state.raw.virtualserver_name;
if('virtualserver_maxclients' in state.raw) state.maxplayers = state.raw.virtualserver_maxclients; if('virtualserver_maxclients' in state.raw) state.maxplayers = state.raw.virtualserver_maxclients;
c(); }
});
}, {
(c) => { const list = await this.sendCommand(socket, 'clientlist');
this.sendCommand('clientlist', (list) => {
for (const client of list) { for (const client of list) {
client.name = client.client_nickname; client.name = client.client_nickname;
delete client.client_nickname; delete client.client_nickname;
@ -28,33 +25,27 @@ class Teamspeak3 extends Core {
state.players.push(client); state.players.push(client);
} }
} }
c(); }
});
}, {
(c) => { const data = await this.sendCommand(socket, 'channellist -topic');
this.sendCommand('channellist -topic', (data) => {
state.raw.channels = data; state.raw.channels = data;
c(); }
}); });
},
(c) => {
this.finish(state);
} }
]);
} async sendCommand(socket,cmd,raw) {
sendCommand(cmd,c,raw) { const body = await this.tcpSend(socket, cmd+'\x0A', (buffer) => {
this.tcpSend(cmd+'\x0A', (buffer) => {
if (buffer.length < 21) return; if (buffer.length < 21) return;
if (buffer.slice(-21).toString() !== '\n\rerror id=0 msg=ok\n\r') return; if (buffer.slice(-21).toString() !== '\n\rerror id=0 msg=ok\n\r') return;
const body = buffer.slice(0,-21).toString(); return buffer.slice(0, -21).toString();
});
let out;
if(raw) { if(raw) {
out = body; return body;
} else { } else {
const segments = body.split('|'); const segments = body.split('|');
out = []; const out = [];
for (const line of segments) { for (const line of segments) {
const split = line.split(' '); const split = line.split(' ');
const unit = {}; const unit = {};
@ -67,12 +58,8 @@ class Teamspeak3 extends Core {
} }
out.push(unit); out.push(unit);
} }
return out;
} }
c(out);
return true;
});
} }
} }

View file

@ -1,25 +1,17 @@
const request = require('request'), const Core = require('./core');
Core = require('./core');
class Terraria extends Core { class Terraria extends Core {
run(state) { async run(state) {
request({ const body = await this.request({
uri: 'http://'+this.options.address+':'+this.options.port_query+'/v2/server/status', uri: 'http://'+this.options.address+':'+this.options.port_query+'/v2/server/status',
timeout: this.options.socketTimeout,
qs: { qs: {
players: 'true', players: 'true',
token: this.options.token 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'); const json = JSON.parse(body);
if(json.status !== 200) throw new Error('Invalid status');
for (const one of json.players) { for (const one of json.players) {
state.players.push({name:one.nickname,team:one.team}); state.players.push({name:one.nickname,team:one.team});
@ -28,9 +20,6 @@ class Terraria extends Core {
state.name = json.name; state.name = json.name;
state.raw.port = json.port; state.raw.port = json.port;
state.raw.numplayers = json.playercount; state.raw.numplayers = json.playercount;
this.finish(state);
});
} }
} }

View file

@ -5,15 +5,19 @@ class Tribes1 extends Core {
super(); super();
this.encoding = 'latin1'; this.encoding = 'latin1';
} }
run(state) {
async run(state) {
const queryBuffer = Buffer.from('b++'); const queryBuffer = Buffer.from('b++');
this.udpSend(queryBuffer,(buffer) => { const reader = await this.udpSend(queryBuffer,(buffer) => {
const reader = this.reader(buffer); const reader = this.reader(buffer);
const header = reader.string({length: 4}); const header = reader.string({length: 4});
if (header !== 'c++b') { if (header !== 'c++b') {
this.fatal('Header response does not match: ' + header); this.debugLog('Header response does not match: ' + header);
return true; return;
} }
return reader;
});
state.raw.gametype = this.readString(reader); state.raw.gametype = this.readString(reader);
state.raw.version = this.readString(reader); state.raw.version = this.readString(reader);
state.name = this.readString(reader); state.name = this.readString(reader);
@ -76,10 +80,6 @@ class Tribes1 extends Core {
} }
state.players.push(playerInfo); state.players.push(playerInfo);
} }
this.finish(state);
return true;
});
} }
readFieldList(reader) { readFieldList(reader) {
const str = this.readString(reader); const str = this.readString(reader);

View file

@ -7,7 +7,8 @@ class Tribes1Master extends Core {
super(); super();
this.encoding = 'latin1'; this.encoding = 'latin1';
} }
run(state) {
async run(state) {
const queryBuffer = Buffer.from([ const queryBuffer = Buffer.from([
0x10, // standard header 0x10, // standard header
0x03, // dump servers 0x03, // dump servers
@ -18,28 +19,27 @@ class Tribes1Master extends Core {
let parts = new Map(); let parts = new Map();
let total = 0; let total = 0;
this.udpSend(queryBuffer,(buffer) => { const full = await this.udpSend(queryBuffer,(buffer) => {
const reader = this.reader(buffer); const reader = this.reader(buffer);
const header = reader.uint(2); const header = reader.uint(2);
if (header !== 0x0610) { if (header !== 0x0610) {
this.fatal('Header response does not match: ' + header.toString(16)); this.debugLog('Header response does not match: ' + header.toString(16));
return true; return;
} }
const num = reader.uint(1); const num = reader.uint(1);
const t = reader.uint(1); const t = reader.uint(1);
if (t <= 0 || (total > 0 && t !== total)) { if (t <= 0 || (total > 0 && t !== total)) {
this.fatal('Conflicting total: ' + t); throw new Error('Conflicting packet total: ' + t);
return true;
} }
total = t; total = t;
if (num < 1 || num > total) { if (num < 1 || num > total) {
this.fatal('Invalid packet number: ' + num + ' ' + total); this.debugLog('Invalid packet number: ' + num + ' ' + total);
return true; return;
} }
if (parts.has(num)) { if (parts.has(num)) {
this.fatal('Duplicate part: ' + num); this.debugLog('Duplicate part: ' + num);
return true; return;
} }
reader.skip(2); // challenge (0x0201) reader.skip(2); // challenge (0x0201)
@ -49,9 +49,11 @@ class Tribes1Master extends Core {
if (parts.size === total) { if (parts.size === total) {
const ordered = []; const ordered = [];
for (let i = 1; i <= total; i++) ordered.push(parts.get(i)); for (let i = 1; i <= total; i++) ordered.push(parts.get(i));
const full = Buffer.concat(ordered); return Buffer.concat(ordered);
const fullReader = this.reader(full); }
});
const fullReader = this.reader(full);
state.raw.name = this.readString(fullReader); state.raw.name = this.readString(fullReader);
state.raw.motd = this.readString(fullReader); state.raw.motd = this.readString(fullReader);
@ -62,8 +64,7 @@ class Tribes1Master extends Core {
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
const six = fullReader.uint(1); const six = fullReader.uint(1);
if (six !== 6) { if (six !== 6) {
this.fatal('Expecting 6'); throw new Error('Expecting 6');
return true;
} }
const ip = fullReader.uint(4); const ip = fullReader.uint(4);
const port = fullReader.uint(2); const port = fullReader.uint(2);
@ -71,10 +72,6 @@ class Tribes1Master extends Core {
state.raw.servers.push(ipStr+":"+port); state.raw.servers.push(ipStr+":"+port);
} }
} }
this.finish(state);
return true;
}
});
} }
readString(reader) { readString(reader) {
const length = reader.uint(1); const length = reader.uint(1);

View file

@ -1,15 +1,13 @@
const async = require('async'), const Core = require('./core');
Core = require('./core');
class Unreal2 extends Core { class Unreal2 extends Core {
constructor() { constructor() {
super(); super();
this.encoding = 'latin1'; this.encoding = 'latin1';
} }
run(state) { async run(state) {
async.series([ {
(c) => { const b = await this.sendPacket(0, true);
this.sendPacket(0,true,(b) => {
const reader = this.reader(b); const reader = this.reader(b);
state.raw.serverid = reader.uint(4); state.raw.serverid = reader.uint(4);
state.raw.ip = this.readUnrealString(reader); state.raw.ip = this.readUnrealString(reader);
@ -21,12 +19,10 @@ class Unreal2 extends Core {
state.raw.numplayers = reader.uint(4); state.raw.numplayers = reader.uint(4);
state.maxplayers = reader.uint(4); state.maxplayers = reader.uint(4);
this.readExtraInfo(reader, state); this.readExtraInfo(reader, state);
}
c(); {
}); const b = await this.sendPacket(1,true);
},
(c) => {
this.sendPacket(1,true,(b) => {
const reader = this.reader(b); const reader = this.reader(b);
state.raw.mutators = []; state.raw.mutators = [];
state.raw.rules = {}; state.raw.rules = {};
@ -36,15 +32,12 @@ class Unreal2 extends Core {
if(key === 'Mutator') state.raw.mutators.push(value); if(key === 'Mutator') state.raw.mutators.push(value);
else state.raw.rules[key] = value; else state.raw.rules[key] = value;
} }
if('GamePassword' in state.raw.rules) if('GamePassword' in state.raw.rules)
state.password = state.raw.rules.GamePassword !== 'True'; state.password = state.raw.rules.GamePassword !== 'True';
}
c(); {
}); const b = await this.sendPacket(2,false);
},
(c) => {
this.sendPacket(2,false,(b) => {
const reader = this.reader(b); const reader = this.reader(b);
while(!reader.done()) { while(!reader.done()) {
@ -78,14 +71,9 @@ class Unreal2 extends Core {
(player.ping ? state.players : state.bots).push(player); (player.ping ? state.players : state.bots).push(player);
} }
c();
});
},
(c) => {
this.finish(state);
} }
]);
} }
readExtraInfo(reader,state) { readExtraInfo(reader,state) {
this.debugLog(log => { this.debugLog(log => {
log("UNREAL2 EXTRA INFO:"); log("UNREAL2 EXTRA INFO:");
@ -96,6 +84,7 @@ class Unreal2 extends Core {
log(reader.buffer.slice(reader.i)); log(reader.buffer.slice(reader.i));
}); });
} }
readUnrealString(reader, stripColor) { readUnrealString(reader, stripColor) {
let length = reader.uint(1); let length = reader.uint(1);
let out; let out;
@ -120,11 +109,12 @@ class Unreal2 extends Core {
return out; return out;
} }
sendPacket(type,required,callback) {
async sendPacket(type,required) {
const outbuffer = Buffer.from([0x79,0,0,0,type]); const outbuffer = Buffer.from([0x79,0,0,0,type]);
const packets = []; const packets = [];
this.udpSend(outbuffer,(buffer) => { return await this.udpSend(outbuffer,(buffer) => {
const reader = this.reader(buffer); const reader = this.reader(buffer);
const header = reader.uint(4); const header = reader.uint(4);
const iType = reader.uint(1); const iType = reader.uint(1);
@ -132,8 +122,7 @@ class Unreal2 extends Core {
packets.push(reader.rest()); packets.push(reader.rest());
}, () => { }, () => {
if(!packets.length && required) return; if(!packets.length && required) return;
callback(Buffer.concat(packets)); return Buffer.concat(packets);
return true;
}); });
} }
} }

View file

@ -5,8 +5,9 @@ class Ventrilo extends Core {
super(); super();
this.byteorder = 'be'; this.byteorder = 'be';
} }
run(state) {
this.sendCommand(2,'',(data) => { async run(state) {
const data = await this.sendCommand(2,'');
state.raw = splitFields(data.toString()); state.raw = splitFields(data.toString());
for (const client of state.raw.CLIENTS) { for (const client of state.raw.CLIENTS) {
client.name = client.NAME; client.name = client.NAME;
@ -20,16 +21,14 @@ class Ventrilo extends Core {
if('NAME' in state.raw) state.name = state.raw.NAME; if('NAME' in state.raw) state.name = state.raw.NAME;
if('MAXCLIENTS' in state.raw) state.maxplayers = state.raw.MAXCLIENTS; if('MAXCLIENTS' in state.raw) state.maxplayers = state.raw.MAXCLIENTS;
if(this.trueTest(state.raw.AUTH)) state.password = true; if(this.trueTest(state.raw.AUTH)) state.password = true;
this.finish(state);
});
} }
sendCommand(cmd,password,c) { async sendCommand(cmd,password) {
const body = Buffer.alloc(16); const body = Buffer.alloc(16);
body.write(password,0,15,'utf8'); body.write(password,0,15,'utf8');
const encrypted = encrypt(cmd,body); const encrypted = encrypt(cmd,body);
const packets = {}; const packets = {};
this.udpSend(encrypted, (buffer) => { return await this.udpSend(encrypted, (buffer) => {
if(buffer.length < 20) return; if(buffer.length < 20) return;
const data = decrypt(buffer); const data = decrypt(buffer);
@ -39,11 +38,10 @@ class Ventrilo extends Core {
const out = []; const out = [];
for(let i = 0; i < data.packetTotal; i++) { for(let i = 0; i < data.packetTotal; i++) {
if(!(i in packets)) return this.fatal('Missing packet #'+i); if(!(i in packets)) throw new Error('Missing packet #'+i);
out.push(packets[i]); out.push(packets[i]);
} }
c(Buffer.concat(out)); return Buffer.concat(out);
return true;
}); });
} }
} }

View file

@ -1,8 +1,8 @@
const Quake3 = require('./quake3'); const Quake3 = require('./quake3');
class Warsow extends Quake3 { class Warsow extends Quake3 {
finalizeState(state) { async run(state) {
super.finalizeState(state); await super.run(state);
if(state.players) { if(state.players) {
for(const player of state.players) { for(const player of state.players) {
player.team = player.address; player.team = player.address;