node-gamedig/protocols/battlefield.js

163 lines
5.1 KiB
JavaScript

import Core from './core.js'
export default class battlefield extends Core {
constructor () {
super()
this.encoding = 'latin1'
}
async run (state) {
await this.withTcp(async socket => {
{
const data = await this.query(socket, ['serverInfo'])
state.name = data.shift()
state.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
})
}
state.raw.targetscore = parseInt(data.shift())
state.raw.status = data.shift()
// Seems like the fields end at random places beyond this point
// depending on the server version
if (data.length) state.raw.ranked = (data.shift() === 'true')
if (data.length) state.raw.punkbuster = (data.shift() === 'true')
if (data.length) state.password = (data.shift() === 'true')
if (data.length) state.raw.uptime = parseInt(data.shift())
if (data.length) state.raw.roundtime = parseInt(data.shift())
const isBadCompany2 = data[0] === 'BC2'
if (isBadCompany2) {
if (data.length) data.shift()
if (data.length) data.shift()
}
if (data.length) {
state.raw.ip = data.shift()
const split = state.raw.ip.split(':')
state.gameHost = split[0]
state.gamePort = split[1]
} else {
// best guess if the server doesn't tell us what the server port is
// these are just the default game ports for different default query ports
if (this.options.port === 48888) state.gamePort = 7673
if (this.options.port === 22000) state.gamePort = 25200
}
if (data.length) state.raw.punkbusterversion = data.shift()
if (data.length) state.raw.joinqueue = (data.shift() === 'true')
if (data.length) state.raw.region = data.shift()
if (data.length) state.raw.pingsite = data.shift()
if (data.length) state.raw.country = data.shift()
if (data.length) state.raw.quickmatch = (data.shift() === 'true')
}
{
const data = await this.query(socket, ['version'])
data.shift()
state.version = data.shift()
}
{
const data = await this.query(socket, ['listPlayers', 'all'])
const fieldCount = parseInt(data.shift())
const fields = []
for (let i = 0; i < fieldCount; i++) {
fields.push(data.shift())
}
const numplayers = data.shift()
for (let i = 0; i < numplayers; i++) {
const player = {}
for (let key of fields) {
let value = data.shift()
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)
}
player[key] = value
}
state.players.push(player)
}
}
})
}
async query (socket, params) {
const outPacket = this.buildPacket(params)
return await this.tcpSend(socket, outPacket, (data) => {
const decoded = this.decodePacket(data)
if (decoded) {
this.logger.debug(decoded)
if (decoded.shift() !== 'OK') throw new Error('Missing OK')
return decoded
}
})
}
buildPacket (params) {
const paramBuffers = []
for (const param of params) {
paramBuffers.push(Buffer.from(param, 'utf8'))
}
let totalLength = 12
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)
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
}
return b
}
decodePacket (buffer) {
if (buffer.length < 8) return false
const reader = this.reader(buffer)
reader.uint(4) // header
const totalLength = reader.uint(4)
if (buffer.length < totalLength) return false
this.logger.debug('Expected ' + totalLength + ' bytes, have ' + buffer.length)
const paramCount = reader.uint(4)
const params = []
for (let i = 0; i < paramCount; i++) {
params.push(reader.pascalString(4))
reader.uint(1) // strNull
}
return params
}
}