From 9b8423b20a92e314c9bc51ec2e5d50ccac3cbc81 Mon Sep 17 00:00:00 2001 From: mmorrison Date: Wed, 9 Jan 2019 05:35:11 -0600 Subject: [PATCH] More async conversions --- games.txt | 4 +- package-lock.json | 526 ++++++++++++++++++++++++------------- package.json | 5 +- protocols/buildandshoot.js | 96 ++++--- protocols/core.js | 63 +++-- protocols/doom3.js | 140 +++++----- protocols/ffow.js | 51 ++-- protocols/fivem.js | 58 ++-- protocols/gamespy1.js | 92 +++---- protocols/gamespy2.js | 99 ++++--- protocols/gamespy3.js | 262 +++++++++--------- protocols/jc2mp.js | 5 +- protocols/quake2.js | 137 +++++----- protocols/ut3.js | 4 +- protocols/valve.js | 21 +- 15 files changed, 859 insertions(+), 704 deletions(-) diff --git a/games.txt b/games.txt index 001684e..c9b9245 100644 --- a/games.txt +++ b/games.txt @@ -63,7 +63,7 @@ bfh|Battlefield Hardline|battlefield|port=25200,port_query_offset=22000 breach|Breach|valve|port=27016 breed|Breed|gamespy2|port=7649 brink|Brink|valve|port_query_offset=1 -buildandshoot|Build and Shoot|buildandshoot|port=32887,port_query=32886 +buildandshoot|Build and Shoot|buildandshoot|port=32887,port_query_offset=-1 cod|Call of Duty|quake3|port=28960 coduo|Call of Duty: United Offensive|quake3|port=28960 @@ -148,7 +148,7 @@ il2|IL-2 Sturmovik|gamespy1|port_query=21000 insurgency|Insurgency|valve ironstorm|Iron Storm|gamespy1|port_query=3505 jamesbondnightfire|James Bond: Nightfire|gamespy1|port_query=6550 -jc2mp|Just Cause 2 Multiplayer|jc2mp|port=7777|isJc2mp +jc2mp|Just Cause 2 Multiplayer|jc2mp|port=7777 killingfloor|Killing Floor|killingfloor|port=7707,port_query_offset=1 killingfloor2|Killing Floor 2|valve|port=7777,port_query=27015 kingpin|Kingpin: Life of Crime|gamespy1|port=31510,port_query_offset=-10 diff --git a/package-lock.json b/package-lock.json index 5526b12..1cf28bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,23 @@ { "name": "gamedig", - "version": "1.0.41", + "version": "1.0.49", "lockfileVersion": 1, "requires": true, "dependencies": { + "@types/node": { + "version": "10.12.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.12.18.tgz", + "integrity": "sha512-fh+pAqt4xRzPfqA6eh3Z2y6fyZavRIumvjhaCL753+TVkGKGhpPeyrJG2JftD0T9q4GF00KjefsQ+PQNDdWQaQ==" + }, "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.2.tgz", + "integrity": "sha512-FBHEW6Jf5TB9MGBgUUA9XHkTbjXYfAUjY43ACMfmdMRHniyoMHjHjzD50OK8LGDWQwp4rWEsIq5kEqq7rvIM1g==", "requires": { - "co": "4.6.0", - "fast-deep-equal": "1.1.0", - "fast-json-stable-stringify": "2.0.0", - "json-schema-traverse": "0.3.1" + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, "amdefine": { @@ -21,9 +26,12 @@ "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" }, "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=" + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } }, "assert-plus": { "version": "1.0.0", @@ -46,51 +54,60 @@ "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", - "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, "barse": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/barse/-/barse-0.4.3.tgz", "integrity": "sha1-KJhk15XQECu7sYHmbs0IxUobwMs=", "requires": { - "readable-stream": "1.0.34" + "readable-stream": "~1.0.2" } }, "bcrypt-pbkdf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", - "optional": true, + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "requires": { - "tweetnacl": "0.14.5" + "tweetnacl": "^0.14.3" } }, - "boom": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", - "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", - "requires": { - "hoek": "4.2.1" - } + "bluebird": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", + "integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==" + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + "cheerio": { + "version": "1.0.0-rc.2", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.2.tgz", + "integrity": "sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=", + "requires": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash": "^4.15.0", + "parse5": "^3.0.1" + } }, "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", "requires": { - "delayed-stream": "1.0.0" + "delayed-stream": "~1.0.0" } }, "commander": { @@ -98,7 +115,7 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.8.1.tgz", "integrity": "sha1-Br42f+v9oMMwqh4qBy09yXYkJdQ=", "requires": { - "graceful-readlink": "1.0.1" + "graceful-readlink": ">= 1.0.0" } }, "compressjs": { @@ -106,8 +123,8 @@ "resolved": "https://registry.npmjs.org/compressjs/-/compressjs-1.0.3.tgz", "integrity": "sha1-ldt03VuQOM+AvKMhqw7eJxtJWbY=", "requires": { - "amdefine": "1.0.1", - "commander": "2.8.1" + "amdefine": "~1.0.0", + "commander": "~2.8.1" } }, "core-util-is": { @@ -115,30 +132,28 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, - "cryptiles": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", - "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", + "css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", "requires": { - "boom": "5.2.0" - }, - "dependencies": { - "boom": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", - "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", - "requires": { - "hoek": "4.2.1" - } - } + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" } }, + "css-what": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.2.tgz", + "integrity": "sha512-wan8dMWQ0GUeF7DGEPVjhHemVW/vy6xUYmFzRY8RYqgA0JtXC9rJmbScBjqSu6dg9q0lwPQy6ZAmJVr3PPTvqQ==" + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" } }, "delayed-stream": { @@ -146,19 +161,62 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, - "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "optional": true, + "dom-serializer": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz", + "integrity": "sha1-BzxpdUbOB4DOI75KKOKT5AvDDII=", "requires": { - "jsbn": "0.1.1" + "domelementtype": "~1.1.1", + "entities": "~1.1.1" + }, + "dependencies": { + "domelementtype": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=" + } } }, + "domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, "extsprintf": { "version": "1.3.0", @@ -166,9 +224,9 @@ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" }, "fast-json-stable-stringify": { "version": "2.0.0", @@ -181,13 +239,13 @@ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "requires": { - "asynckit": "0.4.0", - "combined-stream": "1.0.6", - "mime-types": "2.1.18" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" } }, "gbxremote": { @@ -195,8 +253,8 @@ "resolved": "https://registry.npmjs.org/gbxremote/-/gbxremote-0.1.4.tgz", "integrity": "sha1-x+0iWC5WBRtOF2AbPdWjAE7u/UM=", "requires": { - "barse": "0.4.3", - "sax": "0.4.3", + "barse": "~0.4.2", + "sax": "0.4.x", "xmlbuilder": "0.3.1" } }, @@ -205,7 +263,7 @@ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "requires": { - "assert-plus": "1.0.0" + "assert-plus": "^1.0.0" } }, "graceful-readlink": { @@ -219,38 +277,55 @@ "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", - "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", "requires": { - "ajv": "5.5.2", - "har-schema": "2.0.0" + "ajv": "^6.5.5", + "har-schema": "^2.0.0" } }, - "hawk": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", - "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", + "htmlparser2": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.0.tgz", + "integrity": "sha512-J1nEUGv+MkXS0weHNWVKJJ+UrLfePxRWpN3C9bEi9fLxL2+ggW94DQvgYVXsaT30PGwYRIZKNZXuyMhp3Di4bQ==", "requires": { - "boom": "4.3.1", - "cryptiles": "3.1.2", - "hoek": "4.2.1", - "sntp": "2.1.0" + "domelementtype": "^1.3.0", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.1.1.tgz", + "integrity": "sha512-DkN66hPyqDhnIQ6Jcsvx9bFjhw214O4poMBcIMgPVpQvNy9a0e0Uhg5SqySyDKAmUlwt8LonTBz1ezOnM8pUdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "string_decoder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", + "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==", + "requires": { + "safe-buffer": "~5.1.0" + } + } } }, - "hoek": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", - "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==" - }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "requires": { - "assert-plus": "1.0.0", - "jsprim": "1.4.1", - "sshpk": "1.14.1" + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" } }, "iconv-lite": { @@ -263,6 +338,11 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, + "ip-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-3.0.0.tgz", + "integrity": "sha512-T8wDtjy+Qf2TAPDQmBp0eGKJ8GavlWlUnamr3wRn6vvdZlKVuJXXMlSncYFRYgVHOM3If5NR1H4+OvVQU9Idvg==" + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -278,11 +358,15 @@ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, + "jquery": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.3.1.tgz", + "integrity": "sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==" + }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "optional": true + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "json-schema": { "version": "0.2.3", @@ -290,9 +374,9 @@ "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" }, "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "json-stringify-safe": { "version": "5.0.1", @@ -310,22 +394,27 @@ "verror": "1.10.0" } }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + }, "long": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/long/-/long-2.4.0.tgz", "integrity": "sha1-n6GAux2VAM3CnEFWdmoZleH0Uk8=" }, "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" }, "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", "requires": { - "mime-db": "1.33.0" + "mime-db": "~1.37.0" } }, "minimist": { @@ -338,115 +427,171 @@ "resolved": "https://registry.npmjs.org/moment/-/moment-2.21.0.tgz", "integrity": "sha512-TCZ36BjURTeFTM/CwRcViQlfkMvL1/vFISuNLO5GkcVm1+QHfbSiNqZuWeMFjj1/3+uAjXswgRk30j1kkLYJBQ==" }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "requires": { + "boolbase": "~1.0.0" + } + }, "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "parse5": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", + "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", + "requires": { + "@types/node": "*" + } }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, + "psl": { + "version": "1.1.31", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", + "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==" + }, "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" }, "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" }, "readable-stream": { "version": "1.0.34", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "requires": { - "core-util-is": "1.0.2", - "inherits": "2.0.3", + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", "isarray": "0.0.1", - "string_decoder": "0.10.31" + "string_decoder": "~0.10.x" } }, "request": { - "version": "2.85.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.85.0.tgz", - "integrity": "sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg==", + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", "requires": { - "aws-sign2": "0.7.0", - "aws4": "1.6.0", - "caseless": "0.12.0", - "combined-stream": "1.0.6", - "extend": "3.0.1", - "forever-agent": "0.6.1", - "form-data": "2.3.2", - "har-validator": "5.0.3", - "hawk": "6.0.2", - "http-signature": "1.2.0", - "is-typedarray": "1.0.0", - "isstream": "0.1.2", - "json-stringify-safe": "5.0.1", - "mime-types": "2.1.18", - "oauth-sign": "0.8.2", - "performance-now": "2.1.0", - "qs": "6.5.1", - "safe-buffer": "5.1.1", - "stringstream": "0.0.5", - "tough-cookie": "2.3.4", - "tunnel-agent": "0.6.0", - "uuid": "3.2.1" + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + } + } + }, + "request-promise": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.2.tgz", + "integrity": "sha1-0epG1lSm7k+O5qT+oQGMIpEZBLQ=", + "requires": { + "bluebird": "^3.5.0", + "request-promise-core": "1.1.1", + "stealthy-require": "^1.1.0", + "tough-cookie": ">=2.3.3" + } + }, + "request-promise-core": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", + "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", + "requires": { + "lodash": "^4.13.1" } }, "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sax": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/sax/-/sax-0.4.3.tgz", "integrity": "sha1-cA46NOsueSzjgHkccSgPNzGWXdw=" }, - "sntp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", - "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", + "sshpk": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.0.tgz", + "integrity": "sha512-Zhev35/y7hRMcID/upReIvRse+I9SVhyVre/KTJSJQWMz3C3+G+HpO7m1wK/yckEtujKZ7dS4hkVxAnmHaIGVQ==", "requires": { - "hoek": "4.2.1" + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" } }, - "sshpk": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", - "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", - "requires": { - "asn1": "0.2.3", - "assert-plus": "1.0.0", - "bcrypt-pbkdf": "1.0.1", - "dashdash": "1.14.1", - "ecc-jsbn": "0.1.1", - "getpass": "0.1.7", - "jsbn": "0.1.1", - "tweetnacl": "0.14.5" - } + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" }, "string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" }, - "stringstream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=" - }, "tough-cookie": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", - "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.0.tgz", + "integrity": "sha512-LHMvg+RBP/mAVNqVbOX8t+iJ+tqhBA/t49DuI7+IDAWHrASnesqSu1vWbKB7UrE2yk+HMFUBMadRGMkB4VCfog==", "requires": { - "punycode": "1.4.1" + "ip-regex": "^3.0.0", + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + } } }, "tunnel-agent": { @@ -454,19 +599,38 @@ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "requires": { - "safe-buffer": "5.1.1" + "safe-buffer": "^5.0.1" } }, "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "optional": true + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "requires": { + "punycode": "^2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + } + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==" + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" }, "varint": { "version": "4.0.1", @@ -478,9 +642,9 @@ "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "requires": { - "assert-plus": "1.0.0", + "assert-plus": "^1.0.0", "core-util-is": "1.0.2", - "extsprintf": "1.3.0" + "extsprintf": "^1.2.0" } }, "xmlbuilder": { diff --git a/package.json b/package.json index 66048a9..9a1cf4a 100644 --- a/package.json +++ b/package.json @@ -25,13 +25,16 @@ }, "dependencies": { "async": "^0.9.2", + "cheerio": "^1.0.0-rc.2", "compressjs": "^1.0.2", "gbxremote": "^0.1.4", "iconv-lite": "^0.4.18", + "jquery": "^3.3.1", "long": "^2.4.0", "minimist": "^1.2.0", "moment": "^2.21.0", - "request": "^2.85.0", + "request": "^2.88.0", + "request-promise": "^4.2.2", "varint": "^4.0.1" }, "bin": { diff --git a/protocols/buildandshoot.js b/protocols/buildandshoot.js index 6620c40..db8ebb9 100644 --- a/protocols/buildandshoot.js +++ b/protocols/buildandshoot.js @@ -1,59 +1,51 @@ -const request = require('request'), - Core = require('./core'); +const Core = require('./core'), + cheerio = require('cheerio'); class BuildAndShoot extends Core { - run(state) { - request({ + async run(state) { + const body = await this.request({ uri: 'http://'+this.options.address+':'+this.options.port_query+'/', - timeout: 3000, - }, (e,r,body) => { - if(e) return this.fatal('HTTP error'); - - let m; - - m = body.match(/status server for (.*?)\r|\n/); - if(m) state.name = m[1]; - - m = body.match(/Current uptime: (\d+)/); - if(m) state.raw.uptime = m[1]; - - m = body.match(/currently running (.*?) by /); - if(m) state.map = m[1]; - - m = body.match(/Current players: (\d+)\/(\d+)/); - if(m) { - state.raw.numplayers = m[1]; - state.maxplayers = m[2]; - } - - m = body.match(/class="playerlist"([^]+?)\/table/); - if(m) { - const table = m[1]; - const pre = /[^]*([^]*)<\/td>[^]*([^]*)<\/td>[^]*([^]*)<\/td>[^]*([^]*)<\/td>/g; - let pm; - while(pm = pre.exec(table)) { - if(pm[2] === 'Ping') continue; - state.players.push({ - name: pm[1], - ping: pm[2], - team: pm[3], - score: pm[4] - }); - } - } - /* - var m = this.options.address.match(/(\d+)\.(\d+)\.(\d+)\.(\d+)/); - if(m) { - var o1 = parseInt(m[1]); - var o2 = parseInt(m[2]); - var o3 = parseInt(m[3]); - var o4 = parseInt(m[4]); - var addr = o1+(o2<<8)+(o3<<16)+(o4<<24); - state.raw.url = 'aos://'+addr; - } - */ - this.finish(state); }); + + let m; + + m = body.match(/status server for (.*?)\r|\n/); + if(m) state.name = m[1]; + + m = body.match(/Current uptime: (\d+)/); + if(m) state.raw.uptime = m[1]; + + m = body.match(/currently running (.*?) by /); + if(m) state.map = m[1]; + + m = body.match(/Current players: (\d+)\/(\d+)/); + if(m) { + state.raw.numplayers = m[1]; + state.maxplayers = m[2]; + } + + const $ = cheerio.load(body); + $('#playerlist tbody tr').each((i,tr) => { + if (!$(tr).find('td').first().attr('colspan')) { + state.players.push({ + name: $(tr).find('td').eq(2).text(), + ping: $(tr).find('td').eq(3).text().trim(), + team: $(tr).find('td').eq(4).text().toLowerCase(), + score: parseInt($(tr).find('td').eq(5).text()) + }); + } + }); + /* + var m = this.options.address.match(/(\d+)\.(\d+)\.(\d+)\.(\d+)/); + if(m) { + var o1 = parseInt(m[1]); + var o2 = parseInt(m[2]); + var o3 = parseInt(m[3]); + var o4 = parseInt(m[4]); + var addr = o1+(o2<<8)+(o3<<16)+(o4<<24); + state.raw.url = 'aos://'+addr; + } + */ } } diff --git a/protocols/core.js b/protocols/core.js index 97598fc..960d1a0 100644 --- a/protocols/core.js +++ b/protocols/core.js @@ -5,7 +5,8 @@ const EventEmitter = require('events').EventEmitter, HexUtil = require('../lib/HexUtil'), util = require('util'), dnsLookupAsync = util.promisify(dns.lookup), - dnsResolveAsync = util.promisify(dns.resolve); + dnsResolveAsync = util.promisify(dns.resolve), + requestAsync = require('request-promise'); class Core extends EventEmitter { constructor() { @@ -20,10 +21,10 @@ class Core extends EventEmitter { this.delimiter = '\0'; this.srvRecord = null; - this.attemptAbortables = new Set(); + this.asyncLeaks = new Set(); this.udpCallback = null; this.udpLocked = false; - this.lastAbortableId = 0; + this.lastAsyncLeakId = 0; } initState() { @@ -64,22 +65,24 @@ class Core extends EventEmitter { async runOnceSafe() { try { const result = await this.timedPromise(this.runOnce(), this.options.attemptTimeout, "Attempt"); - if (this.attemptAbortables.size) { + if (this.asyncLeaks.size) { let out = []; - for (const abortable of this.attemptAbortables) { - out.push(abortable.id + " " + abortable.stack); + for (const leak of this.asyncLeaks) { + out.push(leak.id + " " + leak.stack); } - throw new Error('Query succeeded, but abortables were not empty (async leak?):\n' + out.join('\n---\n')); + throw new Error('Query succeeded, but async leak was detected:\n' + out.join('\n---\n')); } return result; } finally { // Clean up any lingering long-running functions - for (const abortable of this.attemptAbortables) { + for (const leak of this.asyncLeaks) { try { - abortable.abort(); - } catch(e) {} + leak.cleanup(); + } catch(e) { + if (this.debug) console.log("Error during async cleanup: " + e.stack); + } } - this.attemptAbortables.clear(); + this.asyncLeaks.clear(); } } @@ -162,15 +165,15 @@ class Core extends EventEmitter { else return await resolveStandard(host); } - addAbortable(fn) { - const id = ++this.lastAbortableId; + addAsyncLeak(fn) { + const id = ++this.lastAsyncLeakId; const stack = new Error().stack; - const entry = { id: id, abort: fn, stack: stack }; - if (this.debug) console.log("Adding abortable: " + id); - this.attemptAbortables.add(entry); + const entry = { id: id, cleanup: fn, stack: stack }; + if (this.debug) console.log("Registering async leak: " + id); + this.asyncLeaks.add(entry); return () => { - if (this.debug) console.log("Removing abortable: " + id); - this.attemptAbortables.delete(entry); + if (this.debug) console.log("Removing async leak: " + id); + this.asyncLeaks.delete(entry); } } @@ -210,7 +213,7 @@ class Core extends EventEmitter { const socket = net.connect(port,address); socket.setNoDelay(true); - const cancelAbortable = this.addAbortable(() => socket.destroy()); + const cancelAsyncLeak = this.addAsyncLeak(() => socket.destroy()); if(this.debug) { console.log(address+':'+port+" TCP Connecting"); @@ -242,21 +245,21 @@ class Core extends EventEmitter { ); return await fn(socket); } finally { - cancelAbortable(); + cancelAsyncLeak(); socket.destroy(); } } setTimeout(callback, time) { - let cancelAbortable; + let cancelAsyncLeak; const onTimeout = () => { - cancelAbortable(); + cancelAsyncLeak(); callback(); }; const timeout = setTimeout(onTimeout, time); - cancelAbortable = this.addAbortable(() => clearTimeout(timeout)); + cancelAsyncLeak = this.addAsyncLeak(() => clearTimeout(timeout)); return () => { - cancelAbortable(); + cancelAsyncLeak(); clearTimeout(timeout); } } @@ -351,6 +354,18 @@ class Core extends EventEmitter { _udpIncoming(buffer) { this.udpCallback && this.udpCallback(buffer); } + + request(params) { + const promise = requestAsync({ + ...params, + timeout: this.options.socketTimeout + }); + const cancelAsyncLeak = this.addAsyncLeak(() => { + promise.cancel(); + }); + promise.finally(cancelAsyncLeak); + return promise; + } } module.exports = Core; diff --git a/protocols/doom3.js b/protocols/doom3.js index dc55713..ecca307 100644 --- a/protocols/doom3.js +++ b/protocols/doom3.js @@ -10,82 +10,80 @@ class Doom3 extends Core { this.hasClanTag = false; this.hasTypeFlag = false; } - run(state) { - this.udpSend('\xff\xffgetInfo\x00PiNGPoNG\x00', (buffer) => { - const reader = this.reader(buffer); - + async run(state) { + const body = await this.udpSend('\xff\xffgetInfo\x00PiNGPoNG\x00', packet => { + const reader = this.reader(packet); const header = reader.uint(2); if(header !== 0xffff) return; const header2 = reader.string(); if(header2 !== 'infoResponse') return; - - if(this.isEtqw) { - const taskId = reader.uint(4); - } - - const challenge = reader.uint(4); - const protoVersion = reader.uint(4); - state.raw.protocolVersion = (protoVersion>>16)+'.'+(protoVersion&0xffff); - - if(this.isEtqw) { - const size = reader.uint(4); - } - - while(!reader.done()) { - const key = reader.string(); - let value = this.stripColors(reader.string()); - if(key === 'si_map') { - value = value.replace('maps/',''); - value = value.replace('.entities',''); - } - if(!key) break; - state.raw[key] = value; - } - - let i = 0; - while(!reader.done()) { - i++; - const player = {}; - player.id = reader.uint(1); - if(player.id === 32) break; - player.ping = reader.uint(2); - if(!this.isEtqw) player.rate = reader.uint(4); - player.name = this.stripColors(reader.string()); - if(this.hasClanTag) { - if(this.hasSpaceBeforeClanTag) reader.uint(1); - player.clantag = this.stripColors(reader.string()); - } - if(this.hasTypeFlag) player.typeflag = reader.uint(1); - - if(!player.ping || player.typeflag) - state.bots.push(player); - else - state.players.push(player); - } - - state.raw.osmask = reader.uint(4); - if(this.isEtqw) { - state.raw.ranked = reader.uint(1); - state.raw.timeleft = reader.uint(4); - state.raw.gamestate = reader.uint(1); - state.raw.servertype = reader.uint(1); - // 0 = regular, 1 = tv - if(state.raw.servertype === 0) { - state.raw.interestedClients = reader.uint(1); - } else if(state.raw.servertype === 1) { - state.raw.connectedClients = reader.uint(4); - state.raw.maxClients = reader.uint(4); - } - } - - if(state.raw.si_name) state.name = state.raw.si_name; - if(state.raw.si_map) state.map = state.raw.si_map; - if(state.raw.si_maxplayers) state.maxplayers = parseInt(state.raw.si_maxplayers); - if(state.raw.si_usepass === '1') state.password = true; - - this.finish(state); - return true; + return reader.rest(); }); + + const reader = this.reader(body); + if(this.isEtqw) { + const taskId = reader.uint(4); + } + + const challenge = reader.uint(4); + const protoVersion = reader.uint(4); + state.raw.protocolVersion = (protoVersion>>16)+'.'+(protoVersion&0xffff); + + if(this.isEtqw) { + const size = reader.uint(4); + } + + while(!reader.done()) { + const key = reader.string(); + let value = this.stripColors(reader.string()); + if(key === 'si_map') { + value = value.replace('maps/',''); + value = value.replace('.entities',''); + } + if(!key) break; + state.raw[key] = value; + } + + let i = 0; + while(!reader.done()) { + i++; + const player = {}; + player.id = reader.uint(1); + if(player.id === 32) break; + player.ping = reader.uint(2); + if(!this.isEtqw) player.rate = reader.uint(4); + player.name = this.stripColors(reader.string()); + if(this.hasClanTag) { + if(this.hasSpaceBeforeClanTag) reader.uint(1); + player.clantag = this.stripColors(reader.string()); + } + if(this.hasTypeFlag) player.typeflag = reader.uint(1); + + if(!player.ping || player.typeflag) + state.bots.push(player); + else + state.players.push(player); + } + + state.raw.osmask = reader.uint(4); + if(this.isEtqw) { + state.raw.ranked = reader.uint(1); + state.raw.timeleft = reader.uint(4); + state.raw.gamestate = reader.uint(1); + state.raw.servertype = reader.uint(1); + // 0 = regular, 1 = tv + if(state.raw.servertype === 0) { + state.raw.interestedClients = reader.uint(1); + } else if(state.raw.servertype === 1) { + state.raw.connectedClients = reader.uint(4); + state.raw.maxClients = reader.uint(4); + } + } + + if(state.raw.si_name) state.name = state.raw.si_name; + if(state.raw.si_map) state.map = state.raw.si_map; + if(state.raw.si_maxplayers) state.maxplayers = parseInt(state.raw.si_maxplayers); + if(state.raw.si_usepass === '1') state.password = true; } stripColors(str) { diff --git a/protocols/ffow.js b/protocols/ffow.js index 6df907b..8f228d3 100644 --- a/protocols/ffow.js +++ b/protocols/ffow.js @@ -6,29 +6,34 @@ class Ffow extends Valve { this.byteorder = 'be'; this.legacyChallenge = true; } - queryInfo(state,c) { - this.sendPacket(0x46,false,'LSQ',0x49, (b) => { - const reader = this.reader(b); - state.raw.protocol = reader.uint(1); - state.name = reader.string(); - state.map = reader.string(); - state.raw.mod = reader.string(); - state.raw.gamemode = reader.string(); - state.raw.description = reader.string(); - state.raw.version = reader.string(); - state.raw.port = reader.uint(2); - state.raw.numplayers = reader.uint(1); - state.maxplayers = reader.uint(1); - state.raw.listentype = String.fromCharCode(reader.uint(1)); - state.raw.environment = String.fromCharCode(reader.uint(1)); - state.password = !!reader.uint(1); - state.raw.secure = reader.uint(1); - state.raw.averagefps = reader.uint(1); - state.raw.round = reader.uint(1); - state.raw.maxrounds = reader.uint(1); - state.raw.timeleft = reader.uint(2); - c(); - }); + async queryInfo(state) { + if(this.debug) console.log("Requesting ffow info ..."); + const b = await this.sendPacket( + 0x46, + false, + 'LSQ', + 0x49 + ); + + const reader = this.reader(b); + state.raw.protocol = reader.uint(1); + state.name = reader.string(); + state.map = reader.string(); + state.raw.mod = reader.string(); + state.raw.gamemode = reader.string(); + state.raw.description = reader.string(); + state.raw.version = reader.string(); + state.raw.port = reader.uint(2); + state.raw.numplayers = reader.uint(1); + state.maxplayers = reader.uint(1); + state.raw.listentype = String.fromCharCode(reader.uint(1)); + state.raw.environment = String.fromCharCode(reader.uint(1)); + state.password = !!reader.uint(1); + state.raw.secure = reader.uint(1); + state.raw.averagefps = reader.uint(1); + state.raw.round = reader.uint(1); + state.raw.maxrounds = reader.uint(1); + state.raw.timeleft = reader.uint(2); } } diff --git a/protocols/fivem.js b/protocols/fivem.js index 49ed7a7..35d1ea0 100644 --- a/protocols/fivem.js +++ b/protocols/fivem.js @@ -1,5 +1,4 @@ -const request = require('request'), - Quake2 = require('./quake2'); +const Quake2 = require('./quake2'); class FiveM extends Quake2 { constructor() { @@ -9,43 +8,28 @@ class FiveM extends Quake2 { this.encoding = 'utf8'; } - finish(state) { - request({ - uri: 'http://'+this.options.address+':'+this.options.port_query+'/info.json', - timeout: this.options.socketTimeout - }, (e,r,body) => { - if(e) return this.fatal('HTTP error'); - let json; - try { - json = JSON.parse(body); - } catch(e) { - return this.fatal('Invalid JSON'); - } + async run(state) { + await super.run(state); - state.raw.info = json; - - request({ - uri: 'http://'+this.options.address+':'+this.options.port_query+'/players.json', - timeout: this.options.socketTimeout - }, (e,r,body) => { - if(e) return this.fatal('HTTP error'); - let json; - try { - json = JSON.parse(body); - } catch(e) { - return this.fatal('Invalid JSON'); - } - - state.raw.players = json; - - state.players = []; - for (const player of json) { - state.players.push({name:player.name, ping:player.ping}); - } - - super.finish(state); + { + const raw = await this.request({ + uri: 'http://' + this.options.address + ':' + this.options.port_query + '/info.json' }); - }); + const json = JSON.parse(raw); + state.raw.info = json; + } + + { + const raw = await this.request({ + uri: 'http://' + this.options.address + ':' + this.options.port_query + '/players.json' + }); + const json = JSON.parse(raw); + state.raw.players = json; + state.players = []; + for (const player of json) { + state.players.push({name: player.name, ping: player.ping}); + } + } } } diff --git a/protocols/gamespy1.js b/protocols/gamespy1.js index 76a078d..9b9729c 100644 --- a/protocols/gamespy1.js +++ b/protocols/gamespy1.js @@ -1,68 +1,57 @@ -const async = require('async'), - Core = require('./core'); +const Core = require('./core'); class Gamespy1 extends Core { constructor() { super(); - this.sessionId = 1; this.encoding = 'latin1'; this.byteorder = 'be'; } - run(state) { - async.series([ - (c) => { - this.sendPacket('info', (data) => { - state.raw = data; - if('hostname' in state.raw) state.name = state.raw.hostname; - if('mapname' in state.raw) state.map = state.raw.mapname; - if(this.trueTest(state.raw.password)) state.password = true; - if('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers); - c(); - }); - }, - (c) => { - this.sendPacket('rules', (data) => { - state.raw.rules = data; - c(); - }); - }, - (c) => { - this.sendPacket('players', (data) => { - const players = {}; - const teams = {}; - for(const ident of Object.keys(data)) { - const split = ident.split('_'); - let key = split[0]; - const id = split[1]; - let value = data[ident]; + async run(state) { + { + const data = await this.sendPacket('info'); + state.raw = data; + if ('hostname' in state.raw) state.name = state.raw.hostname; + if ('mapname' in state.raw) state.map = state.raw.mapname; + if (this.trueTest(state.raw.password)) state.password = true; + if ('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers); + } + { + const data = await this.sendPacket('rules'); + state.raw.rules = data; + } + { + const data = await this.sendPacket('players'); + const players = {}; + const teams = {}; + for (const ident of Object.keys(data)) { + const split = ident.split('_'); + let key = split[0]; + const id = split[1]; + let value = data[ident]; - if(key === 'teamname') { - teams[id] = value; - } else { - if(!(id in players)) players[id] = {}; - if(key === 'playername') key = 'name'; - else if(key === 'team') value = parseInt(value); - else if(key === 'score' || key === 'ping' || key === 'deaths') value = parseInt(value); - players[id][key] = value; - } - } - - state.raw.teams = teams; - for(const id of Object.keys(players)) { - state.players.push(players[id]); - } - this.finish(state); - }); + if (key === 'teamname') { + teams[id] = value; + } else { + if (!(id in players)) players[id] = {}; + if (key === 'playername') key = 'name'; + else if (key === 'team') value = parseInt(value); + else if (key === 'score' || key === 'ping' || key === 'deaths') value = parseInt(value); + players[id][key] = value; + } } - ]); + state.raw.teams = teams; + for (const id of Object.keys(players)) { + state.players.push(players[id]); + } + } } - sendPacket(type,callback) { + async sendPacket(type) { const queryId = ''; const output = {}; - this.udpSend('\\'+type+'\\', (buffer) => { + return await this.udpSend('\\'+type+'\\', buffer => { const reader = this.reader(buffer); const str = reader.string({length:buffer.length}); const split = str.split('\\'); @@ -79,8 +68,7 @@ class Gamespy1 extends Core { if('final' in output) { delete output.final; delete output.queryid; - callback(output); - return true; + return output; } }); } diff --git a/protocols/gamespy2.js b/protocols/gamespy2.js index d6c7f25..941ece2 100644 --- a/protocols/gamespy2.js +++ b/protocols/gamespy2.js @@ -3,53 +3,71 @@ const Core = require('./core'); class Gamespy2 extends Core { constructor() { super(); - this.sessionId = 1; this.encoding = 'latin1'; this.byteorder = 'be'; } - run(state) { - const request = Buffer.from([0xfe,0xfd,0x00,0x00,0x00,0x00,0x01,0xff,0xff,0xff]); - const packets = []; - this.udpSend(request, - (buffer) => { - if(packets.length && buffer.readUInt8(0) === 0) - buffer = buffer.slice(1); - packets.push(buffer); - }, - () => { - const buffer = Buffer.concat(packets); - const reader = this.reader(buffer); - const header = reader.uint(1); - if(header !== 0) return; - const pingId = reader.uint(4); - if(pingId !== 1) return; - - while(!reader.done()) { - const key = reader.string(); - const value = reader.string(); - if(!key) break; - state.raw[key] = value; - } - - if('hostname' in state.raw) state.name = state.raw.hostname; - if('mapname' in state.raw) state.map = state.raw.mapname; - if(this.trueTest(state.raw.password)) state.password = true; - if('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers); - - state.players = this.readFieldData(reader); - state.raw.teams = this.readFieldData(reader); - - this.finish(state); - return true; + async run(state) { + // Parse info + { + const body = await this.sendPacket([0xff, 0, 0]); + const reader = this.reader(body); + while (!reader.done()) { + const key = reader.string(); + const value = reader.string(); + if (!key) break; + state.raw[key] = value; } - ); + if('hostname' in state.raw) state.name = state.raw.hostname; + if('mapname' in state.raw) state.map = state.raw.mapname; + if(this.trueTest(state.raw.password)) state.password = true; + if('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers); + } + + // Parse players + { + const body = await this.sendPacket([0, 0xff, 0]); + const reader = this.reader(body); + state.players = this.readFieldData(reader); + } + + // Parse teams + { + const body = await this.sendPacket([0, 0, 0xff]); + const reader = this.reader(body); + state.raw.teams = this.readFieldData(reader); + } + } + + async sendPacket(type) { + const request = Buffer.concat([ + Buffer.from([0xfe,0xfd,0x00]), // gamespy2 + Buffer.from([0x00,0x00,0x00,0x01]), // ping ID + Buffer.from(type) + ]); + return await this.udpSend(request, buffer => { + const reader = this.reader(buffer); + const header = reader.uint(1); + if (header !== 0) return; + const pingId = reader.uint(4); + if (pingId !== 1) return; + return reader.rest(); + }); } readFieldData(reader) { - const count = reader.uint(1); - // count is unreliable (often it's wrong), so we don't use it. - // read until we hit an empty first field string + const zero = reader.uint(1); // always 0 + const count = reader.uint(1); // number of rows in this data + + // some games omit the count byte entirely if it's 0 or at random (like americas army) + // Luckily, count should always be <64, and ascii characters will typically be >64, + // so we can detect this. + if (count > 64) { + reader.skip(-1); + if (this.debug) console.log("Detected missing count byte, rewinding by 1"); + } else { + if (this.debug) console.log("Detected row count: " + count); + } if(this.debug) console.log("Reading fields, starting at: "+reader.rest()); @@ -57,11 +75,12 @@ class Gamespy2 extends Core { while(!reader.done()) { let field = reader.string(); if(!field) break; - if(field.charCodeAt(0) <= 2) field = field.substring(1); fields.push(field); if(this.debug) console.log("field:"+field); } + if (!fields.length) return []; + const units = []; outer: while(!reader.done()) { const unit = {}; diff --git a/protocols/gamespy3.js b/protocols/gamespy3.js index 48ace96..020418b 100644 --- a/protocols/gamespy3.js +++ b/protocols/gamespy3.js @@ -1,5 +1,5 @@ -const async = require('async'), - Core = require('./core'); +const Core = require('./core'), + HexUtil = require('../lib/HexUtil'); class Gamespy3 extends Core { constructor() { @@ -12,143 +12,129 @@ class Gamespy3 extends Core { this.isJc2mp = false; } - run(state) { - let challenge; + async run(state) { + let challenge = null; + if (!this.noChallenge) { + const buffer = await this.sendPacket(9, false, false, false); + const reader = this.reader(buffer); + challenge = parseInt(reader.string()); + } + let requestPayload; + if(this.isJc2mp) { + // they completely alter the protocol. because why not. + requestPayload = Buffer.from([0xff,0xff,0xff,0x02]); + } else { + requestPayload = Buffer.from([0xff,0xff,0xff,0x01]); + } /** @type Buffer[] */ - let packets; + const packets = await this.sendPacket(0,challenge,requestPayload,true); - async.series([ - (c) => { - if(this.noChallenge) return c(); - this.sendPacket(9,false,false,false,(buffer) => { - const reader = this.reader(buffer); - challenge = parseInt(reader.string()); - c(); - }); - }, - (c) => { - let requestPayload; - if(this.isJc2mp) { - // they completely alter the protocol. because why not. - requestPayload = Buffer.from([0xff,0xff,0xff,0x02]); - } else { - requestPayload = Buffer.from([0xff,0xff,0xff,0x01]); - } + // iterate over the received packets + // the first packet will start off with k/v pairs, followed with data fields + // the following packets will only have data fields + state.raw.playerTeamInfo = {}; - this.sendPacket(0,challenge,requestPayload,true,(b) => { - packets = b; - c(); - }); - }, - (c) => { - // iterate over the received packets - // the first packet will start off with k/v pairs, followed with data fields - // the following packets will only have data fields + for(let iPacket = 0; iPacket < packets.length; iPacket++) { + const packet = packets[iPacket]; + const reader = this.reader(packet); - state.raw.playerTeamInfo = {}; - - for(let iPacket = 0; iPacket < packets.length; iPacket++) { - const packet = packets[iPacket]; - const reader = this.reader(packet); - - if(this.debug) { - console.log("+++"+packet.toString('hex')); - console.log(":::"+packet.toString('ascii')); - } - - // Parse raw server key/values - - if(iPacket === 0) { - while(!reader.done()) { - const key = reader.string(); - if(!key) break; - let value = reader.string(); - - // reread the next line if we hit the weird ut3 bug - if(value === 'p1073741829') value = reader.string(); - - state.raw[key] = value; - } - } - - // Parse player, team, item array state - - if(this.isJc2mp) { - state.raw.numPlayers2 = reader.uint(2); - while(!reader.done()) { - const player = {}; - player.name = reader.string(); - player.steamid = reader.string(); - player.ping = reader.uint(2); - state.players.push(player); - } - } else { - let firstMode = true; - while(!reader.done()) { - let mode = reader.string(); - if(mode.charCodeAt(0) <= 2) mode = mode.substring(1); - if(!mode) continue; - let offset = 0; - if(iPacket !== 0 && firstMode) offset = reader.uint(1); - reader.skip(1); - firstMode = false; - - const modeSplit = mode.split('_'); - const modeName = modeSplit[0]; - const modeType = modeSplit.length > 1 ? modeSplit[1] : 'no_'; - - if(!(modeType in state.raw.playerTeamInfo)) { - state.raw.playerTeamInfo[modeType] = []; - } - const store = state.raw.playerTeamInfo[modeType]; - - while(!reader.done()) { - const item = reader.string(); - if(!item) break; - - while(store.length <= offset) { store.push({}); } - store[offset][modeName] = item; - offset++; - } - } - } - } - - c(); - }, - - (c) => { - // Turn all that raw state into something useful - - if('hostname' in state.raw) state.name = state.raw.hostname; - else if('servername' in state.raw) state.name = state.raw.servername; - if('mapname' in state.raw) state.map = state.raw.mapname; - if(state.raw.password === '1') state.password = true; - if('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers); - - if('' in state.raw.playerTeamInfo) { - for (const playerInfo of state.raw.playerTeamInfo['']) { - const player = {}; - for(const from of Object.keys(playerInfo)) { - let key = from; - let value = playerInfo[from]; - - if(key === 'player') key = 'name'; - if(key === 'score' || key === 'ping' || key === 'team' || key === 'deaths' || key === 'pid') value = parseInt(value); - player[key] = value; - } - state.players.push(player); - } - } - - this.finish(state); + if(this.debug) { + console.log("Parsing packet #" + iPacket); + console.log(HexUtil.debugDump(packet)); } - ]); + + // Parse raw server key/values + + if(iPacket === 0) { + while(!reader.done()) { + const key = reader.string(); + if(!key) break; + + let value = reader.string(); + while(value.match(/^p[0-9]+$/)) { + // fix a weird ut3 bug where some keys don't have values + value = reader.string(); + } + + state.raw[key] = value; + if (this.debug) console.log(key + " = " + value); + } + } + + // Parse player, team, item array state + + if(this.isJc2mp) { + state.raw.numPlayers2 = reader.uint(2); + while(!reader.done()) { + const player = {}; + player.name = reader.string(); + player.steamid = reader.string(); + player.ping = reader.uint(2); + state.players.push(player); + } + } else { + let firstMode = true; + while(!reader.done()) { + if (reader.uint(1) <= 2) continue; + reader.skip(-1); + let fieldId = reader.string(); + if(!fieldId) continue; + const fieldIdSplit = fieldId.split('_'); + const fieldName = fieldIdSplit[0]; + const itemType = fieldIdSplit.length > 1 ? fieldIdSplit[1] : 'no_'; + + if(!(itemType in state.raw.playerTeamInfo)) { + state.raw.playerTeamInfo[itemType] = []; + } + const items = state.raw.playerTeamInfo[itemType]; + + let offset = reader.uint(1); + firstMode = false; + + if (this.debug) { + console.log("Parsing new field: itemType=" + itemType + " fieldName=" + fieldName + " startOffset=" + offset); + } + + while(!reader.done()) { + const item = reader.string(); + if(!item) break; + + while(items.length <= offset) { items.push({}); } + items[offset][fieldName] = item; + if (this.debug) console.log("* " + item); + offset++; + } + } + } + } + + // Turn all that raw state into something useful + + if('hostname' in state.raw) state.name = state.raw.hostname; + else if('servername' in state.raw) state.name = state.raw.servername; + if('mapname' in state.raw) state.map = state.raw.mapname; + if(state.raw.password === '1') state.password = true; + if('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers); + + if('' in state.raw.playerTeamInfo) { + for (const playerInfo of state.raw.playerTeamInfo['']) { + const player = {}; + for(const from of Object.keys(playerInfo)) { + let key = from; + let value = playerInfo[from]; + + if(key === 'player') key = 'name'; + if(key === 'score' || key === 'ping' || key === 'team' || key === 'deaths' || key === 'pid') value = parseInt(value); + player[key] = value; + } + state.players.push(player); + } + } } - sendPacket(type,challenge,payload,assemble,c) { - const challengeLength = (this.noChallenge || challenge === false) ? 0 : 4; + async sendPacket(type,challenge,payload,assemble) { + const challengeLength = challenge === null ? 0 : 4; const payloadLength = payload ? payload.length : 0; const b = Buffer.alloc(7 + challengeLength + payloadLength); @@ -161,7 +147,7 @@ class Gamespy3 extends Core { let numPackets = 0; const packets = {}; - this.udpSend(b,(buffer) => { + return this.udpSend(b,(buffer) => { const reader = this.reader(buffer); const iType = reader.uint(1); if(iType !== type) return; @@ -169,14 +155,12 @@ class Gamespy3 extends Core { if(iSessionId !== this.sessionId) return; if(!assemble) { - c(reader.rest()); - return true; + return reader.rest(); } if(this.useOnlySingleSplit) { // has split headers, but they are worthless and only one packet is used reader.skip(11); - c([reader.rest()]); - return true; + return [reader.rest()]; } reader.skip(9); // filler data -- usually set to 'splitnum\0' @@ -199,13 +183,11 @@ class Gamespy3 extends Core { const list = []; for(let i = 0; i < numPackets; i++) { if(!(i in packets)) { - this.fatal('Missing packet #'+i); - return true; + throw new Error('Missing packet #'+i); } list.push(packets[i]); } - c(list); - return true; + return list; }); } } diff --git a/protocols/jc2mp.js b/protocols/jc2mp.js index 2c704e1..9268c20 100644 --- a/protocols/jc2mp.js +++ b/protocols/jc2mp.js @@ -6,9 +6,10 @@ class Jc2mp extends Gamespy3 { constructor() { super(); this.useOnlySingleSplit = true; + this.isJc2mp = true; } - finalizeState(state) { - super.finalizeState(state); + async run(state) { + super.run(state); if(!state.players.length && parseInt(state.raw.numplayers)) { for(let i = 0; i < parseInt(state.raw.numplayers); i++) { state.players.push({}); diff --git a/protocols/quake2.js b/protocols/quake2.js index b9f8706..2f0a958 100644 --- a/protocols/quake2.js +++ b/protocols/quake2.js @@ -10,79 +10,76 @@ class Quake2 extends Core { this.isQuake1 = false; } - run(state) { - this.udpSend('\xff\xff\xff\xff'+this.sendHeader+'\x00', (buffer) => { - const reader = this.reader(buffer); - - const header = reader.string({length:4,encoding:'latin1'}); - if(header !== '\xff\xff\xff\xff') return; - - let response; - if(this.isQuake1) { - response = reader.string({length:this.responseHeader.length}); + async run(state) { + const body = await this.udpSend('\xff\xff\xff\xff'+this.sendHeader+'\x00', packet => { + const reader = this.reader(packet); + const header = reader.string({length: 4, encoding: 'latin1'}); + if (header !== '\xff\xff\xff\xff') return; + let type; + if (this.isQuake1) { + type = reader.string({length: this.responseHeader.length}); } else { - response = reader.string({encoding:'latin1'}); + type = reader.string({encoding: 'latin1'}); } - if(response !== this.responseHeader) return; - - const info = reader.string().split('\\'); - if(info[0] === '') info.shift(); - - while(true) { - const key = info.shift(); - const value = info.shift(); - if(typeof value === 'undefined') break; - state.raw[key] = value; - } - - while(!reader.done()) { - const line = reader.string(); - if(!line || line.charAt(0) === '\0') break; - - const args = []; - const split = line.split('"'); - split.forEach((part,i) => { - const inQuote = (i%2 === 1); - if(inQuote) { - args.push(part); - } else { - const splitSpace = part.split(' '); - for (const subpart of splitSpace) { - if(subpart) args.push(subpart); - } - } - }); - - const player = {}; - if(this.isQuake1) { - player.id = parseInt(args.shift()); - player.score = parseInt(args.shift()); - player.time = parseInt(args.shift()); - player.ping = parseInt(args.shift()); - player.name = args.shift(); - player.skin = args.shift(); - player.color1 = parseInt(args.shift()); - player.color2 = parseInt(args.shift()); - } else { - player.frags = parseInt(args.shift()); - player.ping = parseInt(args.shift()); - player.name = args.shift() || ''; - player.address = args.shift() || ''; - } - - (player.ping ? state.players : state.bots).push(player); - } - - if('g_needpass' in state.raw) state.password = state.raw.g_needpass; - if('mapname' in state.raw) state.map = state.raw.mapname; - if('sv_maxclients' in state.raw) state.maxplayers = state.raw.sv_maxclients; - if('maxclients' in state.raw) state.maxplayers = state.raw.maxclients; - if('sv_hostname' in state.raw) state.name = state.raw.sv_hostname; - if('hostname' in state.raw) state.name = state.raw.hostname; - - this.finish(state); - return true; + if (type !== this.responseHeader) return; + return reader.rest(); }); + + const reader = this.reader(body); + const info = reader.string().split('\\'); + if(info[0] === '') info.shift(); + + while(true) { + const key = info.shift(); + const value = info.shift(); + if(typeof value === 'undefined') break; + state.raw[key] = value; + } + + while(!reader.done()) { + const line = reader.string(); + if(!line || line.charAt(0) === '\0') break; + + const args = []; + const split = line.split('"'); + split.forEach((part,i) => { + const inQuote = (i%2 === 1); + if(inQuote) { + args.push(part); + } else { + const splitSpace = part.split(' '); + for (const subpart of splitSpace) { + if(subpart) args.push(subpart); + } + } + }); + + const player = {}; + if(this.isQuake1) { + player.id = parseInt(args.shift()); + player.score = parseInt(args.shift()); + player.time = parseInt(args.shift()); + player.ping = parseInt(args.shift()); + player.name = args.shift(); + player.skin = args.shift(); + player.color1 = parseInt(args.shift()); + player.color2 = parseInt(args.shift()); + } else { + player.frags = parseInt(args.shift()); + player.ping = parseInt(args.shift()); + player.name = args.shift() || ''; + player.address = args.shift() || ''; + } + + (player.ping ? state.players : state.bots).push(player); + } + + if('g_needpass' in state.raw) state.password = state.raw.g_needpass; + if('mapname' in state.raw) state.map = state.raw.mapname; + if('sv_maxclients' in state.raw) state.maxplayers = state.raw.sv_maxclients; + if('maxclients' in state.raw) state.maxplayers = state.raw.maxclients; + if('sv_hostname' in state.raw) state.name = state.raw.sv_hostname; + if('hostname' in state.raw) state.name = state.raw.hostname; } } diff --git a/protocols/ut3.js b/protocols/ut3.js index 20fd9b2..9b96b41 100644 --- a/protocols/ut3.js +++ b/protocols/ut3.js @@ -1,8 +1,8 @@ const Gamespy3 = require('./gamespy3'); class Ut3 extends Gamespy3 { - finalizeState(state) { - super.finalizeState(state); + async run(state) { + await super.run(state); this.translate(state.raw,{ 'mapname': false, diff --git a/protocols/valve.js b/protocols/valve.js index 9a77511..7dfc082 100644 --- a/protocols/valve.js +++ b/protocols/valve.js @@ -36,6 +36,7 @@ class Valve extends Core { } async queryInfo(state) { + if(this.debug) console.log("Requesting info ..."); const b = await this.sendPacket( 0x54, false, @@ -127,6 +128,7 @@ class Valve extends Core { if(this.legacyChallenge) { // sendPacket will catch the response packet and // save the challenge for us + if(this.debug) console.log("Requesting legacy challenge key ..."); await this.sendPacket( 0x57, false, @@ -144,6 +146,7 @@ class Valve extends Core { // Ignore timeouts in only this case const allowTimeout = state.raw.steamappid === 730; + if(this.debug) console.log("Requesting player list ..."); const b = await this.sendPacket( 0x55, true, @@ -177,6 +180,7 @@ class Valve extends Core { async queryRules(state) { state.raw.rules = {}; + if(this.debug) console.log("Requesting rules ..."); const b = await this.sendPacket(0x56,true,null,0x45,true); if (b === null) return; // timed out - the server probably just has rules disabled @@ -248,34 +252,37 @@ class Valve extends Core { allowTimeout ) { for (let keyRetry = 0; keyRetry < 3; keyRetry++) { - let retryQuery = false; + let requestKeyChanged = false; const response = await this.sendPacketRaw( type, sendChallenge, payload, (payload) => { const reader = this.reader(payload); const type = reader.uint(1); + if (this.debug) console.log("Received " + type.toString(16) + " expected " + expect.toString(16)); if (type === 0x41) { const key = reader.uint(4); if (this._challenge !== key) { if (this.debug) console.log('Received new challenge key: ' + key); this._challenge = key; - retryQuery = true; - if (keyRetry === 0 && sendChallenge) { - if (this.debug) console.log('Restarting query'); - return null; + if (sendChallenge) { + if (this.debug) console.log('Challenge key changed -- allowing query retry if needed'); + requestKeyChanged = true; } } } - if (this.debug) console.log("Received " + type.toString(16) + " expected " + expect.toString(16)); if (type === expect) { return reader.rest(); + } else if (requestKeyChanged) { + return null; } }, () => { if (allowTimeout) return null; } ); - if (!retryQuery) return response; + if (!requestKeyChanged) { + return response; + } } throw new Error('Received too many challenge key responses'); }