mirror of
https://github.com/tribufu/node-gamedig
synced 2026-05-06 15:17:36 +00:00
chore: Convert all files to LF endings (#400)
* Convert to LF? * Modify gitattributes * Force LF * Git --renormalize * Update .gitattributes to enforce eol=lf * Redo CRLF -> LF on remaining files
This commit is contained in:
parent
a8bc7521f6
commit
cee42e7a88
65 changed files with 5697 additions and 5697 deletions
|
|
@ -1,181 +1,181 @@
|
|||
import Core from './core.js'
|
||||
|
||||
const stringKeys = new Set([
|
||||
'website',
|
||||
'gametype',
|
||||
'gamemode',
|
||||
'player'
|
||||
])
|
||||
|
||||
function normalizeEntry ([key, value]) {
|
||||
key = key.toLowerCase()
|
||||
const split = key.split('_')
|
||||
let keyType = key
|
||||
|
||||
if (split.length === 2 && !isNaN(Number(split[1]))) {
|
||||
keyType = split[0]
|
||||
}
|
||||
|
||||
if (!stringKeys.has(keyType) && !keyType.includes('name')) { // todo! the latter check might be problematic, fails on key "name_tag_distance_scope"
|
||||
if (value.toLowerCase() === 'true') {
|
||||
value = true
|
||||
} else if (value.toLowerCase() === 'false') {
|
||||
value = false
|
||||
} else if (value.length && !isNaN(Number(value))) {
|
||||
value = Number(value)
|
||||
}
|
||||
}
|
||||
|
||||
return [key, value]
|
||||
}
|
||||
|
||||
export default class gamespy1 extends Core {
|
||||
constructor () {
|
||||
super()
|
||||
this.encoding = 'latin1'
|
||||
this.byteorder = 'be'
|
||||
}
|
||||
|
||||
async run (state) {
|
||||
const raw = await this.sendPacket('\\status\\xserverquery')
|
||||
// Convert all keys to lowercase and normalize value types
|
||||
const data = Object.fromEntries(Object.entries(raw).map(entry => normalizeEntry(entry)))
|
||||
state.raw = data
|
||||
if ('hostname' in data) state.name = data.hostname
|
||||
if ('mapname' in data) state.map = data.mapname
|
||||
if (this.trueTest(data.password)) state.password = true
|
||||
if ('maxplayers' in data) state.maxplayers = Number(data.maxplayers)
|
||||
if ('hostport' in data) state.gamePort = Number(data.hostport)
|
||||
|
||||
const teamOffByOne = data.gamename === 'bfield1942'
|
||||
const playersById = {}
|
||||
const teamNamesById = {}
|
||||
for (const ident of Object.keys(data)) {
|
||||
const split = ident.split('_')
|
||||
if (split.length !== 2) continue
|
||||
let key = split[0].toLowerCase()
|
||||
const id = Number(split[1])
|
||||
if (isNaN(id)) continue
|
||||
let value = data[ident]
|
||||
|
||||
delete data[ident]
|
||||
|
||||
if (key !== 'team' && key.startsWith('team')) {
|
||||
// Info about a team
|
||||
if (key === 'teamname') {
|
||||
teamNamesById[id] = value
|
||||
} else {
|
||||
// other team info which we don't track
|
||||
}
|
||||
} else {
|
||||
// Info about a player
|
||||
if (!(id in playersById)) playersById[id] = {}
|
||||
|
||||
if (key === 'playername' || key === 'player') {
|
||||
key = 'name'
|
||||
}
|
||||
if (key === 'team' && !isNaN(value)) { // todo! technically, this NaN check isn't needed.
|
||||
key = 'teamId'
|
||||
value += teamOffByOne ? -1 : 0
|
||||
}
|
||||
|
||||
playersById[id][key] = value
|
||||
}
|
||||
}
|
||||
state.raw.teams = teamNamesById
|
||||
|
||||
const players = Object.values(playersById)
|
||||
|
||||
const seenHashes = new Set()
|
||||
for (const player of players) {
|
||||
// Some servers (bf1942) report the same player multiple times (bug?)
|
||||
// Ignore these duplicates
|
||||
if (player.keyhash) {
|
||||
if (seenHashes.has(player.keyhash)) {
|
||||
this.logger.debug('Rejected player with hash ' + player.keyhash + ' (Duplicate keyhash)')
|
||||
continue
|
||||
} else {
|
||||
seenHashes.add(player.keyhash)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert player's team ID to team name if possible
|
||||
if (Object.prototype.hasOwnProperty.call(player, 'teamId')) {
|
||||
if (Object.keys(teamNamesById).length) {
|
||||
player.team = teamNamesById[player.teamId] || ''
|
||||
} else {
|
||||
player.team = player.teamId
|
||||
delete player.teamId
|
||||
}
|
||||
}
|
||||
|
||||
state.players.push(player)
|
||||
}
|
||||
|
||||
state.numplayers = state.players.length
|
||||
}
|
||||
|
||||
async sendPacket (type) {
|
||||
let receivedQueryId
|
||||
const output = {}
|
||||
const parts = new Set()
|
||||
let maxPartNum = 0
|
||||
|
||||
return await this.udpSend(type, buffer => {
|
||||
const reader = this.reader(buffer)
|
||||
const str = reader.string(buffer.length)
|
||||
const split = str.split('\\')
|
||||
split.shift()
|
||||
const data = {}
|
||||
while (split.length) {
|
||||
const key = split.shift()
|
||||
const value = split.shift() || ''
|
||||
data[key] = value
|
||||
}
|
||||
|
||||
let queryId, partNum
|
||||
const partFinal = ('final' in data)
|
||||
if (data.queryid) {
|
||||
const split = data.queryid.split('.')
|
||||
if (split.length >= 2) {
|
||||
partNum = Number(split[1])
|
||||
}
|
||||
queryId = split[0]
|
||||
}
|
||||
delete data.final
|
||||
delete data.queryid
|
||||
this.logger.debug('Received part num=' + partNum + ' queryId=' + queryId + ' final=' + partFinal)
|
||||
|
||||
if (queryId) {
|
||||
if (receivedQueryId && receivedQueryId !== queryId) {
|
||||
this.logger.debug('Rejected packet (Wrong query ID)')
|
||||
return
|
||||
} else if (!receivedQueryId) {
|
||||
receivedQueryId = queryId
|
||||
}
|
||||
}
|
||||
if (!partNum) {
|
||||
partNum = parts.size
|
||||
this.logger.debug('No part number received (assigned #' + partNum + ')')
|
||||
}
|
||||
if (parts.has(partNum)) {
|
||||
this.logger.debug('Rejected packet (Duplicate part)')
|
||||
return
|
||||
}
|
||||
parts.add(partNum)
|
||||
if (partFinal) {
|
||||
maxPartNum = partNum
|
||||
}
|
||||
|
||||
this.logger.debug('Received part #' + partNum + ' of ' + (maxPartNum || '?'))
|
||||
for (const i of Object.keys(data)) {
|
||||
output[i] = data[i]
|
||||
}
|
||||
if (maxPartNum && parts.size === maxPartNum) {
|
||||
this.logger.debug('Received all parts')
|
||||
this.logger.debug(output)
|
||||
return output
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
import Core from './core.js'
|
||||
|
||||
const stringKeys = new Set([
|
||||
'website',
|
||||
'gametype',
|
||||
'gamemode',
|
||||
'player'
|
||||
])
|
||||
|
||||
function normalizeEntry ([key, value]) {
|
||||
key = key.toLowerCase()
|
||||
const split = key.split('_')
|
||||
let keyType = key
|
||||
|
||||
if (split.length === 2 && !isNaN(Number(split[1]))) {
|
||||
keyType = split[0]
|
||||
}
|
||||
|
||||
if (!stringKeys.has(keyType) && !keyType.includes('name')) { // todo! the latter check might be problematic, fails on key "name_tag_distance_scope"
|
||||
if (value.toLowerCase() === 'true') {
|
||||
value = true
|
||||
} else if (value.toLowerCase() === 'false') {
|
||||
value = false
|
||||
} else if (value.length && !isNaN(Number(value))) {
|
||||
value = Number(value)
|
||||
}
|
||||
}
|
||||
|
||||
return [key, value]
|
||||
}
|
||||
|
||||
export default class gamespy1 extends Core {
|
||||
constructor () {
|
||||
super()
|
||||
this.encoding = 'latin1'
|
||||
this.byteorder = 'be'
|
||||
}
|
||||
|
||||
async run (state) {
|
||||
const raw = await this.sendPacket('\\status\\xserverquery')
|
||||
// Convert all keys to lowercase and normalize value types
|
||||
const data = Object.fromEntries(Object.entries(raw).map(entry => normalizeEntry(entry)))
|
||||
state.raw = data
|
||||
if ('hostname' in data) state.name = data.hostname
|
||||
if ('mapname' in data) state.map = data.mapname
|
||||
if (this.trueTest(data.password)) state.password = true
|
||||
if ('maxplayers' in data) state.maxplayers = Number(data.maxplayers)
|
||||
if ('hostport' in data) state.gamePort = Number(data.hostport)
|
||||
|
||||
const teamOffByOne = data.gamename === 'bfield1942'
|
||||
const playersById = {}
|
||||
const teamNamesById = {}
|
||||
for (const ident of Object.keys(data)) {
|
||||
const split = ident.split('_')
|
||||
if (split.length !== 2) continue
|
||||
let key = split[0].toLowerCase()
|
||||
const id = Number(split[1])
|
||||
if (isNaN(id)) continue
|
||||
let value = data[ident]
|
||||
|
||||
delete data[ident]
|
||||
|
||||
if (key !== 'team' && key.startsWith('team')) {
|
||||
// Info about a team
|
||||
if (key === 'teamname') {
|
||||
teamNamesById[id] = value
|
||||
} else {
|
||||
// other team info which we don't track
|
||||
}
|
||||
} else {
|
||||
// Info about a player
|
||||
if (!(id in playersById)) playersById[id] = {}
|
||||
|
||||
if (key === 'playername' || key === 'player') {
|
||||
key = 'name'
|
||||
}
|
||||
if (key === 'team' && !isNaN(value)) { // todo! technically, this NaN check isn't needed.
|
||||
key = 'teamId'
|
||||
value += teamOffByOne ? -1 : 0
|
||||
}
|
||||
|
||||
playersById[id][key] = value
|
||||
}
|
||||
}
|
||||
state.raw.teams = teamNamesById
|
||||
|
||||
const players = Object.values(playersById)
|
||||
|
||||
const seenHashes = new Set()
|
||||
for (const player of players) {
|
||||
// Some servers (bf1942) report the same player multiple times (bug?)
|
||||
// Ignore these duplicates
|
||||
if (player.keyhash) {
|
||||
if (seenHashes.has(player.keyhash)) {
|
||||
this.logger.debug('Rejected player with hash ' + player.keyhash + ' (Duplicate keyhash)')
|
||||
continue
|
||||
} else {
|
||||
seenHashes.add(player.keyhash)
|
||||
}
|
||||
}
|
||||
|
||||
// Convert player's team ID to team name if possible
|
||||
if (Object.prototype.hasOwnProperty.call(player, 'teamId')) {
|
||||
if (Object.keys(teamNamesById).length) {
|
||||
player.team = teamNamesById[player.teamId] || ''
|
||||
} else {
|
||||
player.team = player.teamId
|
||||
delete player.teamId
|
||||
}
|
||||
}
|
||||
|
||||
state.players.push(player)
|
||||
}
|
||||
|
||||
state.numplayers = state.players.length
|
||||
}
|
||||
|
||||
async sendPacket (type) {
|
||||
let receivedQueryId
|
||||
const output = {}
|
||||
const parts = new Set()
|
||||
let maxPartNum = 0
|
||||
|
||||
return await this.udpSend(type, buffer => {
|
||||
const reader = this.reader(buffer)
|
||||
const str = reader.string(buffer.length)
|
||||
const split = str.split('\\')
|
||||
split.shift()
|
||||
const data = {}
|
||||
while (split.length) {
|
||||
const key = split.shift()
|
||||
const value = split.shift() || ''
|
||||
data[key] = value
|
||||
}
|
||||
|
||||
let queryId, partNum
|
||||
const partFinal = ('final' in data)
|
||||
if (data.queryid) {
|
||||
const split = data.queryid.split('.')
|
||||
if (split.length >= 2) {
|
||||
partNum = Number(split[1])
|
||||
}
|
||||
queryId = split[0]
|
||||
}
|
||||
delete data.final
|
||||
delete data.queryid
|
||||
this.logger.debug('Received part num=' + partNum + ' queryId=' + queryId + ' final=' + partFinal)
|
||||
|
||||
if (queryId) {
|
||||
if (receivedQueryId && receivedQueryId !== queryId) {
|
||||
this.logger.debug('Rejected packet (Wrong query ID)')
|
||||
return
|
||||
} else if (!receivedQueryId) {
|
||||
receivedQueryId = queryId
|
||||
}
|
||||
}
|
||||
if (!partNum) {
|
||||
partNum = parts.size
|
||||
this.logger.debug('No part number received (assigned #' + partNum + ')')
|
||||
}
|
||||
if (parts.has(partNum)) {
|
||||
this.logger.debug('Rejected packet (Duplicate part)')
|
||||
return
|
||||
}
|
||||
parts.add(partNum)
|
||||
if (partFinal) {
|
||||
maxPartNum = partNum
|
||||
}
|
||||
|
||||
this.logger.debug('Received part #' + partNum + ' of ' + (maxPartNum || '?'))
|
||||
for (const i of Object.keys(data)) {
|
||||
output[i] = data[i]
|
||||
}
|
||||
if (maxPartNum && parts.size === maxPartNum) {
|
||||
this.logger.debug('Received all parts')
|
||||
this.logger.debug(output)
|
||||
return output
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue