diff --git a/README.md b/README.md index 5827d4b..87519ab 100644 --- a/README.md +++ b/README.md @@ -125,6 +125,7 @@ Games List * Conan Exiles (conanexiles) * Contact J.A.C.K. (contactjack) * Counter-Strike 1.6 (cs16) +* Counter-Strike: 2D (cs2d) * Counter-Strike: Condition Zero (cscz) * Counter-Strike: Source (css) * Counter-Strike: Global Offensive (csgo) [[Additional Notes](#csgo)] @@ -428,6 +429,9 @@ as well: `--debug`, `--pretty`, `--socketTimeout 5000`, etc. Changelog --- +### 2.0.5 +* Added support for Counter-Strike: 2D + ### 2.0.4 * Added details about new 2.0 reponse fields to the README. diff --git a/bin/gamedig.js b/bin/gamedig.js index d12a80b..720928e 100644 --- a/bin/gamedig.js +++ b/bin/gamedig.js @@ -1,8 +1,12 @@ #!/usr/bin/env node -const argv = require('minimist')(process.argv.slice(2)), +const Minimist = require('minimist'), Gamedig = require('..'); +const argv = Minimist(process.argv.slice(2), { + boolean: ['pretty','debug'] +}); + const debug = argv.debug; delete argv.debug; const pretty = !!argv.pretty || debug; diff --git a/games.txt b/games.txt index a8eb64f..faf75e7 100644 --- a/games.txt +++ b/games.txt @@ -10,7 +10,6 @@ # rfactor|rFactor|rfactor|port=34397,port_query_offset=-100 # bfris|BFRIS|bfris|port=44001 -# cs2d|Counter-Strike: 2D|cs2d|port_query=36963 # freelancer|Freelancer|freelancer|port_query=2302 # gr|Ghost Recon|ghostrecon|port=2346,port_query_offset=2 # gtr2|GTR2|gtr2|port=34297,port_query_offset=1 @@ -83,6 +82,7 @@ conanexiles|Conan Exiles|valve|port=7777,port_query=27015 contactjack|Contact J.A.C.K.|gamespy1|port_query=27888 cs16|Counter-Strike 1.6|valve|port=27015 +cs2d|Counter-Strike: 2D|cs2d|port=36963 cscz|Counter-Strike: Condition Zero|valve|port=27015 css|Counter-Strike: Source|valve|port=27015 csgo|Counter-Strike: Global Offensive|valve|port=27015|doc_notes=csgo diff --git a/package.json b/package.json index 8a5f94d..f4e5767 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ ], "main": "lib/index.js", "author": "Michael Morrison", - "version": "2.0.4", + "version": "2.0.5", "repository": { "type": "git", "url": "https://github.com/sonicsnes/node-gamedig.git" diff --git a/protocols/core.js b/protocols/core.js index 38f7722..4cc22be 100644 --- a/protocols/core.js +++ b/protocols/core.js @@ -278,7 +278,7 @@ class Core extends EventEmitter { /** * @param {Buffer|string} buffer - * @param {function(Buffer):T} onPacket + * @param {function(Buffer):T=} onPacket * @param {(function():T)=} onTimeout * @returns Promise * @template T @@ -297,6 +297,10 @@ class Core extends EventEmitter { const socket = this.udpSocket; socket.send(buffer, address, port); + if (!onPacket && !onTimeout) { + return null; + } + let socketCallback; let timeout; try { diff --git a/protocols/cs2d.js b/protocols/cs2d.js new file mode 100644 index 0000000..c23ad53 --- /dev/null +++ b/protocols/cs2d.js @@ -0,0 +1,74 @@ +const Core = require('./core'); + +class Cs2d extends Core { + async run(state) { + { + const reader = await this.sendQuery( + Buffer.from('\x01\x00\x03\x10\x21\xFB\x01\x75\x00', 'binary'), + Buffer.from('\x01\x00\xfb\x01', 'binary') + ); + const flags = reader.uint(1); + state.raw.flags = flags; + state.password = this.readFlag(flags, 0); + state.raw.registeredOnly = this.readFlag(flags, 1); + state.raw.fogOfWar = this.readFlag(flags, 2); + state.raw.friendlyFire = this.readFlag(flags, 3); + state.raw.botsEnabled = this.readFlag(flags, 5); + state.raw.luaScripts = this.readFlag(flags, 6); + state.name = this.readString(reader); + state.map = this.readString(reader); + state.raw.numplayers = reader.uint(1); + state.maxplayers = reader.uint(1); + state.raw.gamemode = reader.uint(1); + if (state.raw.botsEnabled) { + state.raw.numbots = reader.uint(1); + } else { + state.raw.numbots = 0; + } + } + + { + const reader = await this.sendQuery( + Buffer.from('\x01\x00\xFB\x05', 'binary'), + Buffer.from('\x01\x00\xFB\x05', 'binary') + ); + state.raw.numplayers2 = reader.uint(1); + while(!reader.done()) { + const player = {}; + player.id = reader.uint(1); + player.name = this.readString(reader); + player.team = reader.uint(1); + player.score = reader.uint(4); + player.deaths = reader.uint(4); + if (state.bots.length < state.raw.numbots) { + state.bots.push(player); + } else { + state.players.push(player); + } + } + } + } + + async sendQuery(request, expectedHeader) { + // Send multiple copies of the request packet, because cs2d likes to just ignore them randomly + await this.udpSend(request); + await this.udpSend(request); + return await this.udpSend(request, (buffer) => { + const reader = this.reader(buffer); + const header = reader.part(4); + if (!header.equals(expectedHeader)) return; + return reader; + }); + } + + readFlag(flags, offset) { + return !!(flags & (1 << offset)); + } + + readString(reader) { + const length = reader.uint(1); + return reader.string({length:length}); + } +} + +module.exports = Cs2d;