diff --git a/CHANGELOG.md b/CHANGELOG.md index 6438451b..10d75993 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/README.md b/README.md index 4224ec48..e020600d 100644 --- a/README.md +++ b/README.md @@ -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). diff --git a/lib/Results.js b/lib/Results.js index 3b598ce6..c94dc970 100644 --- a/lib/Results.js +++ b/lib/Results.js @@ -25,6 +25,7 @@ export class Results { password = false raw = {} + version = '' maxplayers = 0 numplayers = 0 diff --git a/protocols/armagetron.js b/protocols/armagetron.js index 63d599cf..968aa68f 100644 --- a/protocols/armagetron.js +++ b/protocols/armagetron.js @@ -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) diff --git a/protocols/asa.js b/protocols/asa.js index 72e18c48..99995c7e 100644 --- a/protocols/asa.js +++ b/protocols/asa.js @@ -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 + } } diff --git a/protocols/ase.js b/protocols/ase.js index 7af7cfc5..b5977a2c 100644 --- a/protocols/ase.js +++ b/protocols/ase.js @@ -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)) diff --git a/protocols/assettocorsa.js b/protocols/assettocorsa.js index ec240879..eea13edd 100644 --- a/protocols/assettocorsa.js +++ b/protocols/assettocorsa.js @@ -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) { diff --git a/protocols/battlefield.js b/protocols/battlefield.js index e27a2a5a..d6b6568e 100644 --- a/protocols/battlefield.js +++ b/protocols/battlefield.js @@ -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() } { diff --git a/protocols/beammp.js b/protocols/beammp.js index 0dfe8662..6c45af9f 100644 --- a/protocols/beammp.js +++ b/protocols/beammp.js @@ -28,5 +28,6 @@ export default class beammp extends Core { }) state.raw = server + if ('version' in state.raw) state.version = state.raw.version } } diff --git a/protocols/doom3.js b/protocols/doom3.js index 18e9d052..3bf9957d 100644 --- a/protocols/doom3.js +++ b/protocols/doom3.js @@ -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) diff --git a/protocols/eco.js b/protocols/eco.js index c3c38547..b02f0e98 100644 --- a/protocols/eco.js +++ b/protocols/eco.js @@ -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 } } diff --git a/protocols/eldewrito.js b/protocols/eldewrito.js index 2b03f141..718f3bc1 100644 --- a/protocols/eldewrito.js +++ b/protocols/eldewrito.js @@ -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 } } diff --git a/protocols/factorio.js b/protocols/factorio.js index e1ebd7d8..b9ede999 100644 --- a/protocols/factorio.js +++ b/protocols/factorio.js @@ -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 } } diff --git a/protocols/farmingsimulator.js b/protocols/farmingsimulator.js index 7a51846d..7adbad4c 100644 --- a/protocols/farmingsimulator.js +++ b/protocols/farmingsimulator.js @@ -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 } diff --git a/protocols/ffow.js b/protocols/ffow.js index b579bb27..ff6e81ec 100644 --- a/protocols/ffow.js +++ b/protocols/ffow.js @@ -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) diff --git a/protocols/fivem.js b/protocols/fivem.js index a74177aa..377c0410 100644 --- a/protocols/fivem.js +++ b/protocols/fivem.js @@ -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 } { diff --git a/protocols/gamespy1.js b/protocols/gamespy1.js index d24540af..31858301 100644 --- a/protocols/gamespy1.js +++ b/protocols/gamespy1.js @@ -113,6 +113,7 @@ export default class gamespy1 extends Core { } state.numplayers = state.players.length + state.version = state.raw.gamever } async sendPacket (type) { diff --git a/protocols/gamespy2.js b/protocols/gamespy2.js index 71014e89..ab135679 100644 --- a/protocols/gamespy2.js +++ b/protocols/gamespy2.js @@ -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 diff --git a/protocols/gamespy3.js b/protocols/gamespy3.js index 3abc7238..ef567dc1 100644 --- a/protocols/gamespy3.js +++ b/protocols/gamespy3.js @@ -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['']) { diff --git a/protocols/geneshift.js b/protocols/geneshift.js index d323435a..f71a4c58 100644 --- a/protocols/geneshift.js +++ b/protocols/geneshift.js @@ -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] } } diff --git a/protocols/jc2mp.js b/protocols/jc2mp.js index 617dc029..f7220e30 100644 --- a/protocols/jc2mp.js +++ b/protocols/jc2mp.js @@ -12,5 +12,7 @@ export default class jc2mp extends gamespy3 { async run (state) { await super.run(state) + + state.version = state.raw.version } } diff --git a/protocols/mafia2mp.js b/protocols/mafia2mp.js index 0d14e6d5..5e4b94c5 100644 --- a/protocols/mafia2mp.js +++ b/protocols/mafia2mp.js @@ -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 diff --git a/protocols/minecraft.js b/protocols/minecraft.js index 69dccc8f..6888abda 100644 --- a/protocols/minecraft.js +++ b/protocols/minecraft.js @@ -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, ' ') diff --git a/protocols/minecraftbedrock.js b/protocols/minecraftbedrock.js index 8f0324e3..084cd670 100644 --- a/protocols/minecraftbedrock.js +++ b/protocols/minecraftbedrock.js @@ -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() diff --git a/protocols/mumbleping.js b/protocols/mumbleping.js index 067d39da..4a70c498 100644 --- a/protocols/mumbleping.js +++ b/protocols/mumbleping.js @@ -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) diff --git a/protocols/openttd.js b/protocols/openttd.js index 3c7187e3..61c91ab1 100644 --- a/protocols/openttd.js +++ b/protocols/openttd.js @@ -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), diff --git a/protocols/palworld.js b/protocols/palworld.js index 33ca9c8e..0e9f21fb 100644 --- a/protocols/palworld.js +++ b/protocols/palworld.js @@ -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 } } diff --git a/protocols/quake1.js b/protocols/quake1.js index dadbb02f..1299617e 100644 --- a/protocols/quake1.js +++ b/protocols/quake1.js @@ -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'] + } } diff --git a/protocols/quake2.js b/protocols/quake2.js index 8d56b879..3de861d5 100644 --- a/protocols/quake2.js +++ b/protocols/quake2.js @@ -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 } } diff --git a/protocols/quake3.js b/protocols/quake3.js index 4868c53b..118e8692 100644 --- a/protocols/quake3.js +++ b/protocols/quake3.js @@ -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) diff --git a/protocols/rfactor.js b/protocols/rfactor.js index ef02ce6a..1089abf4 100644 --- a/protocols/rfactor.js +++ b/protocols/rfactor.js @@ -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) diff --git a/protocols/samp.js b/protocols/samp.js index cc45abd8..95105419 100644 --- a/protocols/samp.js +++ b/protocols/samp.js @@ -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 } } diff --git a/protocols/savage2.js b/protocols/savage2.js index 5d59ae51..411423ec 100644 --- a/protocols/savage2.js +++ b/protocols/savage2.js @@ -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) } diff --git a/protocols/starmade.js b/protocols/starmade.js index 98e13246..3e7bcf05 100644 --- a/protocols/starmade.js +++ b/protocols/starmade.js @@ -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] diff --git a/protocols/teamspeak3.js b/protocols/teamspeak3.js index 459295c4..c22c4c5f 100644 --- a/protocols/teamspeak3.js +++ b/protocols/teamspeak3.js @@ -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 } { diff --git a/protocols/theisleevrima.js b/protocols/theisleevrima.js index 59c0b8ae..8b293727 100644 --- a/protocols/theisleevrima.js +++ b/protocols/theisleevrima.js @@ -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 } } diff --git a/protocols/tribes1.js b/protocols/tribes1.js index da93e67d..050e2b35 100644 --- a/protocols/tribes1.js +++ b/protocols/tribes1.js @@ -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) { diff --git a/protocols/unreal2.js b/protocols/unreal2.js index d8c872cb..00f9076c 100644 --- a/protocols/unreal2.js +++ b/protocols/unreal2.js @@ -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') || diff --git a/protocols/valve.js b/protocols/valve.js index 4f35800b..8c1c5a85 100644 --- a/protocols/valve.js +++ b/protocols/valve.js @@ -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() diff --git a/protocols/ventrilo.js b/protocols/ventrilo.js index 1b4bc98f..25ac085b 100644 --- a/protocols/ventrilo.js +++ b/protocols/ventrilo.js @@ -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 }