From a7c3b5474ca9e2577dd87f1439b674411aca5fcc Mon Sep 17 00:00:00 2001 From: Pedro Ivo Hudson Date: Sat, 24 Feb 2024 15:46:40 -0300 Subject: [PATCH] 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 --- CHANGELOG.md | 1 + README.md | 1 + lib/Results.js | 1 + protocols/armagetron.js | 2 +- protocols/asa.js | 5 +++++ protocols/ase.js | 2 +- protocols/assettocorsa.js | 1 + protocols/battlefield.js | 2 +- protocols/beammp.js | 1 + protocols/doom3.js | 1 + protocols/eco.js | 1 + protocols/eldewrito.js | 1 + protocols/factorio.js | 1 + protocols/farmingsimulator.js | 2 +- protocols/ffow.js | 2 +- protocols/fivem.js | 1 + protocols/gamespy1.js | 1 + protocols/gamespy2.js | 1 + protocols/gamespy3.js | 1 + protocols/geneshift.js | 2 +- protocols/jc2mp.js | 2 ++ protocols/mafia2mp.js | 1 + protocols/minecraft.js | 2 ++ protocols/minecraftbedrock.js | 1 + protocols/mumbleping.js | 1 + protocols/openttd.js | 2 +- protocols/palworld.js | 1 + protocols/quake1.js | 5 +++++ protocols/quake2.js | 1 + protocols/quake3.js | 1 + protocols/rfactor.js | 2 +- protocols/samp.js | 3 ++- protocols/savage2.js | 2 +- protocols/starmade.js | 2 +- protocols/teamspeak3.js | 1 + protocols/theisleevrima.js | 1 + protocols/tribes1.js | 2 +- protocols/unreal2.js | 1 + protocols/valve.js | 3 ++- protocols/ventrilo.js | 1 + 40 files changed, 52 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6438451..10d7599 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 4224ec4..e020600 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 3b598ce..c94dc97 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 63d599c..968aa68 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 72e18c4..99995c7 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 7af7cfc..b5977a2 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 ec24087..eea13ed 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 e27a2a5..d6b6568 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 0dfe866..6c45af9 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 18e9d05..3bf9957 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 c3c3854..b02f0e9 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 2b03f14..718f3bc 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 e1ebd7d..b9ede99 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 7a51846..7adbad4 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 b579bb2..ff6e81e 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 a74177a..377c041 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 d24540a..3185830 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 71014e8..ab13567 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 3abc723..ef567dc 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 d323435..f71a4c5 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 617dc02..f7220e3 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 0d14e6d..5e4b94c 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 69dccc8..6888abd 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 8f0324e..084cd67 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 067d39d..4a70c49 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 3c7187e..61c91ab 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 33ca9c8..0e9f21f 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 dadbb02..1299617 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 8d56b87..3de861d 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 4868c53..118e869 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 ef02ce6..1089abf 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 cc45abd..9510541 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 5d59ae5..411423e 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 98e1324..3e7bcf0 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 459295c..c22c4c5 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 59c0b8a..8b29372 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 da93e67..050e2b3 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 d8c872c..00f9076 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 4f35800..8c1c5a8 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 1b4bc98..25ac085 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 }