feat: Add version as a top level field (#532)

* add top level version on existing entries

* start adding version on new protocols WIP

* add version to more games

* more games with version

* add more games

* more version

* even more games with version

* add 'delete state.raw.version'

* fix delete version

* Update CHANGELOG.md

* add version in Results.js

* more games

* add new game

* more games

* add version on README

* add new game

* other game

* new game

* add unreal2 version

* add ventrilo version

* add eldewrito eldewrito

* add beammp version

* fix starmade version

* add new version in samp protocol

* docs: tweak the changelog line a bit

---------

Co-authored-by: CosminPerRam <cosmin.p@live.com>
This commit is contained in:
Pedro Ivo Hudson 2024-02-24 15:46:40 -03:00 committed by GitHub
parent fb6a5a1c7a
commit a7c3b5474c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
40 changed files with 52 additions and 13 deletions

View file

@ -1,6 +1,7 @@
## To Be Released...
## 5.0.0-beta.3
* Added a new stabilized field `version` in the query response (By @podrivo #532)
* Euro Truck Simulator 2 (2012) - Added support (By @podrivo #523)
* Eco - Fixed querying servers using reverse queries and player names (By @Vito0912 #526)
* Factorio (2016) - Added support (By @Vito0912 #527)

View file

@ -78,6 +78,7 @@ The returned state object will contain the following keys:
| **connect** | string | This will typically include the game's `IP:PORT`. The port will reflect the server's game port, even if your request specified the game's query port in the request. For some games, this may be a server ID or connection URL if an IP:PORT is not appropriate for end-users. |
| **ping** | number | Round trip time to the server (in milliseconds). Note that this is not the RTT of an ICMP echo, as ICMP packets are often blocked by NATs and node has poor support for raw sockets. This value is derived from the RTT of one of the query packets, which is usually quite accurate, but may add a bit due to server lag. |
| **queryPort** | number | Indicates on which port the query was done on, 0 if this is not applicable. |
| **version** | string | Game version that is running on the server. Empty if not present. |
| **raw** | object | Contains all information received from the server in a disorganized format. |
Note that `raw` (or **unstable**) objects contents MAY change on a per-protocol basis between GameDig patch releases (although not typical).

View file

@ -25,6 +25,7 @@ export class Results {
password = false
raw = {}
version = ''
maxplayers = 0
numplayers = 0

View file

@ -21,7 +21,7 @@ export default class armagetron extends Core {
state.numplayers = this.readUInt(reader)
state.raw.versionmin = this.readUInt(reader)
state.raw.versionmax = this.readUInt(reader)
state.raw.version = this.readString(reader)
state.version = this.readString(reader)
state.maxplayers = this.readUInt(reader)
const players = this.readString(reader)

View file

@ -9,4 +9,9 @@ export default class asa extends Epic {
this.clientSecret = 'PP5UGxysEieNfSrEicaD1N2Bb3TdXuD7xHYcsdUHZ7s'
this.deploymentId = 'ad9a8feffb3b4b2ca315546f038c3ae2'
}
async run(state) {
await super.run(state)
state.version = state.raw.attributes.BUILDID_s + '.' + state.raw.attributes.MINORBUILDID_s
}
}

View file

@ -14,7 +14,7 @@ export default class ase extends Core {
state.name = this.readString(reader)
state.raw.gametype = this.readString(reader)
state.map = this.readString(reader)
state.raw.version = this.readString(reader)
state.version = this.readString(reader)
state.password = this.readString(reader) === '1'
state.numplayers = parseInt(this.readString(reader))
state.maxplayers = parseInt(this.readString(reader))

View file

@ -22,6 +22,7 @@ export default class assettocorsa extends Core {
state.gamePort = serverInfo.port
state.raw.carInfo = carInfo.Cars
state.raw.serverInfo = serverInfo
state.version = state.raw.serverInfo.poweredBy
for (const car of carInfo.Cars) {
if (car.IsConnected) {

View file

@ -66,7 +66,7 @@ export default class battlefield extends Core {
{
const data = await this.query(socket, ['version'])
data.shift()
state.raw.version = data.shift()
state.version = data.shift()
}
{

View file

@ -28,5 +28,6 @@ export default class beammp extends Core {
})
state.raw = server
if ('version' in state.raw) state.version = state.raw.version
}
}

View file

@ -24,6 +24,7 @@ export default class doom3 extends Core {
let reader = this.reader(body)
const protoVersion = reader.uint(4)
state.raw.protocolVersion = (protoVersion >> 16) + '.' + (protoVersion & 0xffff)
state.version = state.raw.protocolVersion
// some doom implementations send us a packet size here, some don't (etqw does this)
// we can tell if this is a packet size, because the third and fourth byte will be 0 (no packets are that massive)

View file

@ -17,5 +17,6 @@ export default class eco extends Core {
state.gamePort = serverInfo.GamePort
state.players = serverInfo.OnlinePlayersNames?.map(name => ({ name, raw: {} })) || []
state.raw = serverInfo
state.version = state.raw.Version
}
}

View file

@ -17,5 +17,6 @@ export default class eldewrito extends Core {
state.connect = this.options.address + ':' + json.port
state.raw = json
if ('eldewritoVersion' in state.raw) state.version = state.raw.eldewritoVersion
}
}

View file

@ -19,5 +19,6 @@ export default class factorio extends Core {
state.players = players.map(player => ({ name: player, raw: {} }))
state.raw = serverInfo
state.version = state.raw.application_version.game_version + '.' + state.raw.application_version.build_version
}
}

View file

@ -48,7 +48,7 @@ export default class farmingsimulator extends Core {
}
})
state.raw.version = serverInfo.attr('version')
state.version = serverInfo.attr('version')
// TODO: Add state.raw
}

View file

@ -22,7 +22,7 @@ export default class ffow extends valve {
state.raw.mod = reader.string()
state.raw.gamemode = reader.string()
state.raw.description = reader.string()
state.raw.version = reader.string()
state.version = reader.string()
state.gamePort = reader.uint(2)
state.numplayers = reader.uint(1)
state.maxplayers = reader.uint(1)

View file

@ -17,6 +17,7 @@ export default class fivem extends quake2 {
responseType: 'json'
})
state.raw.info = json
if ('version' in state.raw.info) state.version = state.raw.info.version
}
{

View file

@ -113,6 +113,7 @@ export default class gamespy1 extends Core {
}
state.numplayers = state.players.length
state.version = state.raw.gamever
}
async sendPacket (type) {

View file

@ -23,6 +23,7 @@ export default class gamespy2 extends Core {
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)
if ('gamever' in state.raw) state.version = state.raw.gamever
}
// Parse players

View file

@ -112,6 +112,7 @@ export default class gamespy3 extends Core {
if (state.raw.password === '1') 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)
if ('gamever' in state.raw) state.version = state.raw.gamever
if ('' in state.raw.playerTeamInfo) {
for (const playerInfo of state.raw.playerTeamInfo['']) {

View file

@ -41,6 +41,6 @@ export default class geneshift extends Core {
state.raw.friendlyfire = !!parseInt(found[16])
state.raw.mercs = !!parseInt(found[17])
// fields[18] is unknown? listen server?
state.raw.version = found[19]
state.version = found[19]
}
}

View file

@ -12,5 +12,7 @@ export default class jc2mp extends gamespy3 {
async run (state) {
await super.run(state)
state.version = state.raw.version
}
}

View file

@ -21,6 +21,7 @@ export default class mafia2mp extends Core {
state.numplayers = parseInt(this.readString(reader))
state.maxplayers = parseInt(this.readString(reader))
state.raw.gamemode = this.readString(reader)
state.version = state.raw.gamemode
state.password = !!reader.uint(1)
state.gamePort = this.options.port - 1

View file

@ -78,6 +78,7 @@ export default class minecraft extends Core {
if (vanillaState.maxplayers) state.maxplayers = vanillaState.maxplayers
if (vanillaState.players.length) state.players = vanillaState.players
if (vanillaState.ping) this.registerRtt(vanillaState.ping)
if (vanillaState.raw.version) state.version = vanillaState.raw.version.name
}
if (gamespyState) {
if (gamespyState.name) state.name = gamespyState.name
@ -93,6 +94,7 @@ export default class minecraft extends Core {
if (bedrockState.maxplayers) state.maxplayers = bedrockState.maxplayers
if (bedrockState.map) state.map = bedrockState.map
if (bedrockState.ping) this.registerRtt(bedrockState.ping)
if (bedrockState.raw.mcVersion) state.version = bedrockState.raw.mcVersion
}
// remove dupe spaces from name
state.name = state.name.replace(/\s+/g, ' ')

View file

@ -57,6 +57,7 @@ export default class minecraftbedrock extends Core {
state.name = split.shift()
state.raw.protocolVersion = split.shift()
state.raw.mcVersion = split.shift()
state.version = state.raw.mcVersion
state.numplayers = parseInt(split.shift())
state.maxplayers = parseInt(split.shift())
if (split.length) state.raw.serverId = split.shift()

View file

@ -16,6 +16,7 @@ export default class mumbleping extends Core {
state.raw.versionMajor = reader.uint(1)
state.raw.versionMinor = reader.uint(1)
state.raw.versionPatch = reader.uint(1)
state.version = state.raw.versionMajor + '.' + state.raw.versionMinor + '.' + state.raw.versionPatch
reader.skip(8)
state.numplayers = reader.uint(4)
state.maxplayers = reader.uint(4)

View file

@ -25,7 +25,7 @@ export default class openttd extends Core {
}
state.name = reader.string()
state.raw.version = reader.string()
state.version = reader.string()
state.raw.language = this.decode(
reader.uint(1),

View file

@ -15,5 +15,6 @@ export default class palworld extends Epic {
await super.run(state)
state.name = state.raw.attributes.NAME_s
state.numplayers = state.raw.attributes.PLAYERS_l
state.version = state.raw.attributes.VERSION_S
}
}

View file

@ -6,4 +6,9 @@ export default class quake1 extends quake2 {
this.responseHeader = 'n'
this.isQuake1 = true
}
async run(state) {
await super.run(state)
if ('*version' in state.raw) state.version = state.raw['*version']
}
}

View file

@ -83,6 +83,7 @@ export default class quake2 extends Core {
if ('sv_hostname' in state.raw) state.name = state.raw.sv_hostname
if ('hostname' in state.raw) state.name = state.raw.hostname
if ('clients' in state.raw) state.numplayers = state.raw.clients
if ('iv' in state.raw) state.version = state.raw.iv
else state.numplayers = state.players.length + state.bots.length
}
}

View file

@ -12,6 +12,7 @@ export default class quake3 extends quake2 {
state.name = this.stripColors(state.name)
for (const key of Object.keys(state.raw)) {
state.raw[key] = this.stripColors(state.raw[key])
if ('version' in state.raw) state.version = state.raw.version
}
for (const player of state.players) {
player.name = this.stripColors(player.name)

View file

@ -10,7 +10,7 @@ export default class rfactor extends Core {
state.raw.region = reader.uint(2)
state.raw.ip = reader.part(4)
state.raw.size = reader.uint(2)
state.raw.version = reader.uint(2)
state.version = reader.uint(2)
state.raw.versionRaceCast = reader.uint(2)
state.gamePort = reader.uint(2)
state.raw.queryPort = reader.uint(2)

View file

@ -16,7 +16,7 @@ export default class samp extends Core {
const reader = await this.sendPacket('i')
if (this.isVcmp) {
const consumed = reader.part(12)
state.raw.version = this.reader(consumed).string()
state.version = this.reader(consumed).string()
}
state.password = !!reader.uint(1)
state.numplayers = reader.uint(2)
@ -35,6 +35,7 @@ export default class samp extends Core {
const key = reader.pascalString(1)
const value = reader.pascalString(1)
state.raw.rules[key] = value
if ('version' in state.raw.rules) state.version = state.raw.rules.version
}
}

View file

@ -15,7 +15,7 @@ export default class savage2 extends Core {
state.raw.location = reader.string()
state.raw.minplayers = reader.uint(1)
state.raw.gametype = reader.string()
state.raw.version = reader.string()
state.version = reader.string()
state.raw.minlevel = reader.uint(1)
}

View file

@ -57,7 +57,7 @@ export default class starmade extends Core {
this.logger.debug('Received raw data array', data)
if (typeof data[0] === 'number') state.raw.infoVersion = data[0]
if (typeof data[1] === 'number') state.raw.version = data[1]
if (typeof data[1] === 'string') state.version = data[1]
if (typeof data[2] === 'string') state.name = data[2]
if (typeof data[3] === 'string') state.raw.description = data[3]
if (typeof data[4] === 'number') state.raw.startTime = data[4]

View file

@ -17,6 +17,7 @@ export default class teamspeak3 extends Core {
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_clientsonline' in state.raw) state.numplayers = state.raw.virtualserver_clientsonline
if ('virtualserver_version' in state.raw) state.version = state.raw.virtualserver_version
}
{

View file

@ -14,5 +14,6 @@ export default class theisleevrima extends Epic {
await super.run(state)
state.name = state.raw.attributes.SERVERNAME_s
state.map = state.raw.attributes.MAP_NAME_s
state.version = state.raw.attributes.SERVER_VERSION_s
}
}

View file

@ -35,7 +35,7 @@ export default class tribes1 extends Core {
state.raw.gametype = this.readString(reader)
const isStarsiege2009 = state.raw.gametype === 'Starsiege'
state.raw.version = this.readString(reader)
state.version = this.readString(reader)
state.name = this.readString(reader)
if (isStarsiege2009) {

View file

@ -46,6 +46,7 @@ export default class unreal2 extends Core {
}
}
if ('GamePassword' in state.raw.rules) { state.password = state.raw.rules.GamePassword !== 'True' }
if ('UTComp_Version' in state.raw.rules) { state.version = state.raw.rules.UTComp_Version }
}
if (state.raw.mutators.includes('KillingFloorMut') ||

View file

@ -94,7 +94,8 @@ export default class valve extends Core {
state.raw.shipwitnesses = reader.uint(1)
state.raw.shipduration = reader.uint(1)
}
state.raw.version = reader.string()
state.version = reader.string()
const extraFlag = reader.uint(1)
if (extraFlag & 0x80) state.gamePort = reader.uint(2)
if (extraFlag & 0x10) state.raw.steamid = reader.uint(8).toString()

View file

@ -21,6 +21,7 @@ export default class ventrilo extends Core {
if ('NAME' in state.raw) state.name = state.raw.NAME
if ('MAXCLIENTS' in state.raw) state.maxplayers = state.raw.MAXCLIENTS
if ('VERSION' in state.raw) state.version = state.raw.VERSION
if (this.trueTest(state.raw.AUTH)) state.password = true
}