2019-01-07 01:52:03 +01:00
|
|
|
const Core = require('./core');
|
|
|
|
|
|
|
|
class Gamespy2 extends Core {
|
2017-08-09 12:32:09 +02:00
|
|
|
constructor() {
|
|
|
|
super();
|
|
|
|
this.encoding = 'latin1';
|
|
|
|
this.byteorder = 'be';
|
|
|
|
}
|
2014-10-29 08:02:03 +01:00
|
|
|
|
2019-01-09 12:35:11 +01:00
|
|
|
async run(state) {
|
|
|
|
// Parse info
|
|
|
|
{
|
|
|
|
const body = await this.sendPacket([0xff, 0, 0]);
|
|
|
|
const reader = this.reader(body);
|
|
|
|
while (!reader.done()) {
|
|
|
|
const key = reader.string();
|
|
|
|
const value = reader.string();
|
|
|
|
if (!key) break;
|
|
|
|
state.raw[key] = value;
|
|
|
|
}
|
2019-01-12 12:45:09 +01:00
|
|
|
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 ('hostport' in state.raw) state.gamePort = parseInt(state.raw.hostport);
|
2019-01-09 12:35:11 +01:00
|
|
|
}
|
2017-08-09 12:32:09 +02:00
|
|
|
|
2019-01-09 12:35:11 +01:00
|
|
|
// Parse players
|
|
|
|
{
|
|
|
|
const body = await this.sendPacket([0, 0xff, 0]);
|
|
|
|
const reader = this.reader(body);
|
2021-04-11 05:27:33 +02:00
|
|
|
for (const rawPlayer of this.readFieldData(reader)) {
|
|
|
|
state.players.push(rawPlayer);
|
|
|
|
}
|
2019-01-09 12:35:11 +01:00
|
|
|
}
|
2014-10-29 08:02:03 +01:00
|
|
|
|
2019-01-09 12:35:11 +01:00
|
|
|
// Parse teams
|
|
|
|
{
|
|
|
|
const body = await this.sendPacket([0, 0, 0xff]);
|
|
|
|
const reader = this.reader(body);
|
|
|
|
state.raw.teams = this.readFieldData(reader);
|
|
|
|
}
|
2019-01-12 11:43:36 +01:00
|
|
|
|
|
|
|
// Special case for america's army 1 and 2
|
|
|
|
// both use gamename = "armygame"
|
|
|
|
if (state.raw.gamename === 'armygame') {
|
|
|
|
const stripColor = (str) => {
|
|
|
|
// uses unreal 2 color codes
|
|
|
|
return str.replace(/\x1b...|[\x00-\x1a]/g,'');
|
|
|
|
};
|
|
|
|
state.name = stripColor(state.name);
|
|
|
|
state.map = stripColor(state.map);
|
|
|
|
for(const key of Object.keys(state.raw)) {
|
|
|
|
if(typeof state.raw[key] === 'string') {
|
|
|
|
state.raw[key] = stripColor(state.raw[key]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for(const player of state.players) {
|
|
|
|
if(!('name' in player)) continue;
|
|
|
|
player.name = stripColor(player.name);
|
|
|
|
}
|
|
|
|
}
|
2019-01-09 12:35:11 +01:00
|
|
|
}
|
2014-10-29 08:02:03 +01:00
|
|
|
|
2019-01-09 12:35:11 +01:00
|
|
|
async sendPacket(type) {
|
|
|
|
const request = Buffer.concat([
|
|
|
|
Buffer.from([0xfe,0xfd,0x00]), // gamespy2
|
|
|
|
Buffer.from([0x00,0x00,0x00,0x01]), // ping ID
|
|
|
|
Buffer.from(type)
|
|
|
|
]);
|
|
|
|
return await this.udpSend(request, buffer => {
|
|
|
|
const reader = this.reader(buffer);
|
|
|
|
const header = reader.uint(1);
|
|
|
|
if (header !== 0) return;
|
|
|
|
const pingId = reader.uint(4);
|
|
|
|
if (pingId !== 1) return;
|
|
|
|
return reader.rest();
|
|
|
|
});
|
2017-08-09 12:32:09 +02:00
|
|
|
}
|
2017-08-09 11:05:55 +02:00
|
|
|
|
2017-08-09 12:32:09 +02:00
|
|
|
readFieldData(reader) {
|
2019-01-09 12:35:11 +01:00
|
|
|
const zero = reader.uint(1); // always 0
|
|
|
|
const count = reader.uint(1); // number of rows in this data
|
|
|
|
|
|
|
|
// some games omit the count byte entirely if it's 0 or at random (like americas army)
|
|
|
|
// Luckily, count should always be <64, and ascii characters will typically be >64,
|
|
|
|
// so we can detect this.
|
|
|
|
if (count > 64) {
|
|
|
|
reader.skip(-1);
|
2019-01-09 12:50:30 +01:00
|
|
|
this.debugLog("Detected missing count byte, rewinding by 1");
|
2019-01-09 12:35:11 +01:00
|
|
|
} else {
|
2019-01-09 12:50:30 +01:00
|
|
|
this.debugLog("Detected row count: " + count);
|
2019-01-09 12:35:11 +01:00
|
|
|
}
|
2017-08-09 12:32:09 +02:00
|
|
|
|
2019-01-09 12:50:30 +01:00
|
|
|
this.debugLog(() => "Reading fields, starting at: "+reader.rest());
|
2014-10-29 08:02:03 +01:00
|
|
|
|
2017-08-09 11:05:55 +02:00
|
|
|
const fields = [];
|
2017-08-09 12:32:09 +02:00
|
|
|
while(!reader.done()) {
|
2017-08-09 11:05:55 +02:00
|
|
|
let field = reader.string();
|
2017-08-09 12:32:09 +02:00
|
|
|
if(!field) break;
|
|
|
|
fields.push(field);
|
2019-01-09 12:50:30 +01:00
|
|
|
this.debugLog("field:"+field);
|
2017-08-09 12:32:09 +02:00
|
|
|
}
|
2014-10-29 08:02:03 +01:00
|
|
|
|
2019-01-09 12:35:11 +01:00
|
|
|
if (!fields.length) return [];
|
|
|
|
|
2017-08-09 11:05:55 +02:00
|
|
|
const units = [];
|
2017-08-09 12:32:09 +02:00
|
|
|
outer: while(!reader.done()) {
|
2017-08-09 11:05:55 +02:00
|
|
|
const unit = {};
|
2017-08-09 12:32:09 +02:00
|
|
|
for(let iField = 0; iField < fields.length; iField++) {
|
2017-08-09 11:05:55 +02:00
|
|
|
let key = fields[iField];
|
|
|
|
let value = reader.string();
|
2017-08-09 12:32:09 +02:00
|
|
|
if(!value && iField === 0) break outer;
|
2019-01-09 12:50:30 +01:00
|
|
|
this.debugLog("value:"+value);
|
2017-08-09 12:32:09 +02:00
|
|
|
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);
|
|
|
|
}
|
2014-10-29 08:02:03 +01:00
|
|
|
|
2017-08-09 12:32:09 +02:00
|
|
|
unit[key] = value;
|
|
|
|
}
|
|
|
|
units.push(unit);
|
|
|
|
}
|
2014-10-29 08:02:03 +01:00
|
|
|
|
2017-08-09 12:32:09 +02:00
|
|
|
return units;
|
|
|
|
}
|
2017-08-09 11:05:55 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = Gamespy2;
|