mirror of
https://github.com/gamedig/node-gamedig.git
synced 2024-11-17 17:25:19 +01:00
Merge branch 'master' into master
This commit is contained in:
commit
10ac17fa01
36 changed files with 1246 additions and 868 deletions
186
CHANGELOG.md
Normal file
186
CHANGELOG.md
Normal file
|
@ -0,0 +1,186 @@
|
||||||
|
### 3.0.8
|
||||||
|
* Fixes player array corruption on some protocols which only report player counts without names (Thanks to a-sync)
|
||||||
|
* Fixes minecraft protocol not using player list from bedrock protocol in some cases
|
||||||
|
|
||||||
|
### 3.0.7
|
||||||
|
* Fixes corrupted dayzMods when packet overflow is present
|
||||||
|
|
||||||
|
### 3.0.6
|
||||||
|
* raw.tags for valve servers is now an array rather than a string
|
||||||
|
* The special mod list for dayz servers is now parsed into raw.dayzMods is requestRules is set to true
|
||||||
|
* DayZ queue length, day and night acceleration are now parsed into raw as well
|
||||||
|
|
||||||
|
### 3.0.5
|
||||||
|
* Add support for `listenUdpPort` to specify a fixed bind port.
|
||||||
|
* Improved udp bind failure detection.
|
||||||
|
|
||||||
|
### 3.0.4
|
||||||
|
* Add support for Discord widget
|
||||||
|
|
||||||
|
### 3.0.3
|
||||||
|
* Greatly improve gamespy1 protocol, with additional error handling and xserverquery support.
|
||||||
|
|
||||||
|
### 3.0.2
|
||||||
|
* Fix player name extraction for Unreal Tournament (1999) and possibly
|
||||||
|
other gamespy1 games.
|
||||||
|
|
||||||
|
### 3.0.1
|
||||||
|
* Clarified that nodejs 12 is now required for gamedig 3
|
||||||
|
* Fixed misc player fields not going into `raw` subobject in `assettocorsa`, `fivem`, and `gamespy2`
|
||||||
|
|
||||||
|
### 3.0.0
|
||||||
|
Major Changes:
|
||||||
|
* **NodeJS 12 is now required**
|
||||||
|
* The `name` field is now guaranteed to exist on all player objects. If a player's name is unknown, the `name` will be an empty string.
|
||||||
|
* All non-`name` player fields have been moved into a `raw` sub-field. This means that, like the `raw` subobject of the parent
|
||||||
|
response, all non-`name` fields are now considered to be unstable and may be changed during minor releases of GameDig.
|
||||||
|
* "Rules" are no longer queried for `valve` protocol games by default. Many games do not respond to this query anyways (meaning we have to wait
|
||||||
|
for timeout), and its contents is often not even used since it only exists in the raw subfield. If you depend on rules,
|
||||||
|
you may pass the `requestRules: true` option to re-enable them.
|
||||||
|
* The `raw.steamappid` and `raw.gameid` fields for valve games have been consolidated into `raw.appId`.
|
||||||
|
|
||||||
|
### 2.0.28
|
||||||
|
* Added Valheim (2021)
|
||||||
|
|
||||||
|
### 2.0.27
|
||||||
|
* Reduced chance of protocol collisions between gamespy3 and minecraftbedrock
|
||||||
|
|
||||||
|
### 2.0.26
|
||||||
|
* Added support for the native minecraft bedrock protocol, since some
|
||||||
|
bedrock servers apparently do not respond to the gamespy3 protocol.
|
||||||
|
|
||||||
|
### 2.0.25
|
||||||
|
* Support challenges in A2S_INFO (upcoming change to valve protocol)
|
||||||
|
|
||||||
|
### 2.0.24
|
||||||
|
* Add Savage 2: A Tortured Soul (2008)
|
||||||
|
|
||||||
|
### 2.0.23
|
||||||
|
* Fix Conan Exiles and other games which don't respond to the valve player query
|
||||||
|
* Add givenPortOnly query option for users that require extreme optimization
|
||||||
|
|
||||||
|
### 2.0.22
|
||||||
|
* Updated dependencies
|
||||||
|
|
||||||
|
### 2.0.21
|
||||||
|
* Added Assetto Corsa (2014)
|
||||||
|
* Fixed password flag for Squad
|
||||||
|
* Added Mordhau (2019)
|
||||||
|
* Fixed player count being incorrect in minecraftvanilla protocol in some cases
|
||||||
|
* Updated dependencies
|
||||||
|
* Replaced deprecated Request http library with Got
|
||||||
|
|
||||||
|
### 2.0.20
|
||||||
|
* Fixed minecraft protocol never throwing exceptions
|
||||||
|
|
||||||
|
### 2.0.19
|
||||||
|
* Added Days of War (2017)
|
||||||
|
* Added The Forrest (2014)
|
||||||
|
* Added Just Cause 3 Multiplayer (2017)
|
||||||
|
* Added Project Reality: Battlefield 2 (2005)
|
||||||
|
* Added Quake Live (2010)
|
||||||
|
* Added Contagion (2011)
|
||||||
|
* Added Empyrion: Galactic Survival (2015)
|
||||||
|
* Added PixARK (2018)
|
||||||
|
|
||||||
|
### 2.0.16, 2.0.17, 2.0.18
|
||||||
|
* Various improvements to killing floor / unreal2 protocol
|
||||||
|
|
||||||
|
### 2.0.15
|
||||||
|
* Added Hell Let Loose
|
||||||
|
* Added Rising Storm 2: Vietnam
|
||||||
|
* Added Squad
|
||||||
|
* Fixed DNS lookup not working in some situations when dns.lookup unexpectedly returns a string
|
||||||
|
* Improved minecraft protocol for non-vanilla server implementations (bedrock, waterfall, bungeecord)
|
||||||
|
* Updated dependencies
|
||||||
|
|
||||||
|
### 2.0.14
|
||||||
|
* Node 8 compatibility fixes
|
||||||
|
|
||||||
|
### 2.0.13
|
||||||
|
* Improved logging
|
||||||
|
|
||||||
|
### 2.0.12
|
||||||
|
* Servers are now limited to 10000 players to prevent OOM
|
||||||
|
* Improvements to Starmade (2012)
|
||||||
|
* Added Atlas (2018)
|
||||||
|
|
||||||
|
### 2.0.11
|
||||||
|
* Added Acra Sim Racing
|
||||||
|
* Added Mafia 2: Online
|
||||||
|
|
||||||
|
### 2.0.10
|
||||||
|
* Added rFactor
|
||||||
|
|
||||||
|
### 2.0.9
|
||||||
|
* Added Vice City: Multiplayer
|
||||||
|
|
||||||
|
### 2.0.8
|
||||||
|
* Improve out-of-order packet handling for gamespy1 protocol
|
||||||
|
* Work-around for buggy duplicate player reporting from bf1942 servers
|
||||||
|
* Report team names rather than IDs when possible for gamespy1 protocol
|
||||||
|
|
||||||
|
### 2.0.7
|
||||||
|
* Prevent tcp socket errors from dumping straight to console
|
||||||
|
|
||||||
|
### 2.0.6
|
||||||
|
* Added support for host domains requiring Punycode encoding (special characters)
|
||||||
|
|
||||||
|
### 2.0.5
|
||||||
|
* Added support for Counter-Strike: 2D
|
||||||
|
|
||||||
|
### 2.0.4
|
||||||
|
* Added details about new 2.0 reponse fields to the README.
|
||||||
|
|
||||||
|
### 2.0.3
|
||||||
|
* Added support for Insurgency: Sandstorm
|
||||||
|
|
||||||
|
### 2.0.2
|
||||||
|
* Added support for Starsiege 2009 (starsiege)
|
||||||
|
|
||||||
|
### 2.0.1
|
||||||
|
* Updated readme games list for 2.0
|
||||||
|
* Fixed csgo default port
|
||||||
|
|
||||||
|
### 2.0.0
|
||||||
|
|
||||||
|
##### Breaking API changes
|
||||||
|
* **Node 8 is now required**
|
||||||
|
* Removed the `port_query` option. You can now pass either the server's game port **or** query port in the `port` option, and
|
||||||
|
GameDig will automatically discover the proper port to query. Passing the query port is more likely be successful in
|
||||||
|
unusual cases, as otherwise it must be automatically derived from the game port.
|
||||||
|
* Removed `callback` parameter from Gamedig.query. Only promises are now supported. If you would like to continue
|
||||||
|
using callbacks, you can use node's `util.callbackify` function to convert the method to callback format.
|
||||||
|
* Removed `query` field from response object, as it was poorly documented and unstable.
|
||||||
|
* Removed `notes` field from options / response object. Data can be passed through a standard javascript context if needed.
|
||||||
|
|
||||||
|
##### Minor Changes
|
||||||
|
* Rewrote core to use promises extensively for better error-handling. Async chains have been dramatically simplified
|
||||||
|
by using async/await across the codebase, eliminating callback chains and the 'async' dependency.
|
||||||
|
* Replaced `--output pretty` cli parameter with `--pretty`.
|
||||||
|
* You can now query from CLI using shorthand syntax: `gamedig --type <gameid> <ip>[:<port>]`
|
||||||
|
* UDP socket is only opened if needed by a query.
|
||||||
|
* Automatic query port detection -- If provided with a non-standard port, gamedig will attempt to discover if it is a
|
||||||
|
game port or query port by querying twice: once to the port provided, and once to the port including the game's query
|
||||||
|
port offset (if available).
|
||||||
|
* Added new `connect` field to the response object. This will typically include the game's `ip:port` (the port will reflect the server's
|
||||||
|
game port, even if you passed in a query port in your request). For some games, this may be a server ID or connection url
|
||||||
|
if an IP:Port is not appropriate.
|
||||||
|
* Added new `ping` field (in milliseconds) to the response object. As icmp packets are often blocked by NATs, and node has poor support
|
||||||
|
for raw sockets, this time is derived from the rtt of one of the UDP requests, or the time required to open a TCP socket
|
||||||
|
during the query.
|
||||||
|
* Improved debug logging across all parts of GameDig
|
||||||
|
* Removed global `Gamedig.debug`. `debug` is now an option on each query.
|
||||||
|
|
||||||
|
##### Protocol Changes
|
||||||
|
* Added support for games using older versions of battlefield protocol.
|
||||||
|
* Simplified detection of BC2 when using battlefield protocol.
|
||||||
|
* Fixed buildandshoot not reading player list
|
||||||
|
* Standardized all doom3 games into a single protocol, which can discover protocol discrepancies automatically.
|
||||||
|
* Standardized all gamespy2 games into a single protocol, which can discover protocol discrepancies automatically.
|
||||||
|
* Standardized all gamespy3 games into a single protocol, which can discover protocol discrepancies automatically.
|
||||||
|
* Improved valve protocol challenge key retry process
|
||||||
|
|
||||||
|
### 1.0.0
|
||||||
|
* First official release
|
||||||
|
* Node.js 6 is now required
|
347
README.md
347
README.md
|
@ -43,6 +43,7 @@ this query port may work instead. (defaults to protocol default port)
|
||||||
will cause many queries to take longer even if the server is online. (default 2000)
|
will cause many queries to take longer even if the server is online. (default 2000)
|
||||||
* **attemptTimeout**: number - Milliseconds allowed for an entire query attempt. This timeout is not commonly hit,
|
* **attemptTimeout**: number - Milliseconds allowed for an entire query attempt. This timeout is not commonly hit,
|
||||||
as the socketTimeout typically fires first. (default 10000)
|
as the socketTimeout typically fires first. (default 10000)
|
||||||
|
* **givenPortOnly**: boolean - Only attempt to query server on given port. (default false)
|
||||||
* **debug**: boolean - Enables massive amounts of debug logging to stdout. (default false)
|
* **debug**: boolean - Enables massive amounts of debug logging to stdout. (default false)
|
||||||
|
|
||||||
### Return Value
|
### Return Value
|
||||||
|
@ -54,10 +55,9 @@ The returned state object will contain the following keys:
|
||||||
* **password**: boolean - If a password is required
|
* **password**: boolean - If a password is required
|
||||||
* **maxplayers**: number
|
* **maxplayers**: number
|
||||||
* **players**: array of objects
|
* **players**: array of objects
|
||||||
* Each object **may or may not** contain name, ping, score, team, address.
|
* **name**: string - If the player's name is unknown, the string will be empty.
|
||||||
* The number of players online can be determined by `players.length`.
|
* **raw**: object - Additional information about the player if available (unstable)
|
||||||
* For servers which do not provide player names, this may be an array
|
* The content of this field MAY change on a per-protocol basis between GameDig patch releases (although not typical).
|
||||||
of empty objects (ex. `[{},{},{}]`), one for each player without a name.
|
|
||||||
* **bots**: array of objects - Same schema as `players`
|
* **bots**: array of objects - Same schema as `players`
|
||||||
* **connect**: string
|
* **connect**: string
|
||||||
* This will typically include the game's `ip:port`
|
* This will typically include the game's `ip:port`
|
||||||
|
@ -78,32 +78,32 @@ Games List
|
||||||
### Supported
|
### Supported
|
||||||
<!--- BEGIN GENERATED GAMES -->
|
<!--- BEGIN GENERATED GAMES -->
|
||||||
|
|
||||||
| GameDig Type ID | Name | Notes
|
| GameDig Type ID | Name | See Also
|
||||||
|---|---|---
|
|---|---|---
|
||||||
| `7d2d` | 7 Days to Die (2013)
|
| `7d2d` | 7 Days to Die (2013) | [Valve Protocol](#valve)
|
||||||
| `ageofchivalry` | Age of Chivalry (2007)
|
| `ageofchivalry` | Age of Chivalry (2007) | [Valve Protocol](#valve)
|
||||||
| `aoe2` | Age of Empires 2 (1999)
|
| `aoe2` | Age of Empires 2 (1999)
|
||||||
| `alienarena` | Alien Arena (2004)
|
| `alienarena` | Alien Arena (2004)
|
||||||
| `alienswarm` | Alien Swarm (2010)
|
| `alienswarm` | Alien Swarm (2010) | [Valve Protocol](#valve)
|
||||||
| `avp2` | Aliens versus Predator 2 (2001)
|
| `avp2` | Aliens versus Predator 2 (2001)
|
||||||
| `avp2010` | Aliens vs. Predator (2010)
|
| `avp2010` | Aliens vs. Predator (2010) | [Valve Protocol](#valve)
|
||||||
| `americasarmy` | America's Army (2002)
|
| `americasarmy` | America's Army (2002)
|
||||||
| `americasarmy2` | America's Army 2 (2003)
|
| `americasarmy2` | America's Army 2 (2003)
|
||||||
| `americasarmy3` | America's Army 3 (2009)
|
| `americasarmy3` | America's Army 3 (2009) | [Valve Protocol](#valve)
|
||||||
| `americasarmypg` | America's Army: Proving Grounds (2015)
|
| `americasarmypg` | America's Army: Proving Grounds (2015) | [Valve Protocol](#valve)
|
||||||
| `arcasimracing` | Arca Sim Racing (2008)
|
| `arcasimracing` | Arca Sim Racing (2008)
|
||||||
| `arkse` | Ark: Survival Evolved (2017)
|
| `arkse` | Ark: Survival Evolved (2017) | [Valve Protocol](#valve)
|
||||||
| `arma2` | ARMA 2 (2009)
|
| `arma2` | ARMA 2 (2009) | [Valve Protocol](#valve)
|
||||||
| `arma2oa` | ARMA 2: Operation Arrowhead (2010)
|
| `arma2oa` | ARMA 2: Operation Arrowhead (2010) | [Valve Protocol](#valve)
|
||||||
| `arma3` | ARMA 3 (2013)
|
| `arma3` | ARMA 3 (2013) | [Valve Protocol](#valve)
|
||||||
| `arma` | ARMA: Armed Assault (2007)
|
| `arma` | ARMA: Armed Assault (2007)
|
||||||
| `armacwa` | ARMA: Cold War Assault (2011)
|
| `armacwa` | ARMA: Cold War Assault (2011)
|
||||||
| `armar` | ARMA: Resistance (2011)
|
| `armar` | ARMA: Resistance (2011)
|
||||||
| `armagetron` | Armagetron Advanced (2001)
|
| `armagetron` | Armagetron Advanced (2001)
|
||||||
| `assettocorsa` | Assetto Corsa (2014)
|
| `assettocorsa` | Assetto Corsa (2014)
|
||||||
| `atlas` | Atlas (2018)
|
| `atlas` | Atlas (2018) | [Valve Protocol](#valve)
|
||||||
| `baldursgate` | Baldur's Gate (1998)
|
| `baldursgate` | Baldur's Gate (1998)
|
||||||
| `bat1944` | Battalion 1944 (2018)
|
| `bat1944` | Battalion 1944 (2018) | [Valve Protocol](#valve)
|
||||||
| `bf1942` | Battlefield 1942 (2002)
|
| `bf1942` | Battlefield 1942 (2002)
|
||||||
| `bf2` | Battlefield 2 (2005)
|
| `bf2` | Battlefield 2 (2005)
|
||||||
| `bf2142` | Battlefield 2142 (2006)
|
| `bf2142` | Battlefield 2142 (2006)
|
||||||
|
@ -112,16 +112,16 @@ Games List
|
||||||
| `bfh` | Battlefield Hardline (2015)
|
| `bfh` | Battlefield Hardline (2015)
|
||||||
| `bfv` | Battlefield Vietnam (2004)
|
| `bfv` | Battlefield Vietnam (2004)
|
||||||
| `bfbc2` | Battlefield: Bad Company 2 (2010)
|
| `bfbc2` | Battlefield: Bad Company 2 (2010)
|
||||||
| `breach` | Breach (2011)
|
| `breach` | Breach (2011) | [Valve Protocol](#valve)
|
||||||
| `breed` | Breed (2004)
|
| `breed` | Breed (2004)
|
||||||
| `brink` | Brink (2011)
|
| `brink` | Brink (2011) | [Valve Protocol](#valve)
|
||||||
| `buildandshoot` | Build and Shoot / Ace of Spades Classic (2012)
|
| `buildandshoot` | Build and Shoot / Ace of Spades Classic (2012)
|
||||||
| `cod` | Call of Duty (2003)
|
| `cod` | Call of Duty (2003)
|
||||||
| `cod2` | Call of Duty 2 (2005)
|
| `cod2` | Call of Duty 2 (2005)
|
||||||
| `cod3` | Call of Duty 3 (2006)
|
| `cod3` | Call of Duty 3 (2006)
|
||||||
| `cod4` | Call of Duty 4: Modern Warfare (2007)
|
| `cod4` | Call of Duty 4: Modern Warfare (2007)
|
||||||
| `codmw2` | Call of Duty: Modern Warfare 2 (2009)
|
| `codmw2` | Call of Duty: Modern Warfare 2 (2009)
|
||||||
| `codmw3` | Call of Duty: Modern Warfare 3 (2011)
|
| `codmw3` | Call of Duty: Modern Warfare 3 (2011) | [Valve Protocol](#valve)
|
||||||
| `coduo` | Call of Duty: United Offensive (2004)
|
| `coduo` | Call of Duty: United Offensive (2004)
|
||||||
| `codwaw` | Call of Duty: World at War (2008)
|
| `codwaw` | Call of Duty: World at War (2008)
|
||||||
| `callofjuarez` | Call of Juarez (2006)
|
| `callofjuarez` | Call of Juarez (2006)
|
||||||
|
@ -130,86 +130,87 @@ Games List
|
||||||
| `codenameeagle` | Codename Eagle (2000)
|
| `codenameeagle` | Codename Eagle (2000)
|
||||||
| `cacrenegade` | Command and Conquer: Renegade (2002)
|
| `cacrenegade` | Command and Conquer: Renegade (2002)
|
||||||
| `commandos3` | Commandos 3: Destination Berlin (2003)
|
| `commandos3` | Commandos 3: Destination Berlin (2003)
|
||||||
| `conanexiles` | Conan Exiles (2018)
|
| `conanexiles` | Conan Exiles (2018) | [Valve Protocol](#valve)
|
||||||
| `contagion` | Contagion (2011)
|
| `contagion` | Contagion (2011) | [Valve Protocol](#valve)
|
||||||
| `contactjack` | Contract J.A.C.K. (2003)
|
| `contactjack` | Contract J.A.C.K. (2003)
|
||||||
| `cs15` | Counter-Strike 1.5 (2002)
|
| `cs15` | Counter-Strike 1.5 (2002) | [Valve Protocol](#valve)
|
||||||
| `cs16` | Counter-Strike 1.6 (2003)
|
| `cs16` | Counter-Strike 1.6 (2003) | [Valve Protocol](#valve)
|
||||||
| `cs2d` | Counter-Strike: 2D (2004)
|
| `cs2d` | Counter-Strike: 2D (2004)
|
||||||
| `cscz` | Counter-Strike: Condition Zero (2004)
|
| `cscz` | Counter-Strike: Condition Zero (2004) | [Valve Protocol](#valve)
|
||||||
| `csgo` | Counter-Strike: Global Offensive (2012) | [Notes](#csgo)
|
| `csgo` | Counter-Strike: Global Offensive (2012) | [Notes](#csgo), [Valve Protocol](#valve)
|
||||||
| `css` | Counter-Strike: Source (2004)
|
| `css` | Counter-Strike: Source (2004) | [Valve Protocol](#valve)
|
||||||
| `crossracing` | Cross Racing Championship Extreme 2005 (2005)
|
| `crossracing` | Cross Racing Championship Extreme 2005 (2005)
|
||||||
| `crysis` | Crysis (2007)
|
| `crysis` | Crysis (2007)
|
||||||
| `crysis2` | Crysis 2 (2011)
|
| `crysis2` | Crysis 2 (2011)
|
||||||
| `crysiswars` | Crysis Wars (2008)
|
| `crysiswars` | Crysis Wars (2008)
|
||||||
| `daikatana` | Daikatana (2000)
|
| `daikatana` | Daikatana (2000)
|
||||||
| `dnl` | Dark and Light (2017)
|
| `dnl` | Dark and Light (2017) | [Valve Protocol](#valve)
|
||||||
| `dmomam` | Dark Messiah of Might and Magic (2006)
|
| `dmomam` | Dark Messiah of Might and Magic (2006) | [Valve Protocol](#valve)
|
||||||
| `darkesthour` | Darkest Hour: Europe '44-'45 (2008)
|
| `darkesthour` | Darkest Hour: Europe '44-'45 (2008)
|
||||||
| `dod` | Day of Defeat (2003)
|
| `dod` | Day of Defeat (2003) | [Valve Protocol](#valve)
|
||||||
| `dods` | Day of Defeat: Source (2005)
|
| `dods` | Day of Defeat: Source (2005) | [Valve Protocol](#valve)
|
||||||
| `doi` | Day of Infamy (2017)
|
| `doi` | Day of Infamy (2017) | [Valve Protocol](#valve)
|
||||||
| `daysofwar` | Days of War (2017)
|
| `daysofwar` | Days of War (2017) | [Valve Protocol](#valve)
|
||||||
| `dayz` | DayZ (2018)
|
| `dayz` | DayZ (2018) | [Valve Protocol](#valve)
|
||||||
| `dayzmod` | DayZ Mod (2013)
|
| `dayzmod` | DayZ Mod (2013) | [Valve Protocol](#valve)
|
||||||
| `deadlydozenpt` | Deadly Dozen: Pacific Theater (2002)
|
| `deadlydozenpt` | Deadly Dozen: Pacific Theater (2002)
|
||||||
| `dh2005` | Deer Hunter 2005 (2004)
|
| `dh2005` | Deer Hunter 2005 (2004)
|
||||||
| `descent3` | Descent 3 (1999)
|
| `descent3` | Descent 3 (1999)
|
||||||
| `deusex` | Deus Ex (2000)
|
| `deusex` | Deus Ex (2000)
|
||||||
| `devastation` | Devastation (2003)
|
| `devastation` | Devastation (2003)
|
||||||
| `dinodday` | Dino D-Day (2011)
|
| `dinodday` | Dino D-Day (2011) | [Valve Protocol](#valve)
|
||||||
| `dirttrackracing2` | Dirt Track Racing 2 (2002)
|
| `dirttrackracing2` | Dirt Track Racing 2 (2002)
|
||||||
|
| `discord` | Discord | [Notes](#discord)
|
||||||
| `doom3` | Doom 3 (2004)
|
| `doom3` | Doom 3 (2004)
|
||||||
| `dota2` | Dota 2 (2013)
|
| `dota2` | Dota 2 (2013) | [Valve Protocol](#valve)
|
||||||
| `drakan` | Drakan: Order of the Flame (1999)
|
| `drakan` | Drakan: Order of the Flame (1999)
|
||||||
| `empyrion` | Empyrion - Galactic Survival (2015)
|
| `empyrion` | Empyrion - Galactic Survival (2015) | [Valve Protocol](#valve)
|
||||||
| `etqw` | Enemy Territory: Quake Wars (2007)
|
| `etqw` | Enemy Territory: Quake Wars (2007)
|
||||||
| `fear` | F.E.A.R. (2005)
|
| `fear` | F.E.A.R. (2005)
|
||||||
| `f1c9902` | F1 Challenge '99-'02 (2002)
|
| `f1c9902` | F1 Challenge '99-'02 (2002)
|
||||||
| `farcry` | Far Cry (2004)
|
| `farcry` | Far Cry (2004)
|
||||||
| `farcry2` | Far Cry 2 (2008)
|
| `farcry2` | Far Cry 2 (2008)
|
||||||
| `f12002` | Formula One 2002 (2002)
|
| `f12002` | Formula One 2002 (2002)
|
||||||
| `fortressforever` | Fortress Forever (2007)
|
| `fortressforever` | Fortress Forever (2007) | [Valve Protocol](#valve)
|
||||||
| `ffow` | Frontlines: Fuel of War (2008)
|
| `ffow` | Frontlines: Fuel of War (2008)
|
||||||
| `garrysmod` | Garry's Mod (2004)
|
| `garrysmod` | Garry's Mod (2004) | [Valve Protocol](#valve)
|
||||||
| `geneshift`<br>`mutantfactions` | Geneshift (2017)
|
| `geneshift`<br>`mutantfactions` | Geneshift (2017)
|
||||||
| `giantscitizenkabuto` | Giants: Citizen Kabuto (2000)
|
| `giantscitizenkabuto` | Giants: Citizen Kabuto (2000)
|
||||||
| `globaloperations` | Global Operations (2002)
|
| `globaloperations` | Global Operations (2002)
|
||||||
| `ges` | GoldenEye: Source (2010)
|
| `ges` | GoldenEye: Source (2010) | [Valve Protocol](#valve)
|
||||||
| `gore` | Gore: Ultimate Soldier (2002)
|
| `gore` | Gore: Ultimate Soldier (2002)
|
||||||
| `fivem` | Grand Theft Auto V - FiveM (2013)
|
| `fivem` | Grand Theft Auto V - FiveM (2013)
|
||||||
| `mtasa` | Grand Theft Auto: San Andreas - Multi Theft Auto (2004)
|
| `mtasa` | Grand Theft Auto: San Andreas - Multi Theft Auto (2004)
|
||||||
| `mtavc` | Grand Theft Auto: Vice City - Multi Theft Auto (2002)
|
| `mtavc` | Grand Theft Auto: Vice City - Multi Theft Auto (2002)
|
||||||
| `gunmanchronicles` | Gunman Chronicles (2000)
|
| `gunmanchronicles` | Gunman Chronicles (2000) | [Valve Protocol](#valve)
|
||||||
| `hl2dm` | Half-Life 2: Deathmatch (2004)
|
| `hl2dm` | Half-Life 2: Deathmatch (2004) | [Valve Protocol](#valve)
|
||||||
| `hldm` | Half-Life Deathmatch (1998)
|
| `hldm` | Half-Life Deathmatch (1998) | [Valve Protocol](#valve)
|
||||||
| `hldms` | Half-Life Deathmatch: Source (2005)
|
| `hldms` | Half-Life Deathmatch: Source (2005) | [Valve Protocol](#valve)
|
||||||
| `halo` | Halo (2003)
|
| `halo` | Halo (2003)
|
||||||
| `halo2` | Halo 2 (2007)
|
| `halo2` | Halo 2 (2007)
|
||||||
| `hll` | Hell Let Loose
|
| `hll` | Hell Let Loose | [Valve Protocol](#valve)
|
||||||
| `heretic2` | Heretic II (1998)
|
| `heretic2` | Heretic II (1998)
|
||||||
| `hexen2` | Hexen II (1997)
|
| `hexen2` | Hexen II (1997)
|
||||||
| `had2` | Hidden & Dangerous 2 (2003)
|
| `had2` | Hidden & Dangerous 2 (2003)
|
||||||
| `homefront` | Homefront (2011)
|
| `homefront` | Homefront (2011) | [Valve Protocol](#valve)
|
||||||
| `homeworld2` | Homeworld 2 (2003)
|
| `homeworld2` | Homeworld 2 (2003)
|
||||||
| `hurtworld` | Hurtworld (2015)
|
| `hurtworld` | Hurtworld (2015) | [Valve Protocol](#valve)
|
||||||
| `igi2` | I.G.I.-2: Covert Strike (2003)
|
| `igi2` | I.G.I.-2: Covert Strike (2003)
|
||||||
| `il2` | IL-2 Sturmovik (2001)
|
| `il2` | IL-2 Sturmovik (2001)
|
||||||
| `insurgency` | Insurgency (2014)
|
| `insurgency` | Insurgency (2014) | [Valve Protocol](#valve)
|
||||||
| `insurgencysandstorm` | Insurgency: Sandstorm (2018)
|
| `insurgencysandstorm` | Insurgency: Sandstorm (2018) | [Valve Protocol](#valve)
|
||||||
| `ironstorm` | Iron Storm (2002)
|
| `ironstorm` | Iron Storm (2002)
|
||||||
| `jamesbondnightfire` | James Bond 007: Nightfire (2002)
|
| `jamesbondnightfire` | James Bond 007: Nightfire (2002)
|
||||||
| `jc2mp` | Just Cause 2 - Multiplayer (2010)
|
| `jc2mp` | Just Cause 2 - Multiplayer (2010)
|
||||||
| `jc3mp` | Just Cause 3 - Multiplayer (2017)
|
| `jc3mp` | Just Cause 3 - Multiplayer (2017) | [Valve Protocol](#valve)
|
||||||
| `kspdmp` | Kerbal Space Program - DMP Multiplayer (2015)
|
| `kspdmp` | Kerbal Space Program - DMP Multiplayer (2015)
|
||||||
| `killingfloor` | Killing Floor (2009)
|
| `killingfloor` | Killing Floor (2009)
|
||||||
| `killingfloor2` | Killing Floor 2 (2016)
|
| `killingfloor2` | Killing Floor 2 (2016) | [Valve Protocol](#valve)
|
||||||
| `kingpin` | Kingpin: Life of Crime (1999)
|
| `kingpin` | Kingpin: Life of Crime (1999)
|
||||||
| `kisspc` | Kiss: Psycho Circus: The Nightmare Child (2000)
|
| `kisspc` | Kiss: Psycho Circus: The Nightmare Child (2000)
|
||||||
| `kzmod` | Kreedz Climbing (2017)
|
| `kzmod` | Kreedz Climbing (2017) | [Valve Protocol](#valve)
|
||||||
| `left4dead` | Left 4 Dead (2008)
|
| `left4dead` | Left 4 Dead (2008) | [Valve Protocol](#valve)
|
||||||
| `left4dead2` | Left 4 Dead 2 (2009)
|
| `left4dead2` | Left 4 Dead 2 (2009) | [Valve Protocol](#valve)
|
||||||
| `m2mp` | Mafia II - Multiplayer (2010)
|
| `m2mp` | Mafia II - Multiplayer (2010)
|
||||||
| `m2o` | Mafia II - Online (2010)
|
| `m2o` | Mafia II - Online (2010)
|
||||||
| `moh2010` | Medal of Honor (2010)
|
| `moh2010` | Medal of Honor (2010)
|
||||||
|
@ -219,16 +220,16 @@ Games List
|
||||||
| `mohsh` | Medal of Honor: Allied Assault Spearhead (2002)
|
| `mohsh` | Medal of Honor: Allied Assault Spearhead (2002)
|
||||||
| `mohpa` | Medal of Honor: Pacific Assault (2004)
|
| `mohpa` | Medal of Honor: Pacific Assault (2004)
|
||||||
| `mohwf` | Medal of Honor: Warfighter (2012)
|
| `mohwf` | Medal of Honor: Warfighter (2012)
|
||||||
| `medievalengineers` | Medieval Engineers (2015)
|
| `medievalengineers` | Medieval Engineers (2015) | [Valve Protocol](#valve)
|
||||||
| `minecraft`<br>`minecraftping` | Minecraft (2009)
|
| `minecraft`<br>`minecraftping` | Minecraft (2009)
|
||||||
| `minecraftpe`<br>`minecraftbe` | Minecraft: Bedrock Edition (2011)
|
| `minecraftpe`<br>`minecraftbe` | Minecraft: Bedrock Edition (2011)
|
||||||
| `mnc` | Monday Night Combat (2011)
|
| `mnc` | Monday Night Combat (2011) | [Valve Protocol](#valve)
|
||||||
| `mordhau` | Mordhau (2019)
|
| `mordhau` | Mordhau (2019) | [Valve Protocol](#valve)
|
||||||
| `mumble` | Mumble - GTmurmur Plugin (2005) | [Notes](#mumble)
|
| `mumble` | Mumble - GTmurmur Plugin (2005) | [Notes](#mumble)
|
||||||
| `mumbleping` | Mumble - Lightweight (2005) | [Notes](#mumble)
|
| `mumbleping` | Mumble - Lightweight (2005) | [Notes](#mumble)
|
||||||
| `nascarthunder2004` | NASCAR Thunder 2004 (2003)
|
| `nascarthunder2004` | NASCAR Thunder 2004 (2003)
|
||||||
| `ns` | Natural Selection (2002)
|
| `ns` | Natural Selection (2002) | [Valve Protocol](#valve)
|
||||||
| `ns2` | Natural Selection 2 (2012)
|
| `ns2` | Natural Selection 2 (2012) | [Valve Protocol](#valve)
|
||||||
| `nfshp2` | Need for Speed: Hot Pursuit 2 (2002)
|
| `nfshp2` | Need for Speed: Hot Pursuit 2 (2002)
|
||||||
| `nab` | Nerf Arena Blast (1999)
|
| `nab` | Nerf Arena Blast (1999)
|
||||||
| `netpanzer` | netPanzer (2002)
|
| `netpanzer` | netPanzer (2002)
|
||||||
|
@ -236,56 +237,57 @@ Games List
|
||||||
| `nwn2` | Neverwinter Nights 2 (2006)
|
| `nwn2` | Neverwinter Nights 2 (2006)
|
||||||
| `nexuiz` | Nexuiz (2005)
|
| `nexuiz` | Nexuiz (2005)
|
||||||
| `nitrofamily` | Nitro Family (2004)
|
| `nitrofamily` | Nitro Family (2004)
|
||||||
| `nmrih` | No More Room in Hell (2011)
|
| `nmrih` | No More Room in Hell (2011) | [Valve Protocol](#valve)
|
||||||
| `nolf2` | No One Lives Forever 2: A Spy in H.A.R.M.'s Way (2002)
|
| `nolf2` | No One Lives Forever 2: A Spy in H.A.R.M.'s Way (2002)
|
||||||
| `nucleardawn` | Nuclear Dawn (2011)
|
| `nucleardawn` | Nuclear Dawn (2011) | [Valve Protocol](#valve)
|
||||||
| `openarena` | OpenArena (2005)
|
| `openarena` | OpenArena (2005)
|
||||||
| `openttd` | OpenTTD (2004)
|
| `openttd` | OpenTTD (2004)
|
||||||
| `operationflashpoint`<br>`flashpoint` | Operation Flashpoint: Cold War Crisis (2001)
|
| `operationflashpoint`<br>`flashpoint` | Operation Flashpoint: Cold War Crisis (2001)
|
||||||
| `flashpointresistance` | Operation Flashpoint: Resistance (2002)
|
| `flashpointresistance` | Operation Flashpoint: Resistance (2002)
|
||||||
| `painkiller` | Painkiller
|
| `painkiller` | Painkiller
|
||||||
| `pixark` | PixARK (2018)
|
| `pixark` | PixARK (2018) | [Valve Protocol](#valve)
|
||||||
| `ps` | Post Scriptum
|
| `ps` | Post Scriptum
|
||||||
| `postal2` | Postal 2
|
| `postal2` | Postal 2
|
||||||
| `prey` | Prey
|
| `prey` | Prey
|
||||||
| `primalcarnage` | Primal Carnage: Extinction
|
| `primalcarnage` | Primal Carnage: Extinction | [Valve Protocol](#valve)
|
||||||
| `prbf2` | Project Reality: Battlefield 2 (2005)
|
| `prbf2` | Project Reality: Battlefield 2 (2005)
|
||||||
| `quake1` | Quake 1: QuakeWorld (1996)
|
| `quake1` | Quake 1: QuakeWorld (1996)
|
||||||
| `quake2` | Quake 2 (1997)
|
| `quake2` | Quake 2 (1997)
|
||||||
| `quake3` | Quake 3: Arena (1999)
|
| `quake3` | Quake 3: Arena (1999)
|
||||||
| `quake4` | Quake 4 (2005)
|
| `quake4` | Quake 4 (2005)
|
||||||
| `quakelive` | Quake Live (2010)
|
| `quakelive` | Quake Live (2010) | [Valve Protocol](#valve)
|
||||||
| `ragdollkungfu` | Rag Doll Kung Fu
|
| `ragdollkungfu` | Rag Doll Kung Fu | [Valve Protocol](#valve)
|
||||||
| `r6` | Rainbow Six
|
| `r6` | Rainbow Six
|
||||||
| `r6roguespear` | Rainbow Six 2: Rogue Spear
|
| `r6roguespear` | Rainbow Six 2: Rogue Spear
|
||||||
| `r6ravenshield` | Rainbow Six 3: Raven Shield
|
| `r6ravenshield` | Rainbow Six 3: Raven Shield
|
||||||
| `rallisportchallenge` | RalliSport Challenge
|
| `rallisportchallenge` | RalliSport Challenge
|
||||||
| `rallymasters` | Rally Masters
|
| `rallymasters` | Rally Masters
|
||||||
| `redorchestra` | Red Orchestra
|
| `redorchestra` | Red Orchestra
|
||||||
| `redorchestra2` | Red Orchestra 2
|
| `redorchestra2` | Red Orchestra 2 | [Valve Protocol](#valve)
|
||||||
| `redorchestraost` | Red Orchestra: Ostfront 41-45
|
| `redorchestraost` | Red Orchestra: Ostfront 41-45
|
||||||
| `redline` | Redline
|
| `redline` | Redline
|
||||||
| `rtcw` | Return to Castle Wolfenstein
|
| `rtcw` | Return to Castle Wolfenstein
|
||||||
| `rfactor` | rFactor
|
| `rfactor` | rFactor
|
||||||
| `ricochet` | Ricochet
|
| `ricochet` | Ricochet | [Valve Protocol](#valve)
|
||||||
| `riseofnations` | Rise of Nations
|
| `riseofnations` | Rise of Nations
|
||||||
| `rs2` | Rising Storm 2: Vietnam
|
| `rs2` | Rising Storm 2: Vietnam | [Valve Protocol](#valve)
|
||||||
| `rune` | Rune
|
| `rune` | Rune
|
||||||
| `rust` | Rust
|
| `rust` | Rust | [Valve Protocol](#valve)
|
||||||
| `stalker` | S.T.A.L.K.E.R.
|
| `stalker` | S.T.A.L.K.E.R.
|
||||||
| `samp` | San Andreas Multiplayer
|
| `samp` | San Andreas Multiplayer
|
||||||
|
| `savage2` | Savage 2: A Tortured Soul (2008)
|
||||||
| `ss` | Serious Sam
|
| `ss` | Serious Sam
|
||||||
| `ss2` | Serious Sam 2
|
| `ss2` | Serious Sam 2
|
||||||
| `shatteredhorizon` | Shattered Horizon
|
| `shatteredhorizon` | Shattered Horizon | [Valve Protocol](#valve)
|
||||||
| `shogo` | Shogo
|
| `shogo` | Shogo
|
||||||
| `shootmania` | Shootmania | [Notes](#nadeo-shootmania--trackmania--etc)
|
| `shootmania` | Shootmania | [Notes](#nadeo-shootmania--trackmania--etc)
|
||||||
| `sin` | SiN
|
| `sin` | SiN
|
||||||
| `sinep` | SiN Episodes
|
| `sinep` | SiN Episodes | [Valve Protocol](#valve)
|
||||||
| `soldat` | Soldat
|
| `soldat` | Soldat
|
||||||
| `sof` | Soldier of Fortune
|
| `sof` | Soldier of Fortune
|
||||||
| `sof2` | Soldier of Fortune 2
|
| `sof2` | Soldier of Fortune 2
|
||||||
| `spaceengineers` | Space Engineers
|
| `spaceengineers` | Space Engineers | [Valve Protocol](#valve)
|
||||||
| `squad` | Squad
|
| `squad` | Squad | [Valve Protocol](#valve)
|
||||||
| `stbc` | Star Trek: Bridge Commander
|
| `stbc` | Star Trek: Bridge Commander
|
||||||
| `stvef` | Star Trek: Voyager - Elite Force
|
| `stvef` | Star Trek: Voyager - Elite Force
|
||||||
| `stvef2` | Star Trek: Voyager - Elite Force 2
|
| `stvef2` | Star Trek: Voyager - Elite Force 2
|
||||||
|
@ -294,32 +296,32 @@ Games List
|
||||||
| `swbf` | Star Wars: Battlefront
|
| `swbf` | Star Wars: Battlefront
|
||||||
| `swbf2` | Star Wars: Battlefront 2
|
| `swbf2` | Star Wars: Battlefront 2
|
||||||
| `swrc` | Star Wars: Republic Commando
|
| `swrc` | Star Wars: Republic Commando
|
||||||
| `starbound` | Starbound
|
| `starbound` | Starbound | [Valve Protocol](#valve)
|
||||||
| `starmade` | StarMade
|
| `starmade` | StarMade
|
||||||
| `starsiege` | Starsiege (2009)
|
| `starsiege` | Starsiege (2009)
|
||||||
| `suicidesurvival` | Suicide Survival
|
| `suicidesurvival` | Suicide Survival | [Valve Protocol](#valve)
|
||||||
| `svencoop` | Sven Coop
|
| `svencoop` | Sven Coop | [Valve Protocol](#valve)
|
||||||
| `swat4` | SWAT 4
|
| `swat4` | SWAT 4
|
||||||
| `synergy` | Synergy
|
| `synergy` | Synergy | [Valve Protocol](#valve)
|
||||||
| `tacticalops` | Tactical Ops
|
| `tacticalops` | Tactical Ops
|
||||||
| `takeonhelicopters` | Take On Helicopters (2011)
|
| `takeonhelicopters` | Take On Helicopters (2011)
|
||||||
| `teamfactor` | Team Factor
|
| `teamfactor` | Team Factor
|
||||||
| `tf2` | Team Fortress 2
|
| `tf2` | Team Fortress 2 | [Valve Protocol](#valve)
|
||||||
| `tfc` | Team Fortress Classic
|
| `tfc` | Team Fortress Classic | [Valve Protocol](#valve)
|
||||||
| `teamspeak2` | Teamspeak 2
|
| `teamspeak2` | Teamspeak 2
|
||||||
| `teamspeak3` | Teamspeak 3 | [Notes](#teamspeak3)
|
| `teamspeak3` | Teamspeak 3 | [Notes](#teamspeak3)
|
||||||
| `terminus` | Terminus
|
| `terminus` | Terminus
|
||||||
| `terraria`<br>`tshock` | Terraria - TShock (2011) | [Notes](#terraria)
|
| `terraria`<br>`tshock` | Terraria - TShock (2011) | [Notes](#terraria)
|
||||||
| `forrest` | The Forrest (2014)
|
| `forrest` | The Forrest (2014) | [Valve Protocol](#valve)
|
||||||
| `hidden` | The Hidden (2005)
|
| `hidden` | The Hidden (2005) | [Valve Protocol](#valve)
|
||||||
| `nolf` | The Operative: No One Lives Forever (2000)
|
| `nolf` | The Operative: No One Lives Forever (2000)
|
||||||
| `ship` | The Ship
|
| `ship` | The Ship | [Valve Protocol](#valve)
|
||||||
| `graw` | Tom Clancy's Ghost Recon Advanced Warfighter (2006)
|
| `graw` | Tom Clancy's Ghost Recon Advanced Warfighter (2006)
|
||||||
| `graw2` | Tom Clancy's Ghost Recon Advanced Warfighter 2 (2007)
|
| `graw2` | Tom Clancy's Ghost Recon Advanced Warfighter 2 (2007)
|
||||||
| `thps3` | Tony Hawk's Pro Skater 3
|
| `thps3` | Tony Hawk's Pro Skater 3
|
||||||
| `thps4` | Tony Hawk's Pro Skater 4
|
| `thps4` | Tony Hawk's Pro Skater 4
|
||||||
| `thu2` | Tony Hawk's Underground 2
|
| `thu2` | Tony Hawk's Underground 2
|
||||||
| `towerunite` | Tower Unite
|
| `towerunite` | Tower Unite | [Valve Protocol](#valve)
|
||||||
| `trackmania2` | Trackmania 2 | [Notes](#nadeo-shootmania--trackmania--etc)
|
| `trackmania2` | Trackmania 2 | [Notes](#nadeo-shootmania--trackmania--etc)
|
||||||
| `trackmaniaforever` | Trackmania Forever | [Notes](#nadeo-shootmania--trackmania--etc)
|
| `trackmaniaforever` | Trackmania Forever | [Notes](#nadeo-shootmania--trackmania--etc)
|
||||||
| `tremulous` | Tremulous
|
| `tremulous` | Tremulous
|
||||||
|
@ -333,9 +335,10 @@ Games List
|
||||||
| `ut2003` | Unreal Tournament 2003
|
| `ut2003` | Unreal Tournament 2003
|
||||||
| `ut2004` | Unreal Tournament 2004
|
| `ut2004` | Unreal Tournament 2004
|
||||||
| `ut3` | Unreal Tournament 3
|
| `ut3` | Unreal Tournament 3
|
||||||
| `unturned` | unturned
|
| `unturned` | unturned | [Valve Protocol](#valve)
|
||||||
| `urbanterror` | Urban Terror
|
| `urbanterror` | Urban Terror
|
||||||
| `v8supercar` | V8 Supercar Challenge
|
| `v8supercar` | V8 Supercar Challenge
|
||||||
|
| `valheim` | Valheim (2021) | [Notes](#valheim), [Valve Protocol](#valve)
|
||||||
| `ventrilo` | Ventrilo
|
| `ventrilo` | Ventrilo
|
||||||
| `vcmp` | Vice City Multiplayer
|
| `vcmp` | Vice City Multiplayer
|
||||||
| `vietcong` | Vietcong
|
| `vietcong` | Vietcong
|
||||||
|
@ -345,8 +348,8 @@ Games List
|
||||||
| `wolfenstein2009` | Wolfenstein 2009
|
| `wolfenstein2009` | Wolfenstein 2009
|
||||||
| `wolfensteinet` | Wolfenstein: Enemy Territory
|
| `wolfensteinet` | Wolfenstein: Enemy Territory
|
||||||
| `xpandrally` | Xpand Rally
|
| `xpandrally` | Xpand Rally
|
||||||
| `zombiemaster` | Zombie Master
|
| `zombiemaster` | Zombie Master | [Valve Protocol](#valve)
|
||||||
| `zps` | Zombie Panic: Source
|
| `zps` | Zombie Panic: Source | [Valve Protocol](#valve)
|
||||||
|
|
||||||
<!--- END GENERATED GAMES -->
|
<!--- END GENERATED GAMES -->
|
||||||
|
|
||||||
|
@ -385,7 +388,6 @@ Games List
|
||||||
* Red Faction
|
* Red Faction
|
||||||
* S.T.A.L.K.E.R. Clear Sky
|
* S.T.A.L.K.E.R. Clear Sky
|
||||||
* Savage: The Battle For Newerth
|
* Savage: The Battle For Newerth
|
||||||
* Savage 2: A Tortured Soul
|
|
||||||
* SiN 1 Multiplayer
|
* SiN 1 Multiplayer
|
||||||
* South Park
|
* South Park
|
||||||
* Star Wars Jedi Knight: Dark Forces II
|
* Star Wars Jedi Knight: Dark Forces II
|
||||||
|
@ -402,7 +404,7 @@ Games List
|
||||||
|
|
||||||
> Want support for one of these games? Please open an issue to show your interest!
|
> Want support for one of these games? Please open an issue to show your interest!
|
||||||
> __Know how to code?__ Protocol details for many of the games above are documented
|
> __Know how to code?__ Protocol details for many of the games above are documented
|
||||||
> at https://github.com/sonicsnes/legacy-query-library-archive
|
> at https://github.com/gamedig/legacy-query-library-archive
|
||||||
> , ready for you to develop into GameDig!
|
> , ready for you to develop into GameDig!
|
||||||
|
|
||||||
> Don't see your game listed here?
|
> Don't see your game listed here?
|
||||||
|
@ -428,6 +430,11 @@ Games with Additional Notes
|
||||||
To receive a full player list response from CS:GO servers, the server must
|
To receive a full player list response from CS:GO servers, the server must
|
||||||
have set the cvar: host_players_show 2
|
have set the cvar: host_players_show 2
|
||||||
|
|
||||||
|
### Discord
|
||||||
|
You must set the `guildId` request field to the server's guild ID. Do not provide an IP.
|
||||||
|
The Guild ID can be found in server widget settings (Server ID) or by enabling developer mode in client settings and right-clicking the server's icon.
|
||||||
|
In order to retrieve information from discord server's they must have the `Enable server widget` option enabled.
|
||||||
|
|
||||||
### Mumble
|
### Mumble
|
||||||
For full query results from Mumble, you must be running the
|
For full query results from Mumble, you must be running the
|
||||||
[GTmurmur plugin](http://www.gametracker.com/downloads/gtmurmurplugin.php).
|
[GTmurmur plugin](http://www.gametracker.com/downloads/gtmurmurplugin.php).
|
||||||
|
@ -452,7 +459,49 @@ For teamspeak 3 queries to work correctly, the following permissions must be ava
|
||||||
|
|
||||||
### Terraria
|
### Terraria
|
||||||
Requires tshock server mod, and a REST user token, which can be passed to GameDig with the
|
Requires tshock server mod, and a REST user token, which can be passed to GameDig with the
|
||||||
additional option: token
|
additional option: `token`
|
||||||
|
|
||||||
|
### Valheim
|
||||||
|
Valheim servers will only respond to queries if they are started in public mode (`-public 1`).
|
||||||
|
|
||||||
|
### <a name="valve"></a>Valve Protocol
|
||||||
|
For many valve games, additional 'rules' may be fetched into the unstable `raw` field by passing the additional
|
||||||
|
option: `requestRules: true`. Beware that this may increase query time.
|
||||||
|
|
||||||
|
Common Issues
|
||||||
|
---
|
||||||
|
|
||||||
|
### Firewalls block incoming UDP
|
||||||
|
*(replit / docker / some VPS providers)*
|
||||||
|
|
||||||
|
Most game query protocols require a UDP request and response. This means that in some environments, gamedig may not be able to receive the reponse required due to environmental restrictions.
|
||||||
|
|
||||||
|
Some examples include:
|
||||||
|
* Docker containers
|
||||||
|
* You may need to run the container in `--network host` mode so that gamedig can bind a UDP listen port.
|
||||||
|
* Alternatively, you can forward a single UDP port to your container, and force gamedig to listen on that port using the
|
||||||
|
instructions in the section down below.
|
||||||
|
* replit
|
||||||
|
* Most online IDEs run in an isolated container, which will never receive UDP responses from outside networks.
|
||||||
|
* Various VPS / server providers
|
||||||
|
* Even if your server provider doesn't explicitly block incoming UDP packets, some server hosts block other server hosts from connecting to them for DDOS-mitigation and anti-botting purposes.
|
||||||
|
|
||||||
|
### Gamedig doesn't work in the browser
|
||||||
|
Gamedig cannot operate within a browser. This means you cannot package it as part of your webpack / browserify / rollup / parcel package.
|
||||||
|
Even if you were able to get it packaged into a bundle, unfortunately no browsers support the UDP protocols required to query server status
|
||||||
|
from most game servers. As an alternative, we'd recommend using gamedig on your server-side, then expose your own API to your webapp's frontend
|
||||||
|
displaying the status information. If your application is thin (with no constant server component), you may wish to investigate a server-less lambda provider.
|
||||||
|
|
||||||
|
### Specifying a listen UDP port override
|
||||||
|
In some very rare scenarios, you may need to bind / listen on a fixed local UDP port. The is usually not needed except behind
|
||||||
|
some extremely strict firewalls, or within a docker container (where you only wish to forward a single UDP port).
|
||||||
|
To use a fixed listen udp port, construct a new Gamedig object like this:
|
||||||
|
```
|
||||||
|
const gamedig = new Gamedig({
|
||||||
|
listenUdpPort: 13337
|
||||||
|
});
|
||||||
|
gamedig.query(...)
|
||||||
|
```
|
||||||
|
|
||||||
Usage from Command Line
|
Usage from Command Line
|
||||||
---
|
---
|
||||||
|
@ -470,121 +519,3 @@ gamedig --type minecraft mc.example.com:11234
|
||||||
|
|
||||||
The output of the command will be in JSON format. Additional advanced parameters can be passed in
|
The output of the command will be in JSON format. Additional advanced parameters can be passed in
|
||||||
as well: `--debug`, `--pretty`, `--socketTimeout 5000`, etc.
|
as well: `--debug`, `--pretty`, `--socketTimeout 5000`, etc.
|
||||||
|
|
||||||
Changelog
|
|
||||||
---
|
|
||||||
|
|
||||||
### 2.0.20
|
|
||||||
* Fixed minecraft protocol never throwing exceptions
|
|
||||||
|
|
||||||
### 2.0.19
|
|
||||||
* Added Days of War (2017)
|
|
||||||
* Added The Forrest (2014)
|
|
||||||
* Added Just Cause 3 Multiplayer (2017)
|
|
||||||
* Added Project Reality: Battlefield 2 (2005)
|
|
||||||
* Added Quake Live (2010)
|
|
||||||
* Added Contagion (2011)
|
|
||||||
* Added Empyrion: Galactic Survival (2015)
|
|
||||||
* Added PixARK (2018)
|
|
||||||
|
|
||||||
### 2.0.16, 2.0.17, 2.0.18
|
|
||||||
* Various improvements to killing floor / unreal2 protocol
|
|
||||||
|
|
||||||
### 2.0.15
|
|
||||||
* Added Hell Let Loose
|
|
||||||
* Added Rising Storm 2: Vietnam
|
|
||||||
* Added Squad
|
|
||||||
* Fixed DNS lookup not working in some situations when dns.lookup unexpectedly returns a string
|
|
||||||
* Improved minecraft protocol for non-vanilla server implementations (bedrock, waterfall, bungeecord)
|
|
||||||
* Updated dependencies
|
|
||||||
|
|
||||||
### 2.0.14
|
|
||||||
* Node 8 compatibility fixes
|
|
||||||
|
|
||||||
### 2.0.13
|
|
||||||
* Improved logging
|
|
||||||
|
|
||||||
### 2.0.12
|
|
||||||
* Servers are now limited to 10000 players to prevent OOM
|
|
||||||
* Improvements to Starmade (2012)
|
|
||||||
* Added Atlas (2018)
|
|
||||||
|
|
||||||
### 2.0.11
|
|
||||||
* Added Acra Sim Racing
|
|
||||||
* Added Mafia 2: Online
|
|
||||||
|
|
||||||
### 2.0.10
|
|
||||||
* Added rFactor
|
|
||||||
|
|
||||||
### 2.0.9
|
|
||||||
* Added Vice City: Multiplayer
|
|
||||||
|
|
||||||
### 2.0.8
|
|
||||||
* Improve out-of-order packet handling for gamespy1 protocol
|
|
||||||
* Work-around for buggy duplicate player reporting from bf1942 servers
|
|
||||||
* Report team names rather than IDs when possible for gamespy1 protocol
|
|
||||||
|
|
||||||
### 2.0.7
|
|
||||||
* Prevent tcp socket errors from dumping straight to console
|
|
||||||
|
|
||||||
### 2.0.6
|
|
||||||
* Added support for host domains requiring Punycode encoding (special characters)
|
|
||||||
|
|
||||||
### 2.0.5
|
|
||||||
* Added support for Counter-Strike: 2D
|
|
||||||
|
|
||||||
### 2.0.4
|
|
||||||
* Added details about new 2.0 reponse fields to the README.
|
|
||||||
|
|
||||||
### 2.0.3
|
|
||||||
* Added support for Insurgency: Sandstorm
|
|
||||||
|
|
||||||
### 2.0.2
|
|
||||||
* Added support for Starsiege 2009 (starsiege)
|
|
||||||
|
|
||||||
### 2.0.1
|
|
||||||
* Updated readme games list for 2.0
|
|
||||||
* Fixed csgo default port
|
|
||||||
|
|
||||||
### 2.0.0
|
|
||||||
|
|
||||||
##### Breaking API changes
|
|
||||||
* **Node 8 is now required**
|
|
||||||
* Removed the `port_query` option. You can now pass either the server's game port **or** query port in the `port` option, and
|
|
||||||
GameDig will automatically discover the proper port to query. Passing the query port is more likely be successful in
|
|
||||||
unusual cases, as otherwise it must be automatically derived from the game port.
|
|
||||||
* Removed `callback` parameter from Gamedig.query. Only promises are now supported. If you would like to continue
|
|
||||||
using callbacks, you can use node's `util.callbackify` function to convert the method to callback format.
|
|
||||||
* Removed `query` field from response object, as it was poorly documented and unstable.
|
|
||||||
* Removed `notes` field from options / response object. Data can be passed through a standard javascript context if needed.
|
|
||||||
|
|
||||||
##### Minor Changes
|
|
||||||
* Rewrote core to use promises extensively for better error-handling. Async chains have been dramatically simplified
|
|
||||||
by using async/await across the codebase, eliminating callback chains and the 'async' dependency.
|
|
||||||
* Replaced `--output pretty` cli parameter with `--pretty`.
|
|
||||||
* You can now query from CLI using shorthand syntax: `gamedig --type <gameid> <ip>[:<port>]`
|
|
||||||
* UDP socket is only opened if needed by a query.
|
|
||||||
* Automatic query port detection -- If provided with a non-standard port, gamedig will attempt to discover if it is a
|
|
||||||
game port or query port by querying twice: once to the port provided, and once to the port including the game's query
|
|
||||||
port offset (if available).
|
|
||||||
* Added new `connect` field to the response object. This will typically include the game's `ip:port` (the port will reflect the server's
|
|
||||||
game port, even if you passed in a query port in your request). For some games, this may be a server ID or connection url
|
|
||||||
if an IP:Port is not appropriate.
|
|
||||||
* Added new `ping` field (in milliseconds) to the response object. As icmp packets are often blocked by NATs, and node has poor support
|
|
||||||
for raw sockets, this time is derived from the rtt of one of the UDP requests, or the time required to open a TCP socket
|
|
||||||
during the query.
|
|
||||||
* Improved debug logging across all parts of GameDig
|
|
||||||
* Removed global `Gamedig.debug`. `debug` is now an option on each query.
|
|
||||||
|
|
||||||
##### Protocol Changes
|
|
||||||
* Added support for games using older versions of battlefield protocol.
|
|
||||||
* Simplified detection of BC2 when using battlefield protocol.
|
|
||||||
* Fixed buildandshoot not reading player list
|
|
||||||
* Standardized all doom3 games into a single protocol, which can discover protocol discrepancies automatically.
|
|
||||||
* Standardized all gamespy2 games into a single protocol, which can discover protocol discrepancies automatically.
|
|
||||||
* Standardized all gamespy3 games into a single protocol, which can discover protocol discrepancies automatically.
|
|
||||||
* Improved valve protocol challenge key retry process
|
|
||||||
|
|
||||||
### 1.0.0
|
|
||||||
* First official release
|
|
||||||
* Node.js 6 is now required
|
|
||||||
|
|
11
bin/gamedig.js
Normal file → Executable file
11
bin/gamedig.js
Normal file → Executable file
|
@ -4,13 +4,16 @@ const Minimist = require('minimist'),
|
||||||
Gamedig = require('..');
|
Gamedig = require('..');
|
||||||
|
|
||||||
const argv = Minimist(process.argv.slice(2), {
|
const argv = Minimist(process.argv.slice(2), {
|
||||||
boolean: ['pretty','debug']
|
boolean: ['pretty','debug','givenPortOnly','requestRules'],
|
||||||
|
string: ['guildId','listenUdpPort']
|
||||||
});
|
});
|
||||||
|
|
||||||
const debug = argv.debug;
|
const debug = argv.debug;
|
||||||
delete argv.debug;
|
delete argv.debug;
|
||||||
const pretty = !!argv.pretty || debug;
|
const pretty = !!argv.pretty || debug;
|
||||||
delete argv.pretty;
|
delete argv.pretty;
|
||||||
|
const givenPortOnly = argv.givenPortOnly;
|
||||||
|
delete argv.givenPortOnly;
|
||||||
|
|
||||||
const options = {};
|
const options = {};
|
||||||
for(const key of Object.keys(argv)) {
|
for(const key of Object.keys(argv)) {
|
||||||
|
@ -34,8 +37,12 @@ if (argv._.length >= 1) {
|
||||||
if (debug) {
|
if (debug) {
|
||||||
options.debug = true;
|
options.debug = true;
|
||||||
}
|
}
|
||||||
|
if (givenPortOnly) {
|
||||||
|
options.givenPortOnly = true;
|
||||||
|
}
|
||||||
|
|
||||||
Gamedig.query(options)
|
const gamedig = new Gamedig(options);
|
||||||
|
gamedig.query(options)
|
||||||
.then((state) => {
|
.then((state) => {
|
||||||
if(pretty) {
|
if(pretty) {
|
||||||
console.log(JSON.stringify(state,null,' '));
|
console.log(JSON.stringify(state,null,' '));
|
||||||
|
|
|
@ -88,6 +88,7 @@ deusex|Deus Ex (2000)|gamespy2|port=7791,port_query_offset=1
|
||||||
devastation|Devastation (2003)|unreal2|port=7777,port_query_offset=1
|
devastation|Devastation (2003)|unreal2|port=7777,port_query_offset=1
|
||||||
dinodday|Dino D-Day (2011)|valve|port=27015
|
dinodday|Dino D-Day (2011)|valve|port=27015
|
||||||
dirttrackracing2|Dirt Track Racing 2 (2002)|gamespy1|port=32240,port_query_offset=-100
|
dirttrackracing2|Dirt Track Racing 2 (2002)|gamespy1|port=32240,port_query_offset=-100
|
||||||
|
discord|Discord|discord||doc_notes=discord
|
||||||
dnl|Dark and Light (2017)|valve|port=7777,port_query=27015
|
dnl|Dark and Light (2017)|valve|port=7777,port_query=27015
|
||||||
dod|Day of Defeat (2003)|valve|port=27015
|
dod|Day of Defeat (2003)|valve|port=27015
|
||||||
dods|Day of Defeat: Source (2005)|valve|port=27015
|
dods|Day of Defeat: Source (2005)|valve|port=27015
|
||||||
|
@ -217,6 +218,7 @@ rs2|Rising Storm 2: Vietnam|valve|port=27015
|
||||||
rune|Rune|gamespy1|port=7777,port_query_offset=1
|
rune|Rune|gamespy1|port=7777,port_query_offset=1
|
||||||
rust|Rust|valve|port=28015
|
rust|Rust|valve|port=28015
|
||||||
samp|San Andreas Multiplayer|samp|port=7777
|
samp|San Andreas Multiplayer|samp|port=7777
|
||||||
|
savage2|Savage 2: A Tortured Soul (2008)|savage2|port_query=11235
|
||||||
spaceengineers|Space Engineers|valve|port=27015
|
spaceengineers|Space Engineers|valve|port=27015
|
||||||
ss|Serious Sam|gamespy1|port=25600,port_query_offset=1
|
ss|Serious Sam|gamespy1|port=25600,port_query_offset=1
|
||||||
ss2|Serious Sam 2|gamespy2|port=25600
|
ss2|Serious Sam 2|gamespy2|port=25600
|
||||||
|
@ -234,7 +236,7 @@ stalker|S.T.A.L.K.E.R.|gamespy3|port=5445,port_query_offset=2
|
||||||
stbc|Star Trek: Bridge Commander|gamespy1|port_query=22101
|
stbc|Star Trek: Bridge Commander|gamespy1|port_query=22101
|
||||||
stvef|Star Trek: Voyager - Elite Force|quake3|port_query=27960
|
stvef|Star Trek: Voyager - Elite Force|quake3|port_query=27960
|
||||||
stvef2|Star Trek: Voyager - Elite Force 2|quake3|port_query=29253
|
stvef2|Star Trek: Voyager - Elite Force 2|quake3|port_query=29253
|
||||||
squad|Squad|squad|port=7787,port_query=27165
|
squad|Squad|valve|port=7787,port_query=27165
|
||||||
swbf|Star Wars: Battlefront|gamespy2|port_query=3658
|
swbf|Star Wars: Battlefront|gamespy2|port_query=3658
|
||||||
swbf2|Star Wars: Battlefront 2|gamespy2|port_query=3658
|
swbf2|Star Wars: Battlefront 2|gamespy2|port_query=3658
|
||||||
swjk|Star Wars Jedi Knight: Jedi Academy (2003)|quake3|port_query=29070
|
swjk|Star Wars Jedi Knight: Jedi Academy (2003)|quake3|port_query=29070
|
||||||
|
@ -279,6 +281,7 @@ ut3|Unreal Tournament 3|ut3|port=7777,port_query_offset=-1277
|
||||||
|
|
||||||
urbanterror|Urban Terror|quake3|port_query=27960
|
urbanterror|Urban Terror|quake3|port_query=27960
|
||||||
v8supercar|V8 Supercar Challenge|gamespy1|port_query=16700
|
v8supercar|V8 Supercar Challenge|gamespy1|port_query=16700
|
||||||
|
valheim|Valheim (2021)|valve|port=2456,port_query_offset=1|doc_notes=valheim
|
||||||
vcmp|Vice City Multiplayer|vcmp|port=8192
|
vcmp|Vice City Multiplayer|vcmp|port=8192
|
||||||
ventrilo|Ventrilo|ventrilo|port=3784
|
ventrilo|Ventrilo|ventrilo|port=3784
|
||||||
vietcong|Vietcong|gamespy1|port=5425,port_query=15425
|
vietcong|Vietcong|gamespy1|port=5425,port_query=15425
|
||||||
|
|
|
@ -24,7 +24,7 @@ class GameResolver {
|
||||||
|
|
||||||
printReadme() {
|
printReadme() {
|
||||||
let out = '';
|
let out = '';
|
||||||
out += '| GameDig Type ID | Name | Notes\n';
|
out += '| GameDig Type ID | Name | See Also\n';
|
||||||
out += '|---|---|---\n';
|
out += '|---|---|---\n';
|
||||||
|
|
||||||
const sorted = this.games
|
const sorted = this.games
|
||||||
|
@ -36,8 +36,16 @@ class GameResolver {
|
||||||
let keysOut = game.keys.map(key => '`'+key+'`').join('<br>');
|
let keysOut = game.keys.map(key => '`'+key+'`').join('<br>');
|
||||||
out += "| " + keysOut.padEnd(10, " ") + " "
|
out += "| " + keysOut.padEnd(10, " ") + " "
|
||||||
+ "| " + game.pretty;
|
+ "| " + game.pretty;
|
||||||
if(game.extra.doc_notes)
|
let notes = [];
|
||||||
out += " | [Notes](#"+game.extra.doc_notes+")";
|
if(game.extra.doc_notes) {
|
||||||
|
notes.push("[Notes](#" + game.extra.doc_notes + ")");
|
||||||
|
}
|
||||||
|
if(game.options.protocol === 'valve') {
|
||||||
|
notes.push('[Valve Protocol](#valve)');
|
||||||
|
}
|
||||||
|
if(notes.length) {
|
||||||
|
out += " | " + notes.join(', ');
|
||||||
|
}
|
||||||
out += "\n";
|
out += "\n";
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
|
|
|
@ -1,25 +1,29 @@
|
||||||
const dgram = require('dgram'),
|
const dgram = require('dgram');
|
||||||
HexUtil = require('./HexUtil'),
|
const HexUtil = require('./HexUtil');
|
||||||
Logger = require('./Logger');
|
const Logger = require('./Logger');
|
||||||
|
const util = require('util');
|
||||||
|
|
||||||
class GlobalUdpSocket {
|
class GlobalUdpSocket {
|
||||||
constructor() {
|
constructor({port}) {
|
||||||
this.socket = null;
|
this.socket = null;
|
||||||
this.callbacks = new Set();
|
this.callbacks = new Set();
|
||||||
this.debuggingCallbacks = new Set();
|
this.debuggingCallbacks = new Set();
|
||||||
this.logger = new Logger();
|
this.logger = new Logger();
|
||||||
|
this.port = port;
|
||||||
}
|
}
|
||||||
|
|
||||||
_getSocket() {
|
async _getSocket() {
|
||||||
if (!this.socket) {
|
if (!this.socket) {
|
||||||
const udpSocket = this.socket = dgram.createSocket('udp4');
|
const udpSocket = dgram.createSocket({
|
||||||
|
type: 'udp4',
|
||||||
|
reuseAddr: true
|
||||||
|
});
|
||||||
udpSocket.unref();
|
udpSocket.unref();
|
||||||
udpSocket.bind();
|
|
||||||
udpSocket.on('message', (buffer, rinfo) => {
|
udpSocket.on('message', (buffer, rinfo) => {
|
||||||
const fromAddress = rinfo.address;
|
const fromAddress = rinfo.address;
|
||||||
const fromPort = rinfo.port;
|
const fromPort = rinfo.port;
|
||||||
this.logger.debug(log => {
|
this.logger.debug(log => {
|
||||||
log(fromAddress + ':' + fromPort + " <--UDP");
|
log(fromAddress + ':' + fromPort + " <--UDP(" + this.port + ")");
|
||||||
log(HexUtil.debugDump(buffer));
|
log(HexUtil.debugDump(buffer));
|
||||||
});
|
});
|
||||||
for (const cb of this.callbacks) {
|
for (const cb of this.callbacks) {
|
||||||
|
@ -29,12 +33,22 @@ class GlobalUdpSocket {
|
||||||
udpSocket.on('error', e => {
|
udpSocket.on('error', e => {
|
||||||
this.logger.debug("UDP ERROR:", e);
|
this.logger.debug("UDP ERROR:", e);
|
||||||
});
|
});
|
||||||
|
await util.promisify(udpSocket.bind).bind(udpSocket)(this.port);
|
||||||
|
this.port = udpSocket.address().port;
|
||||||
|
this.socket = udpSocket;
|
||||||
}
|
}
|
||||||
return this.socket;
|
return this.socket;
|
||||||
}
|
}
|
||||||
|
|
||||||
send(buffer, address, port) {
|
async send(buffer, address, port, debug) {
|
||||||
this._getSocket().send(buffer,0,buffer.length,port,address);
|
const socket = await this._getSocket();
|
||||||
|
if (debug) {
|
||||||
|
this.logger._print(log => {
|
||||||
|
log(address + ':' + port + " UDP(" + this.port + ")-->");
|
||||||
|
log(HexUtil.debugDump(buffer));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await util.promisify(socket.send).bind(socket)(buffer,0,buffer.length,port,address);
|
||||||
}
|
}
|
||||||
|
|
||||||
addCallback(callback, debug) {
|
addCallback(callback, debug) {
|
||||||
|
|
|
@ -9,8 +9,10 @@ const defaultOptions = {
|
||||||
};
|
};
|
||||||
|
|
||||||
class QueryRunner {
|
class QueryRunner {
|
||||||
constructor() {
|
constructor(runnerOpts = {}) {
|
||||||
this.udpSocket = new GlobalUdpSocket();
|
this.udpSocket = new GlobalUdpSocket({
|
||||||
|
port: runnerOpts.listenUdpPort
|
||||||
|
});
|
||||||
this.gameResolver = new GameResolver();
|
this.gameResolver = new GameResolver();
|
||||||
this.protocolResolver = new ProtocolResolver();
|
this.protocolResolver = new ProtocolResolver();
|
||||||
}
|
}
|
||||||
|
@ -30,7 +32,7 @@ class QueryRunner {
|
||||||
const attempts = [];
|
const attempts = [];
|
||||||
|
|
||||||
if (userOptions.port) {
|
if (userOptions.port) {
|
||||||
if (gameQueryPortOffset) {
|
if (gameQueryPortOffset && !userOptions.givenPortOnly) {
|
||||||
attempts.push({
|
attempts.push({
|
||||||
...defaultOptions,
|
...defaultOptions,
|
||||||
...gameOptions,
|
...gameOptions,
|
||||||
|
@ -38,7 +40,7 @@ class QueryRunner {
|
||||||
port: userOptions.port + gameQueryPortOffset
|
port: userOptions.port + gameQueryPortOffset
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (userOptions.port === gameOptions.port && gameQueryPort) {
|
if (userOptions.port === gameOptions.port && gameQueryPort && !userOptions.givenPortOnly) {
|
||||||
attempts.push({
|
attempts.push({
|
||||||
...defaultOptions,
|
...defaultOptions,
|
||||||
...gameOptions,
|
...gameOptions,
|
||||||
|
@ -66,7 +68,12 @@ class QueryRunner {
|
||||||
port: gameOptions.port + (gameQueryPortOffset || 0)
|
port: gameOptions.port + (gameQueryPortOffset || 0)
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
throw new Error("Could not determine port to query. Did you provide a port or gameid?");
|
// Hopefully the request doesn't need a port. If it does, it'll fail when making the request.
|
||||||
|
attempts.push({
|
||||||
|
...defaultOptions,
|
||||||
|
...gameOptions,
|
||||||
|
...userOptions
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const numRetries = userOptions.maxAttempts || gameOptions.maxAttempts || defaultOptions.maxAttempts;
|
const numRetries = userOptions.maxAttempts || gameOptions.maxAttempts || defaultOptions.maxAttempts;
|
||||||
|
|
44
lib/Results.js
Normal file
44
lib/Results.js
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
class Player {
|
||||||
|
name = '';
|
||||||
|
raw = {};
|
||||||
|
|
||||||
|
constructor(data) {
|
||||||
|
if (typeof data === 'string') {
|
||||||
|
this.name = data;
|
||||||
|
} else {
|
||||||
|
const {name, ...raw} = data;
|
||||||
|
if (name) this.name = name;
|
||||||
|
if (raw) this.raw = raw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Players extends Array {
|
||||||
|
setNum(num) {
|
||||||
|
// If the server specified some ridiculous number of players (billions), we don't want to
|
||||||
|
// run out of ram allocating these objects.
|
||||||
|
num = Math.min(num, 10000);
|
||||||
|
|
||||||
|
while(this.length < num) {
|
||||||
|
this.push({});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
push(data) {
|
||||||
|
super.push(new Player(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Results {
|
||||||
|
name = '';
|
||||||
|
map = '';
|
||||||
|
password = false;
|
||||||
|
|
||||||
|
raw = {};
|
||||||
|
|
||||||
|
maxplayers = 0;
|
||||||
|
players = new Players();
|
||||||
|
bots = new Players();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Results;
|
|
@ -3,8 +3,8 @@ const QueryRunner = require('./QueryRunner');
|
||||||
let singleton = null;
|
let singleton = null;
|
||||||
|
|
||||||
class Gamedig {
|
class Gamedig {
|
||||||
constructor() {
|
constructor(runnerOpts) {
|
||||||
this.queryRunner = new QueryRunner();
|
this.queryRunner = new QueryRunner(runnerOpts);
|
||||||
}
|
}
|
||||||
|
|
||||||
async query(userOptions) {
|
async query(userOptions) {
|
||||||
|
|
|
@ -115,12 +115,12 @@ class Reader {
|
||||||
if(bytes === 1) r = this.buffer.readUInt8(this.i);
|
if(bytes === 1) r = this.buffer.readUInt8(this.i);
|
||||||
else if(bytes === 2) r = this.buffer.readUInt16BE(this.i);
|
else if(bytes === 2) r = this.buffer.readUInt16BE(this.i);
|
||||||
else if(bytes === 4) r = this.buffer.readUInt32BE(this.i);
|
else if(bytes === 4) r = this.buffer.readUInt32BE(this.i);
|
||||||
else if(bytes === 8) r = readUInt64BE(this.buffer,this.i).toString();
|
else if(bytes === 8) r = readUInt64BE(this.buffer,this.i);
|
||||||
} else {
|
} else {
|
||||||
if(bytes === 1) r = this.buffer.readUInt8(this.i);
|
if(bytes === 1) r = this.buffer.readUInt8(this.i);
|
||||||
else if(bytes === 2) r = this.buffer.readUInt16LE(this.i);
|
else if(bytes === 2) r = this.buffer.readUInt16LE(this.i);
|
||||||
else if(bytes === 4) r = this.buffer.readUInt32LE(this.i);
|
else if(bytes === 4) r = this.buffer.readUInt32LE(this.i);
|
||||||
else if(bytes === 8) r = readUInt64LE(this.buffer,this.i).toString();
|
else if(bytes === 8) r = readUInt64LE(this.buffer,this.i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.i += bytes;
|
this.i += bytes;
|
||||||
|
|
552
package-lock.json
generated
552
package-lock.json
generated
|
@ -1,32 +1,66 @@
|
||||||
{
|
{
|
||||||
"name": "gamedig",
|
"name": "gamedig",
|
||||||
"version": "2.0.14",
|
"version": "3.0.1",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@sindresorhus/is": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-n4J+zu52VdY43kdi/XdI9DzuMr1Mur8zFL5ZRG2opCans9aiFwkPxHYFEb5Xgy7n1Z4K6WfI4FpqUqsh3E8BPQ=="
|
||||||
|
},
|
||||||
|
"@szmarczak/http-timer": {
|
||||||
|
"version": "4.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz",
|
||||||
|
"integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==",
|
||||||
|
"requires": {
|
||||||
|
"defer-to-connect": "^2.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@types/cacheable-request": {
|
||||||
|
"version": "6.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz",
|
||||||
|
"integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==",
|
||||||
|
"requires": {
|
||||||
|
"@types/http-cache-semantics": "*",
|
||||||
|
"@types/keyv": "*",
|
||||||
|
"@types/node": "*",
|
||||||
|
"@types/responselike": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/cheerio": {
|
"@types/cheerio": {
|
||||||
"version": "0.22.13",
|
"version": "0.22.21",
|
||||||
"resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/cheerio/-/cheerio-0.22.21.tgz",
|
||||||
"integrity": "sha512-OZd7dCUOUkiTorf97vJKwZnSja/DmHfuBAroe1kREZZTCf/tlFecwHhsOos3uVHxeKGZDwzolIrCUApClkdLuA==",
|
"integrity": "sha512-aGI3DfswwqgKPiEOTaiHV2ZPC9KEhprpgEbJnv0fZl3SGX0cGgEva1126dGrMC6AJM6v/aihlUgJn9M5DbDZ/Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@types/node": {
|
"@types/http-cache-semantics": {
|
||||||
"version": "8.10.54",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.54.tgz",
|
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz",
|
||||||
"integrity": "sha512-kaYyLYf6ICn6/isAyD4K1MyWWd5Q3JgH6bnMN089LUx88+s4W8GvK9Q6JMBVu5vsFFp7pMdSxdKmlBXwH/VFRg=="
|
"integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A=="
|
||||||
},
|
},
|
||||||
"ajv": {
|
"@types/keyv": {
|
||||||
"version": "6.6.2",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz",
|
||||||
"integrity": "sha512-FBHEW6Jf5TB9MGBgUUA9XHkTbjXYfAUjY43ACMfmdMRHniyoMHjHjzD50OK8LGDWQwp4rWEsIq5kEqq7rvIM1g==",
|
"integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"fast-deep-equal": "^2.0.1",
|
"@types/node": "*"
|
||||||
"fast-json-stable-stringify": "^2.0.0",
|
}
|
||||||
"json-schema-traverse": "^0.4.1",
|
},
|
||||||
"uri-js": "^4.2.2"
|
"@types/node": {
|
||||||
|
"version": "12.20.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.20.7.tgz",
|
||||||
|
"integrity": "sha512-gWL8VUkg8VRaCAUgG9WmhefMqHmMblxe2rVpMF86nZY/+ZysU+BkAp+3cz03AixWDSSz0ks5WX59yAhv/cDwFA=="
|
||||||
|
},
|
||||||
|
"@types/responselike": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==",
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"amdefine": {
|
"amdefine": {
|
||||||
|
@ -39,34 +73,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
|
||||||
"integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8="
|
"integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8="
|
||||||
},
|
},
|
||||||
"asn1": {
|
|
||||||
"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",
|
|
||||||
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
|
|
||||||
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
|
|
||||||
},
|
|
||||||
"asynckit": {
|
|
||||||
"version": "0.4.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
|
||||||
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
|
|
||||||
},
|
|
||||||
"aws-sign2": {
|
|
||||||
"version": "0.7.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
|
|
||||||
"integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
|
|
||||||
},
|
|
||||||
"aws4": {
|
|
||||||
"version": "1.8.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
|
|
||||||
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
|
|
||||||
},
|
|
||||||
"barse": {
|
"barse": {
|
||||||
"version": "0.4.3",
|
"version": "0.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/barse/-/barse-0.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/barse/-/barse-0.4.3.tgz",
|
||||||
|
@ -75,28 +81,29 @@
|
||||||
"readable-stream": "~1.0.2"
|
"readable-stream": "~1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"bcrypt-pbkdf": {
|
|
||||||
"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.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"bluebird": {
|
|
||||||
"version": "3.7.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.1.tgz",
|
|
||||||
"integrity": "sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg=="
|
|
||||||
},
|
|
||||||
"boolbase": {
|
"boolbase": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||||
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
|
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
|
||||||
},
|
},
|
||||||
"caseless": {
|
"cacheable-lookup": {
|
||||||
"version": "0.12.0",
|
"version": "5.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
|
"resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.3.tgz",
|
||||||
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
|
"integrity": "sha512-W+JBqF9SWe18A72XFzN/V/CULFzPm7sBXzzR6ekkE+3tLG72wFZrBiBZhrZuDoYexop4PHJVdFAKb/Nj9+tm9w=="
|
||||||
|
},
|
||||||
|
"cacheable-request": {
|
||||||
|
"version": "7.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.1.tgz",
|
||||||
|
"integrity": "sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==",
|
||||||
|
"requires": {
|
||||||
|
"clone-response": "^1.0.2",
|
||||||
|
"get-stream": "^5.1.0",
|
||||||
|
"http-cache-semantics": "^4.0.0",
|
||||||
|
"keyv": "^4.0.0",
|
||||||
|
"lowercase-keys": "^2.0.0",
|
||||||
|
"normalize-url": "^4.1.0",
|
||||||
|
"responselike": "^2.0.0"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"cheerio": {
|
"cheerio": {
|
||||||
"version": "1.0.0-rc.3",
|
"version": "1.0.0-rc.3",
|
||||||
|
@ -111,12 +118,12 @@
|
||||||
"parse5": "^3.0.1"
|
"parse5": "^3.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"combined-stream": {
|
"clone-response": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz",
|
||||||
"integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==",
|
"integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=",
|
||||||
"requires": {
|
"requires": {
|
||||||
"delayed-stream": "~1.0.0"
|
"mimic-response": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"commander": {
|
"commander": {
|
||||||
|
@ -157,18 +164,25 @@
|
||||||
"resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz",
|
||||||
"integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg=="
|
"integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg=="
|
||||||
},
|
},
|
||||||
"dashdash": {
|
"decompress-response": {
|
||||||
"version": "1.14.1",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
|
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
|
||||||
"integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
|
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"assert-plus": "^1.0.0"
|
"mimic-response": "^3.1.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"mimic-response": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"delayed-stream": {
|
"defer-to-connect": {
|
||||||
"version": "1.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.0.tgz",
|
||||||
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
|
"integrity": "sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg=="
|
||||||
},
|
},
|
||||||
"dom-serializer": {
|
"dom-serializer": {
|
||||||
"version": "0.1.1",
|
"version": "0.1.1",
|
||||||
|
@ -201,13 +215,12 @@
|
||||||
"domelementtype": "1"
|
"domelementtype": "1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ecc-jsbn": {
|
"end-of-stream": {
|
||||||
"version": "0.1.2",
|
"version": "1.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
|
||||||
"integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
|
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"jsbn": "~0.1.0",
|
"once": "^1.4.0"
|
||||||
"safer-buffer": "^2.1.0"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"entities": {
|
"entities": {
|
||||||
|
@ -220,41 +233,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/event-to-promise/-/event-to-promise-0.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/event-to-promise/-/event-to-promise-0.7.0.tgz",
|
||||||
"integrity": "sha1-ywffzUGNoiIdkPd+q3E7wjXiCQ8="
|
"integrity": "sha1-ywffzUGNoiIdkPd+q3E7wjXiCQ8="
|
||||||
},
|
},
|
||||||
"extend": {
|
|
||||||
"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",
|
|
||||||
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
|
|
||||||
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
|
|
||||||
},
|
|
||||||
"fast-deep-equal": {
|
|
||||||
"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",
|
|
||||||
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
|
|
||||||
"integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
|
|
||||||
},
|
|
||||||
"forever-agent": {
|
|
||||||
"version": "0.6.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
|
|
||||||
"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
|
|
||||||
},
|
|
||||||
"form-data": {
|
|
||||||
"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.12"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"gbxremote": {
|
"gbxremote": {
|
||||||
"version": "0.2.1",
|
"version": "0.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/gbxremote/-/gbxremote-0.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/gbxremote/-/gbxremote-0.2.1.tgz",
|
||||||
|
@ -267,12 +245,30 @@
|
||||||
"xmlrpc": "^1.3.1"
|
"xmlrpc": "^1.3.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"getpass": {
|
"get-stream": {
|
||||||
"version": "0.1.7",
|
"version": "5.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz",
|
||||||
"integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
|
"integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"assert-plus": "^1.0.0"
|
"pump": "^3.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"got": {
|
||||||
|
"version": "11.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/got/-/got-11.5.1.tgz",
|
||||||
|
"integrity": "sha512-reQEZcEBMTGnujmQ+Wm97mJs/OK6INtO6HmLI+xt3+9CvnRwWjXutUvb2mqr+Ao4Lu05Rx6+udx9sOQAmExMxA==",
|
||||||
|
"requires": {
|
||||||
|
"@sindresorhus/is": "^3.0.0",
|
||||||
|
"@szmarczak/http-timer": "^4.0.5",
|
||||||
|
"@types/cacheable-request": "^6.0.1",
|
||||||
|
"@types/responselike": "^1.0.0",
|
||||||
|
"cacheable-lookup": "^5.0.3",
|
||||||
|
"cacheable-request": "^7.0.1",
|
||||||
|
"decompress-response": "^6.0.0",
|
||||||
|
"http2-wrapper": "^1.0.0-beta.5.0",
|
||||||
|
"lowercase-keys": "^2.0.0",
|
||||||
|
"p-cancelable": "^2.0.0",
|
||||||
|
"responselike": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"graceful-readlink": {
|
"graceful-readlink": {
|
||||||
|
@ -280,20 +276,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz",
|
||||||
"integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU="
|
"integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU="
|
||||||
},
|
},
|
||||||
"har-schema": {
|
|
||||||
"version": "2.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
|
|
||||||
"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
|
|
||||||
},
|
|
||||||
"har-validator": {
|
|
||||||
"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": "^6.5.5",
|
|
||||||
"har-schema": "^2.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"htmlparser2": {
|
"htmlparser2": {
|
||||||
"version": "3.10.1",
|
"version": "3.10.1",
|
||||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
|
||||||
|
@ -332,22 +314,26 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"http-signature": {
|
"http-cache-semantics": {
|
||||||
"version": "1.2.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
|
||||||
"integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
|
"integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ=="
|
||||||
|
},
|
||||||
|
"http2-wrapper": {
|
||||||
|
"version": "1.0.0-beta.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.0-beta.5.2.tgz",
|
||||||
|
"integrity": "sha512-xYz9goEyBnC8XwXDTuC/MZ6t+MrKVQZOk4s7+PaDkwIsQd8IwqvM+0M6bA/2lvG8GHXcPdf+MejTUeO2LCPCeQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"assert-plus": "^1.0.0",
|
"quick-lru": "^5.1.1",
|
||||||
"jsprim": "^1.2.2",
|
"resolve-alpn": "^1.0.0"
|
||||||
"sshpk": "^1.7.0"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"iconv-lite": {
|
"iconv-lite": {
|
||||||
"version": "0.5.0",
|
"version": "0.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.2.tgz",
|
||||||
"integrity": "sha512-NnEhI9hIEKHOzJ4f697DMz9IQEXr/MMJ5w64vN2/4Ai+wRnvV7SBrL0KLoRlwaKVghOc7LQ5YkPLuX146b6Ydw==",
|
"integrity": "sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"safer-buffer": ">= 2.1.2 < 3"
|
"safer-buffer": ">= 2.1.2 < 3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"inherits": {
|
"inherits": {
|
||||||
|
@ -355,84 +341,58 @@
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
|
||||||
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
|
||||||
},
|
},
|
||||||
"is-typedarray": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
|
|
||||||
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
|
|
||||||
},
|
|
||||||
"isarray": {
|
"isarray": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
|
||||||
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
|
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
|
||||||
},
|
},
|
||||||
"isstream": {
|
"json-buffer": {
|
||||||
"version": "0.1.2",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
|
||||||
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
|
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="
|
||||||
},
|
},
|
||||||
"jsbn": {
|
"keyv": {
|
||||||
"version": "0.1.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.1.tgz",
|
||||||
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
|
"integrity": "sha512-xz6Jv6oNkbhrFCvCP7HQa8AaII8y8LRpoSm661NOKLr4uHuBwhX4epXrPQgF3+xdJnN4Esm5X0xwY4bOlALOtw==",
|
||||||
},
|
|
||||||
"json-schema": {
|
|
||||||
"version": "0.2.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
|
|
||||||
"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
|
|
||||||
},
|
|
||||||
"json-schema-traverse": {
|
|
||||||
"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",
|
|
||||||
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
|
|
||||||
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
|
|
||||||
},
|
|
||||||
"jsprim": {
|
|
||||||
"version": "1.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
|
|
||||||
"integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"assert-plus": "1.0.0",
|
"json-buffer": "3.0.1"
|
||||||
"extsprintf": "1.3.0",
|
|
||||||
"json-schema": "0.2.3",
|
|
||||||
"verror": "1.10.0"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"lodash": {
|
"lodash": {
|
||||||
"version": "4.17.15",
|
"version": "4.17.19",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
|
||||||
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
|
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ=="
|
||||||
},
|
},
|
||||||
"long": {
|
"long": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
|
||||||
"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
|
"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
|
||||||
},
|
},
|
||||||
"mime-db": {
|
"lowercase-keys": {
|
||||||
"version": "1.37.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz",
|
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
|
||||||
"integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg=="
|
"integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="
|
||||||
},
|
},
|
||||||
"mime-types": {
|
"mimic-response": {
|
||||||
"version": "2.1.21",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz",
|
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
|
||||||
"integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==",
|
"integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="
|
||||||
"requires": {
|
|
||||||
"mime-db": "~1.37.0"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"minimist": {
|
"minimist": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||||
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ="
|
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
|
||||||
},
|
},
|
||||||
"moment": {
|
"moment": {
|
||||||
"version": "2.24.0",
|
"version": "2.27.0",
|
||||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
|
"resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz",
|
||||||
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
|
"integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ=="
|
||||||
|
},
|
||||||
|
"normalize-url": {
|
||||||
|
"version": "4.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz",
|
||||||
|
"integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ=="
|
||||||
},
|
},
|
||||||
"nth-check": {
|
"nth-check": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
|
@ -442,10 +402,18 @@
|
||||||
"boolbase": "~1.0.0"
|
"boolbase": "~1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"oauth-sign": {
|
"once": {
|
||||||
"version": "0.9.0",
|
"version": "1.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||||
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
|
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||||
|
"requires": {
|
||||||
|
"wrappy": "1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"p-cancelable": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg=="
|
||||||
},
|
},
|
||||||
"parse5": {
|
"parse5": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
|
@ -455,30 +423,29 @@
|
||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"performance-now": {
|
|
||||||
"version": "2.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
|
|
||||||
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
|
|
||||||
},
|
|
||||||
"process-nextick-args": {
|
"process-nextick-args": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
|
||||||
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
|
||||||
},
|
},
|
||||||
"psl": {
|
"pump": {
|
||||||
"version": "1.1.31",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz",
|
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
||||||
"integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw=="
|
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
|
||||||
|
"requires": {
|
||||||
|
"end-of-stream": "^1.1.0",
|
||||||
|
"once": "^1.3.1"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"punycode": {
|
"punycode": {
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
||||||
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
|
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
|
||||||
},
|
},
|
||||||
"qs": {
|
"quick-lru": {
|
||||||
"version": "6.5.2",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
|
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
|
||||||
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
|
"integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="
|
||||||
},
|
},
|
||||||
"readable-stream": {
|
"readable-stream": {
|
||||||
"version": "1.0.34",
|
"version": "1.0.34",
|
||||||
|
@ -491,68 +458,17 @@
|
||||||
"string_decoder": "~0.10.x"
|
"string_decoder": "~0.10.x"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"request": {
|
"resolve-alpn": {
|
||||||
"version": "2.88.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
|
"resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.0.0.tgz",
|
||||||
"integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
|
"integrity": "sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA=="
|
||||||
"requires": {
|
|
||||||
"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": {
|
"responselike": {
|
||||||
"tough-cookie": {
|
"version": "2.0.0",
|
||||||
"version": "2.4.3",
|
"resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz",
|
||||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
|
"integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==",
|
||||||
"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"psl": "^1.1.24",
|
"lowercase-keys": "^2.0.0"
|
||||||
"punycode": "^1.4.1"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"punycode": {
|
|
||||||
"version": "1.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
|
|
||||||
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"request-promise": {
|
|
||||||
"version": "4.2.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.4.tgz",
|
|
||||||
"integrity": "sha512-8wgMrvE546PzbR5WbYxUQogUnUDfM0S7QIFZMID+J73vdFARkFy+HElj4T+MWYhpXwlLp0EQ8Zoj8xUA0he4Vg==",
|
|
||||||
"requires": {
|
|
||||||
"bluebird": "^3.5.0",
|
|
||||||
"request-promise-core": "1.1.2",
|
|
||||||
"stealthy-require": "^1.1.1",
|
|
||||||
"tough-cookie": "^2.3.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"request-promise-core": {
|
|
||||||
"version": "1.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz",
|
|
||||||
"integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==",
|
|
||||||
"requires": {
|
|
||||||
"lodash": "^4.17.11"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"safe-buffer": {
|
"safe-buffer": {
|
||||||
|
@ -570,27 +486,6 @@
|
||||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
||||||
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
|
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
|
||||||
},
|
},
|
||||||
"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": {
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"stealthy-require": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
|
|
||||||
"integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks="
|
|
||||||
},
|
|
||||||
"string-to-stream": {
|
"string-to-stream": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/string-to-stream/-/string-to-stream-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/string-to-stream/-/string-to-stream-1.1.1.tgz",
|
||||||
|
@ -634,67 +529,20 @@
|
||||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
|
||||||
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
|
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
|
||||||
},
|
},
|
||||||
"tough-cookie": {
|
|
||||||
"version": "2.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
|
|
||||||
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
|
|
||||||
"requires": {
|
|
||||||
"psl": "^1.1.28",
|
|
||||||
"punycode": "^2.1.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tunnel-agent": {
|
|
||||||
"version": "0.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
|
|
||||||
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
|
|
||||||
"requires": {
|
|
||||||
"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="
|
|
||||||
},
|
|
||||||
"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": {
|
"util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
"integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
|
||||||
},
|
},
|
||||||
"uuid": {
|
|
||||||
"version": "3.3.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
|
|
||||||
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
|
|
||||||
},
|
|
||||||
"varint": {
|
"varint": {
|
||||||
"version": "5.0.0",
|
"version": "5.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/varint/-/varint-5.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/varint/-/varint-5.0.0.tgz",
|
||||||
"integrity": "sha1-2Ca4n3SQcy+rwMDtaT7Uddyynr8="
|
"integrity": "sha1-2Ca4n3SQcy+rwMDtaT7Uddyynr8="
|
||||||
},
|
},
|
||||||
"verror": {
|
"wrappy": {
|
||||||
"version": "1.10.0",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||||
"integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
|
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||||
"requires": {
|
|
||||||
"assert-plus": "^1.0.0",
|
|
||||||
"core-util-is": "1.0.2",
|
|
||||||
"extsprintf": "^1.2.0"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"xmlbuilder": {
|
"xmlbuilder": {
|
||||||
"version": "8.2.2",
|
"version": "8.2.2",
|
||||||
|
|
38
package.json
38
package.json
|
@ -7,33 +7,45 @@
|
||||||
"game",
|
"game",
|
||||||
"utility",
|
"utility",
|
||||||
"util",
|
"util",
|
||||||
"server"
|
"server",
|
||||||
|
"gameserver",
|
||||||
|
"node",
|
||||||
|
"nodejs",
|
||||||
|
"game-server-query",
|
||||||
|
"game server query",
|
||||||
|
"server query",
|
||||||
|
"game server",
|
||||||
|
"gameserverquery",
|
||||||
|
"serverquery",
|
||||||
|
"terraria",
|
||||||
|
"counter strike",
|
||||||
|
"csgo",
|
||||||
|
"minecraft"
|
||||||
],
|
],
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"author": "Michael Morrison",
|
"author": "GameDig Contributors",
|
||||||
"version": "2.0.20",
|
"version": "3.0.8",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/sonicsnes/node-gamedig.git"
|
"url": "https://github.com/gamedig/node-gamedig.git"
|
||||||
},
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/sonicsnes/node-gamedig/issues"
|
"url": "https://github.com/gamedig/node-gamedig/issues"
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.0.0"
|
"node": ">=12.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cheerio": "^1.0.0-rc.3",
|
"cheerio": "^1.0.0-rc.3",
|
||||||
"compressjs": "^1.0.2",
|
"compressjs": "^1.0.2",
|
||||||
"gbxremote": "^0.2.1",
|
"gbxremote": "^0.2.1",
|
||||||
"iconv-lite": "^0.5.0",
|
"got": "^11.5.1",
|
||||||
|
"iconv-lite": "^0.6.2",
|
||||||
"long": "^4.0.0",
|
"long": "^4.0.0",
|
||||||
"minimist": "^1.2.0",
|
"minimist": "^1.2.5",
|
||||||
"moment": "^2.24.0",
|
"moment": "^2.27.0",
|
||||||
"punycode": "^2.1.1",
|
"punycode": "^2.1.1",
|
||||||
"request": "^2.88.0",
|
|
||||||
"request-promise": "^4.2.4",
|
|
||||||
"varint": "^5.0.0"
|
"varint": "^5.0.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
|
@ -48,7 +60,7 @@
|
||||||
"README.md"
|
"README.md"
|
||||||
],
|
],
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/cheerio": "^0.22.13",
|
"@types/cheerio": "^0.22.21",
|
||||||
"@types/node": "^8.10.54"
|
"@types/node": "^12.20.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,12 +3,12 @@ const Core = require('./core');
|
||||||
class AssettoCorsa extends Core {
|
class AssettoCorsa extends Core {
|
||||||
async run(state) {
|
async run(state) {
|
||||||
const serverInfo = await this.request({
|
const serverInfo = await this.request({
|
||||||
json: true,
|
url: `http://${this.options.address}:${this.options.port}/INFO`,
|
||||||
uri: `http://${this.options.address}:${this.options.port}/INFO`
|
responseType: 'json'
|
||||||
});
|
});
|
||||||
const carInfo = await this.request({
|
const carInfo = await this.request({
|
||||||
json: true,
|
url: `http://${this.options.address}:${this.options.port}/JSON|${parseInt(Math.random() * 999999999999999, 10)}`,
|
||||||
uri: `http://${this.options.address}:${this.options.port}/JSON|${parseInt(Math.random() * 999999999999999, 10)}`
|
responseType: 'json'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!serverInfo || !carInfo || !carInfo.Cars) {
|
if (!serverInfo || !carInfo || !carInfo.Cars) {
|
||||||
|
@ -23,18 +23,17 @@ class AssettoCorsa extends Core {
|
||||||
state.raw.carInfo = carInfo.Cars;
|
state.raw.carInfo = carInfo.Cars;
|
||||||
state.raw.serverInfo = serverInfo;
|
state.raw.serverInfo = serverInfo;
|
||||||
|
|
||||||
state.players = carInfo.Cars.reduce((r, e) => {
|
for (const car of carInfo.Cars) {
|
||||||
if (e.IsConnected) {
|
if (car.IsConnected) {
|
||||||
r.push({
|
state.players.push({
|
||||||
name: e.DriverName,
|
name: car.DriverName,
|
||||||
car: e.Model,
|
car: car.Model,
|
||||||
skin: e.Skin,
|
skin: car.Skin,
|
||||||
nation: e.DriverNation,
|
nation: car.DriverNation,
|
||||||
team: e.DriverTeam
|
team: car.DriverTeam
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return r;
|
}
|
||||||
}, state.players);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ const Core = require('./core'),
|
||||||
class BuildAndShoot extends Core {
|
class BuildAndShoot extends Core {
|
||||||
async run(state) {
|
async run(state) {
|
||||||
const body = await this.request({
|
const body = await this.request({
|
||||||
uri: 'http://'+this.options.address+':'+this.options.port+'/',
|
url: 'http://'+this.options.address+':'+this.options.port+'/',
|
||||||
});
|
});
|
||||||
|
|
||||||
let m;
|
let m;
|
||||||
|
|
|
@ -2,10 +2,11 @@ const EventEmitter = require('events').EventEmitter,
|
||||||
net = require('net'),
|
net = require('net'),
|
||||||
Reader = require('../lib/reader'),
|
Reader = require('../lib/reader'),
|
||||||
HexUtil = require('../lib/HexUtil'),
|
HexUtil = require('../lib/HexUtil'),
|
||||||
requestAsync = require('request-promise'),
|
got = require('got'),
|
||||||
Promises = require('../lib/Promises'),
|
Promises = require('../lib/Promises'),
|
||||||
Logger = require('../lib/Logger'),
|
Logger = require('../lib/Logger'),
|
||||||
DnsResolver = require('../lib/DnsResolver');
|
DnsResolver = require('../lib/DnsResolver'),
|
||||||
|
Results = require('../lib/Results');
|
||||||
|
|
||||||
let uid = 0;
|
let uid = 0;
|
||||||
|
|
||||||
|
@ -35,15 +36,16 @@ class Core extends EventEmitter {
|
||||||
}
|
}
|
||||||
this.logger.prefix = 'Q#' + (uid++);
|
this.logger.prefix = 'Q#' + (uid++);
|
||||||
|
|
||||||
this.logger.debug("Query is running with options:", this.options);
|
this.logger.debug("Starting");
|
||||||
|
this.logger.debug("Protocol: " + this.constructor.name);
|
||||||
|
this.logger.debug("Options:", this.options);
|
||||||
|
|
||||||
let abortCall = null;
|
let abortCall = null;
|
||||||
this.abortedPromise = new Promise((resolve,reject) => {
|
this.abortedPromise = new Promise((resolve,reject) => {
|
||||||
abortCall = () => reject(new Error("Query is finished -- cancelling outstanding promises"));
|
abortCall = () => reject(new Error("Query is finished -- cancelling outstanding promises"));
|
||||||
});
|
}).catch(() => {
|
||||||
|
|
||||||
// Make sure that if this promise isn't attached to, it doesn't throw a unhandled promise rejection
|
// Make sure that if this promise isn't attached to, it doesn't throw a unhandled promise rejection
|
||||||
this.abortedPromise.catch(() => {});
|
});
|
||||||
|
|
||||||
let timeout;
|
let timeout;
|
||||||
try {
|
try {
|
||||||
|
@ -73,44 +75,13 @@ class Core extends EventEmitter {
|
||||||
if (resolved.port) options.port = resolved.port;
|
if (resolved.port) options.port = resolved.port;
|
||||||
}
|
}
|
||||||
|
|
||||||
const state = {
|
const state = new Results();
|
||||||
name: '',
|
|
||||||
map: '',
|
|
||||||
password: false,
|
|
||||||
|
|
||||||
raw: {},
|
|
||||||
|
|
||||||
maxplayers: 0,
|
|
||||||
players: [],
|
|
||||||
bots: []
|
|
||||||
};
|
|
||||||
|
|
||||||
await this.run(state);
|
await this.run(state);
|
||||||
|
|
||||||
// because lots of servers prefix with spaces to try to appear first
|
// because lots of servers prefix with spaces to try to appear first
|
||||||
state.name = (state.name || '').trim();
|
state.name = (state.name || '').trim();
|
||||||
|
|
||||||
if (typeof state.players === 'number') {
|
|
||||||
const num = state.players;
|
|
||||||
state.players = [];
|
|
||||||
state.raw.rcvNumPlayers = num;
|
|
||||||
if (num < 10000) {
|
|
||||||
for (let i = 0; i < num; i++) {
|
|
||||||
state.players.push({});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (typeof state.bots === 'number') {
|
|
||||||
const num = state.bots;
|
|
||||||
state.bots = [];
|
|
||||||
state.raw.rcvNumBots = num;
|
|
||||||
if (num < 10000) {
|
|
||||||
for (let i = 0; i < num; i++) {
|
|
||||||
state.bots.push({});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!('connect' in state)) {
|
if (!('connect' in state)) {
|
||||||
state.connect = ''
|
state.connect = ''
|
||||||
+ (state.gameHost || this.options.host || this.options.address)
|
+ (state.gameHost || this.options.host || this.options.address)
|
||||||
|
@ -129,7 +100,7 @@ class Core extends EventEmitter {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
async run(state) {}
|
async run(/** Results */ state) {}
|
||||||
|
|
||||||
/** Param can be a time in ms, or a promise (which will be timed) */
|
/** Param can be a time in ms, or a promise (which will be timed) */
|
||||||
registerRtt(param) {
|
registerRtt(param) {
|
||||||
|
@ -175,7 +146,10 @@ class Core extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
assertValidPort(port) {
|
assertValidPort(port) {
|
||||||
if (!port || port < 1 || port > 65535) {
|
if (!port) {
|
||||||
|
throw new Error("Could not determine port to query. Did you provide a port?");
|
||||||
|
}
|
||||||
|
if (port < 1 || port > 65535) {
|
||||||
throw new Error("Invalid tcp/ip port: " + port);
|
throw new Error("Invalid tcp/ip port: " + port);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -278,13 +252,9 @@ class Core extends EventEmitter {
|
||||||
this.assertValidPort(port);
|
this.assertValidPort(port);
|
||||||
|
|
||||||
if(typeof buffer === 'string') buffer = Buffer.from(buffer,'binary');
|
if(typeof buffer === 'string') buffer = Buffer.from(buffer,'binary');
|
||||||
this.debugLog(log => {
|
|
||||||
log(address+':'+port+" UDP-->");
|
|
||||||
log(HexUtil.debugDump(buffer));
|
|
||||||
});
|
|
||||||
|
|
||||||
const socket = this.udpSocket;
|
const socket = this.udpSocket;
|
||||||
socket.send(buffer, address, port);
|
await socket.send(buffer, address, port, this.options.debug);
|
||||||
|
|
||||||
if (!onPacket && !onTimeout) {
|
if (!onPacket && !onTimeout) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -342,24 +312,26 @@ class Core extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async request(params) {
|
async tcpPing() {
|
||||||
// If we haven't opened a raw tcp socket yet during this query, just open one and then immediately close it.
|
// This will give a much more accurate RTT than using the rtt of an http request.
|
||||||
// This will give us a much more accurate RTT than using the rtt of the http request.
|
|
||||||
if (!this.usedTcp) {
|
if (!this.usedTcp) {
|
||||||
await this.withTcp(() => {});
|
await this.withTcp(() => {});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async request(params) {
|
||||||
|
await this.tcpPing();
|
||||||
|
|
||||||
let requestPromise;
|
let requestPromise;
|
||||||
try {
|
try {
|
||||||
requestPromise = requestAsync({
|
requestPromise = got({
|
||||||
...params,
|
...params,
|
||||||
timeout: this.options.socketTimeout,
|
timeout: this.options.socketTimeout
|
||||||
resolveWithFullResponse: true
|
|
||||||
});
|
});
|
||||||
this.debugLog(log => {
|
this.debugLog(log => {
|
||||||
log(() => params.uri + " HTTP-->");
|
log(() => params.url + " HTTP-->");
|
||||||
requestPromise
|
requestPromise
|
||||||
.then((response) => log(params.uri + " <--HTTP " + response.statusCode))
|
.then((response) => log(params.url + " <--HTTP " + response.statusCode))
|
||||||
.catch(() => {});
|
.catch(() => {});
|
||||||
});
|
});
|
||||||
const wrappedPromise = requestPromise.then(response => {
|
const wrappedPromise = requestPromise.then(response => {
|
||||||
|
|
31
protocols/discord.js
Normal file
31
protocols/discord.js
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
const Core = require('./core');
|
||||||
|
|
||||||
|
class Discord extends Core {
|
||||||
|
async run(state) {
|
||||||
|
const guildId = this.options.guildId;
|
||||||
|
if (typeof guildId !== 'string') {
|
||||||
|
throw new Error('guildId option must be set when querying discord. Ensure the guildId is a string and not a number.'
|
||||||
|
+ " (It's too large of a number for javascript to store without losing precision)");
|
||||||
|
}
|
||||||
|
this.usedTcp = true;
|
||||||
|
const raw = await this.request({
|
||||||
|
url: 'https://discordapp.com/api/guilds/' + guildId + '/widget.json',
|
||||||
|
});
|
||||||
|
const json = JSON.parse(raw);
|
||||||
|
state.name = json.name;
|
||||||
|
if (json.instant_invite) {
|
||||||
|
state.connect = json.instant_invite;
|
||||||
|
} else {
|
||||||
|
state.connect = 'https://discordapp.com/channels/' + guildId;
|
||||||
|
}
|
||||||
|
for (const member of json.members) {
|
||||||
|
const {username: name, ...rest} = member;
|
||||||
|
state.players.push({ name, ...rest });
|
||||||
|
}
|
||||||
|
delete json.members;
|
||||||
|
state.maxplayers = 500000;
|
||||||
|
state.raw = json;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Discord;
|
|
@ -10,7 +10,6 @@ class Ffow extends Valve {
|
||||||
this.debugLog("Requesting ffow info ...");
|
this.debugLog("Requesting ffow info ...");
|
||||||
const b = await this.sendPacket(
|
const b = await this.sendPacket(
|
||||||
0x46,
|
0x46,
|
||||||
false,
|
|
||||||
'LSQ',
|
'LSQ',
|
||||||
0x49
|
0x49
|
||||||
);
|
);
|
||||||
|
|
|
@ -12,20 +12,19 @@ class FiveM extends Quake2 {
|
||||||
await super.run(state);
|
await super.run(state);
|
||||||
|
|
||||||
{
|
{
|
||||||
const raw = await this.request({
|
const json = await this.request({
|
||||||
uri: 'http://' + this.options.address + ':' + this.options.port + '/info.json'
|
url: 'http://' + this.options.address + ':' + this.options.port + '/info.json',
|
||||||
|
responseType: 'json'
|
||||||
});
|
});
|
||||||
const json = JSON.parse(raw);
|
|
||||||
state.raw.info = json;
|
state.raw.info = json;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
const raw = await this.request({
|
const json = await this.request({
|
||||||
uri: 'http://' + this.options.address + ':' + this.options.port + '/players.json'
|
url: 'http://' + this.options.address + ':' + this.options.port + '/players.json',
|
||||||
|
responseType: 'json'
|
||||||
});
|
});
|
||||||
const json = JSON.parse(raw);
|
|
||||||
state.raw.players = json;
|
state.raw.players = json;
|
||||||
state.players = [];
|
|
||||||
for (const player of json) {
|
for (const player of json) {
|
||||||
state.players.push({name: player.name, ping: player.ping});
|
state.players.push({name: player.name, ping: player.ping});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,33 @@
|
||||||
const Core = require('./core');
|
const Core = require('./core');
|
||||||
|
|
||||||
|
const stringKeys = new Set([
|
||||||
|
'website',
|
||||||
|
'gametype',
|
||||||
|
'gamemode',
|
||||||
|
'player'
|
||||||
|
]);
|
||||||
|
|
||||||
|
function normalizeEntry([key,value]) {
|
||||||
|
key = key.toLowerCase();
|
||||||
|
const split = key.split('_');
|
||||||
|
let keyType;
|
||||||
|
if (split.length === 2 && !isNaN(parseInt(split[1]))) {
|
||||||
|
keyType = split[0];
|
||||||
|
} else {
|
||||||
|
keyType = key;
|
||||||
|
}
|
||||||
|
if (!stringKeys.has(keyType) && !keyType.includes('name')) {
|
||||||
|
if (value.toLowerCase() === 'true') {
|
||||||
|
value = true;
|
||||||
|
} else if (value.toLowerCase() === 'false') {
|
||||||
|
value = false;
|
||||||
|
} else if (!isNaN(parseInt(value))) {
|
||||||
|
value = parseInt(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [key,value];
|
||||||
|
}
|
||||||
|
|
||||||
class Gamespy1 extends Core {
|
class Gamespy1 extends Core {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
@ -8,36 +36,49 @@ class Gamespy1 extends Core {
|
||||||
}
|
}
|
||||||
|
|
||||||
async run(state) {
|
async run(state) {
|
||||||
{
|
const raw = await this.sendPacket('\\status\\xserverquery');
|
||||||
const data = await this.sendPacket('info');
|
// Convert all keys to lowercase and normalize value types
|
||||||
|
const data = Object.fromEntries(Object.entries(raw).map(entry => normalizeEntry(entry)));
|
||||||
state.raw = data;
|
state.raw = data;
|
||||||
if ('hostname' in state.raw) state.name = state.raw.hostname;
|
if ('hostname' in data) state.name = data.hostname;
|
||||||
if ('mapname' in state.raw) state.map = state.raw.mapname;
|
if ('mapname' in data) state.map = data.mapname;
|
||||||
if (this.trueTest(state.raw.password)) state.password = true;
|
if (this.trueTest(data.password)) state.password = true;
|
||||||
if ('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers);
|
if ('maxplayers' in data) state.maxplayers = parseInt(data.maxplayers);
|
||||||
if ('hostport' in state.raw) state.gamePort = parseInt(state.raw.hostport);
|
if ('hostport' in data) state.gamePort = parseInt(data.hostport);
|
||||||
}
|
|
||||||
{
|
const teamOffByOne = data.gamename === 'bfield1942';
|
||||||
const data = await this.sendPacket('rules');
|
|
||||||
state.raw.rules = data;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
const data = await this.sendPacket('players');
|
|
||||||
const playersById = {};
|
const playersById = {};
|
||||||
const teamNamesById = {};
|
const teamNamesById = {};
|
||||||
for (const ident of Object.keys(data)) {
|
for (const ident of Object.keys(data)) {
|
||||||
const split = ident.split('_');
|
const split = ident.split('_');
|
||||||
let key = split[0];
|
if (split.length !== 2) continue;
|
||||||
const id = split[1];
|
let key = split[0].toLowerCase();
|
||||||
|
const id = parseInt(split[1]);
|
||||||
|
if (isNaN(id)) continue;
|
||||||
let value = data[ident];
|
let value = data[ident];
|
||||||
|
|
||||||
|
delete data[ident];
|
||||||
|
|
||||||
|
if (key !== 'team' && key.startsWith('team')) {
|
||||||
|
// Info about a team
|
||||||
if (key === 'teamname') {
|
if (key === 'teamname') {
|
||||||
teamNamesById[id] = value;
|
teamNamesById[id] = value;
|
||||||
} else {
|
} else {
|
||||||
|
// other team info which we don't track
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Info about a player
|
||||||
if (!(id in playersById)) playersById[id] = {};
|
if (!(id in playersById)) playersById[id] = {};
|
||||||
if (key === 'playername') key = 'name';
|
if (key === 'playername' || key === 'player') {
|
||||||
else if (key === 'team') value = parseInt(value);
|
key = 'name';
|
||||||
else if (key === 'score' || key === 'ping' || key === 'deaths' || key === 'kills') value = parseInt(value);
|
}
|
||||||
|
if (key === 'team' && !isNaN(parseInt(value))) {
|
||||||
|
key = 'teamId';
|
||||||
|
value = parseInt(value) + (teamOffByOne ? -1 : 0);
|
||||||
|
}
|
||||||
|
if (key !== 'name' && !isNaN(parseInt(value))) {
|
||||||
|
value = parseInt(value);
|
||||||
|
}
|
||||||
playersById[id][key] = value;
|
playersById[id][key] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,28 +86,6 @@ class Gamespy1 extends Core {
|
||||||
|
|
||||||
const players = Object.values(playersById);
|
const players = Object.values(playersById);
|
||||||
|
|
||||||
// Determine which team id might be for spectators
|
|
||||||
let specTeamId = null;
|
|
||||||
for (const player of players) {
|
|
||||||
if (!player.team) {
|
|
||||||
continue;
|
|
||||||
} else if (teamNamesById[player.team]) {
|
|
||||||
continue;
|
|
||||||
} else if (teamNamesById[player.team-1] && (specTeamId === null || specTeamId === player.team)) {
|
|
||||||
specTeamId = player.team;
|
|
||||||
} else {
|
|
||||||
specTeamId = null;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.logger.debug(log => {
|
|
||||||
if (specTeamId === null) {
|
|
||||||
log("Could not detect a team ID for spectators");
|
|
||||||
} else {
|
|
||||||
log("Detected that team ID " + specTeamId + " is probably for spectators");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const seenHashes = new Set();
|
const seenHashes = new Set();
|
||||||
for (const player of players) {
|
for (const player of players) {
|
||||||
// Some servers (bf1942) report the same player multiple times (bug?)
|
// Some servers (bf1942) report the same player multiple times (bug?)
|
||||||
|
@ -81,18 +100,18 @@ class Gamespy1 extends Core {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert player's team ID to team name if possible
|
// Convert player's team ID to team name if possible
|
||||||
if (player.team) {
|
if (player.hasOwnProperty('teamId')) {
|
||||||
if (teamNamesById[player.team]) {
|
if (Object.keys(teamNamesById).length) {
|
||||||
player.team = teamNamesById[player.team];
|
player.team = teamNamesById[player.teamId] || '';
|
||||||
} else if (player.team === specTeamId) {
|
} else {
|
||||||
player.team = "spec";
|
player.team = player.teamId;
|
||||||
|
delete player.teamId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.players.push(player);
|
state.players.push(player);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
async sendPacket(type) {
|
async sendPacket(type) {
|
||||||
let receivedQueryId;
|
let receivedQueryId;
|
||||||
|
@ -100,7 +119,7 @@ class Gamespy1 extends Core {
|
||||||
const parts = new Set();
|
const parts = new Set();
|
||||||
let maxPartNum = 0;
|
let maxPartNum = 0;
|
||||||
|
|
||||||
return await this.udpSend('\\'+type+'\\', buffer => {
|
return await this.udpSend(type, buffer => {
|
||||||
const reader = this.reader(buffer);
|
const reader = this.reader(buffer);
|
||||||
const str = reader.string(buffer.length);
|
const str = reader.string(buffer.length);
|
||||||
const split = str.split('\\');
|
const split = str.split('\\');
|
||||||
|
|
|
@ -29,7 +29,9 @@ class Gamespy2 extends Core {
|
||||||
{
|
{
|
||||||
const body = await this.sendPacket([0, 0xff, 0]);
|
const body = await this.sendPacket([0, 0xff, 0]);
|
||||||
const reader = this.reader(body);
|
const reader = this.reader(body);
|
||||||
state.players = this.readFieldData(reader);
|
for (const rawPlayer of this.readFieldData(reader)) {
|
||||||
|
state.players.push(rawPlayer);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse teams
|
// Parse teams
|
||||||
|
|
|
@ -148,9 +148,15 @@ class Gamespy3 extends Core {
|
||||||
return await this.udpSend(b,(buffer) => {
|
return await this.udpSend(b,(buffer) => {
|
||||||
const reader = this.reader(buffer);
|
const reader = this.reader(buffer);
|
||||||
const iType = reader.uint(1);
|
const iType = reader.uint(1);
|
||||||
if(iType !== type) return;
|
if(iType !== type) {
|
||||||
|
this.logger.debug('Skipping packet, type mismatch');
|
||||||
|
return;
|
||||||
|
}
|
||||||
const iSessionId = reader.uint(4);
|
const iSessionId = reader.uint(4);
|
||||||
if(iSessionId !== this.sessionId) return;
|
if(iSessionId !== this.sessionId) {
|
||||||
|
this.logger.debug('Skipping packet, session id mismatch');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if(!assemble) {
|
if(!assemble) {
|
||||||
return reader.rest();
|
return reader.rest();
|
||||||
|
|
|
@ -2,8 +2,10 @@ const Core = require('./core');
|
||||||
|
|
||||||
class GeneShift extends Core {
|
class GeneShift extends Core {
|
||||||
async run(state) {
|
async run(state) {
|
||||||
|
await this.tcpPing();
|
||||||
|
|
||||||
const body = await this.request({
|
const body = await this.request({
|
||||||
uri: 'http://geneshift.net/game/receiveLobby.php'
|
url: 'http://geneshift.net/game/receiveLobby.php'
|
||||||
});
|
});
|
||||||
|
|
||||||
const split = body.split('<br/>');
|
const split = body.split('<br/>');
|
||||||
|
@ -26,7 +28,7 @@ class GeneShift extends Core {
|
||||||
state.raw.country = found[1];
|
state.raw.country = found[1];
|
||||||
state.name = found[4];
|
state.name = found[4];
|
||||||
state.map = found[5];
|
state.map = found[5];
|
||||||
state.players = parseInt(found[6]);
|
state.players.setNum(parseInt(found[6]));
|
||||||
state.maxplayers = parseInt(found[7]);
|
state.maxplayers = parseInt(found[7]);
|
||||||
// fields[8] is unknown?
|
// fields[8] is unknown?
|
||||||
state.raw.rules = found[9];
|
state.raw.rules = found[9];
|
||||||
|
|
|
@ -12,7 +12,7 @@ class Jc2mp extends Gamespy3 {
|
||||||
async run(state) {
|
async run(state) {
|
||||||
await super.run(state);
|
await super.run(state);
|
||||||
if(!state.players.length && parseInt(state.raw.numplayers)) {
|
if(!state.players.length && parseInt(state.raw.numplayers)) {
|
||||||
state.players = parseInt(state.raw.numplayers);
|
state.players.setNum(parseInt(state.raw.numplayers));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,11 +2,11 @@ const Core = require('./core');
|
||||||
|
|
||||||
class Kspdmp extends Core {
|
class Kspdmp extends Core {
|
||||||
async run(state) {
|
async run(state) {
|
||||||
const body = await this.request({
|
const json = await this.request({
|
||||||
uri: 'http://'+this.options.address+':'+this.options.port
|
url: 'http://'+this.options.address+':'+this.options.port,
|
||||||
|
responseType: 'json'
|
||||||
});
|
});
|
||||||
|
|
||||||
const json = JSON.parse(body);
|
|
||||||
for (const one of json.players) {
|
for (const one of json.players) {
|
||||||
state.players.push({name:one.nickname,team:one.team});
|
state.players.push({name:one.nickname,team:one.team});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,16 @@
|
||||||
const Core = require('./core'),
|
const Core = require('./core');
|
||||||
MinecraftVanilla = require('./minecraftvanilla'),
|
const MinecraftVanilla = require('./minecraftvanilla');
|
||||||
Gamespy3 = require('./gamespy3');
|
const MinecraftBedrock = require('./minecraftbedrock');
|
||||||
|
const Gamespy3 = require('./gamespy3');
|
||||||
|
const Results = require('../lib/Results');
|
||||||
|
|
||||||
|
/*
|
||||||
|
Vanilla servers respond to minecraftvanilla only
|
||||||
|
Some modded vanilla servers respond to minecraftvanilla and gamespy3, or gamespy3 only
|
||||||
|
Some bedrock servers respond to gamespy3 only
|
||||||
|
Some bedrock servers respond to minecraftbedrock only
|
||||||
|
Unsure if any bedrock servers respond to gamespy3 and minecraftbedrock
|
||||||
|
*/
|
||||||
|
|
||||||
class Minecraft extends Core {
|
class Minecraft extends Core {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -8,6 +18,7 @@ class Minecraft extends Core {
|
||||||
this.srvRecord = "_minecraft._tcp";
|
this.srvRecord = "_minecraft._tcp";
|
||||||
}
|
}
|
||||||
async run(state) {
|
async run(state) {
|
||||||
|
/** @type {Promise<Results>[]} */
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
const vanillaResolver = new MinecraftVanilla();
|
const vanillaResolver = new MinecraftVanilla();
|
||||||
|
@ -17,25 +28,40 @@ class Minecraft extends Core {
|
||||||
try { return await vanillaResolver.runOnceSafe(); } catch(e) {}
|
try { return await vanillaResolver.runOnceSafe(); } catch(e) {}
|
||||||
})());
|
})());
|
||||||
|
|
||||||
const bedrockResolver = new Gamespy3();
|
const gamespyResolver = new Gamespy3();
|
||||||
bedrockResolver.options = {
|
gamespyResolver.options = {
|
||||||
...this.options,
|
...this.options,
|
||||||
encoding: 'utf8',
|
encoding: 'utf8',
|
||||||
};
|
};
|
||||||
|
gamespyResolver.udpSocket = this.udpSocket;
|
||||||
|
promises.push((async () => {
|
||||||
|
try { return await gamespyResolver.runOnceSafe(); } catch(e) {}
|
||||||
|
})());
|
||||||
|
|
||||||
|
const bedrockResolver = new MinecraftBedrock();
|
||||||
|
bedrockResolver.options = this.options;
|
||||||
bedrockResolver.udpSocket = this.udpSocket;
|
bedrockResolver.udpSocket = this.udpSocket;
|
||||||
promises.push((async () => {
|
promises.push((async () => {
|
||||||
try { return await bedrockResolver.runOnceSafe(); } catch(e) {}
|
try { return await bedrockResolver.runOnceSafe(); } catch(e) {}
|
||||||
})());
|
})());
|
||||||
|
|
||||||
const [ vanillaState, bedrockState ] = await Promise.all(promises);
|
const [ vanillaState, gamespyState, bedrockState ] = await Promise.all(promises);
|
||||||
|
|
||||||
state.raw.vanilla = vanillaState;
|
state.raw.vanilla = vanillaState;
|
||||||
|
state.raw.gamespy = gamespyState;
|
||||||
state.raw.bedrock = bedrockState;
|
state.raw.bedrock = bedrockState;
|
||||||
|
|
||||||
if (!vanillaState && !bedrockState) {
|
if (!vanillaState && !gamespyState && !bedrockState) {
|
||||||
throw new Error('No protocols succeeded');
|
throw new Error('No protocols succeeded');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ordered from least worth to most worth (player names / etc)
|
||||||
|
if (bedrockState) {
|
||||||
|
if (bedrockState.name) state.name = bedrockState.name;
|
||||||
|
if (bedrockState.maxplayers) state.maxplayers = bedrockState.maxplayers;
|
||||||
|
if (bedrockState.players.length) state.players = bedrockState.players;
|
||||||
|
if (bedrockState.map) state.map = bedrockState.map;
|
||||||
|
}
|
||||||
if (vanillaState) {
|
if (vanillaState) {
|
||||||
try {
|
try {
|
||||||
let name = '';
|
let name = '';
|
||||||
|
@ -52,12 +78,13 @@ class Minecraft extends Core {
|
||||||
state.name = name;
|
state.name = name;
|
||||||
} catch(e) {}
|
} catch(e) {}
|
||||||
if (vanillaState.maxplayers) state.maxplayers = vanillaState.maxplayers;
|
if (vanillaState.maxplayers) state.maxplayers = vanillaState.maxplayers;
|
||||||
if (vanillaState.players) state.players = vanillaState.players;
|
if (vanillaState.players.length) state.players = vanillaState.players;
|
||||||
}
|
}
|
||||||
if (bedrockState) {
|
if (gamespyState) {
|
||||||
if (bedrockState.name) state.name = bedrockState.name;
|
if (gamespyState.name) state.name = gamespyState.name;
|
||||||
if (bedrockState.maxplayers) state.maxplayers = bedrockState.maxplayers;
|
if (gamespyState.maxplayers) state.maxplayers = gamespyState.maxplayers;
|
||||||
if (bedrockState.players) state.players = bedrockState.players;
|
if (gamespyState.players.length) state.players = gamespyState.players;
|
||||||
|
else if (gamespyState.raw.numplayers) state.players.setNum(parseInt(gamespyState.raw.numplayers));
|
||||||
}
|
}
|
||||||
// remove dupe spaces from name
|
// remove dupe spaces from name
|
||||||
state.name = state.name.replace(/\s+/g, ' ');
|
state.name = state.name.replace(/\s+/g, ' ');
|
||||||
|
|
76
protocols/minecraftbedrock.js
Normal file
76
protocols/minecraftbedrock.js
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
const Core = require('./core');
|
||||||
|
|
||||||
|
class MinecraftBedrock extends Core {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.byteorder = 'be';
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(state) {
|
||||||
|
const bufs = [
|
||||||
|
Buffer.from([0x01]), // Message ID, ID_UNCONNECTED_PING
|
||||||
|
Buffer.from('1122334455667788', 'hex'), // Nonce / timestamp
|
||||||
|
Buffer.from('00ffff00fefefefefdfdfdfd12345678', 'hex'), // Magic
|
||||||
|
Buffer.from('0000000000000000', 'hex') // Cliend GUID
|
||||||
|
];
|
||||||
|
|
||||||
|
return await this.udpSend(Buffer.concat(bufs), buffer => {
|
||||||
|
const reader = this.reader(buffer);
|
||||||
|
|
||||||
|
const messageId = reader.uint(1);
|
||||||
|
if (messageId !== 0x1c) {
|
||||||
|
this.logger.debug('Skipping packet, invalid message id');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nonce = reader.part(8).toString('hex'); // should match the nonce we sent
|
||||||
|
this.logger.debug('Nonce: ' + nonce);
|
||||||
|
if (nonce !== '1122334455667788') {
|
||||||
|
this.logger.debug('Skipping packet, invalid nonce');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// These 8 bytes are identical to the serverId string we receive in decimal below
|
||||||
|
reader.skip(8);
|
||||||
|
|
||||||
|
const magic = reader.part(16).toString('hex');
|
||||||
|
this.logger.debug('Magic value: ' + magic);
|
||||||
|
if (magic !== '00ffff00fefefefefdfdfdfd12345678') {
|
||||||
|
this.logger.debug('Skipping packet, invalid magic');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusLen = reader.uint(2);
|
||||||
|
if (reader.remaining() !== statusLen) {
|
||||||
|
throw new Error('Invalid status length: ' + reader.remaining() + ' vs ' + statusLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
const statusStr = reader.rest().toString('utf8');
|
||||||
|
this.logger.debug('Raw status str: ' + statusStr);
|
||||||
|
|
||||||
|
const split = statusStr.split(';');
|
||||||
|
if (split.length < 6) {
|
||||||
|
throw new Error('Missing enough chunks in status str');
|
||||||
|
}
|
||||||
|
|
||||||
|
state.raw.edition = split.shift();
|
||||||
|
state.name = split.shift();
|
||||||
|
state.raw.protocolVersion = split.shift();
|
||||||
|
state.raw.mcVersion = split.shift();
|
||||||
|
state.players.setNum(parseInt(split.shift()));
|
||||||
|
state.maxplayers = parseInt(split.shift());
|
||||||
|
if (split.length) state.raw.serverId = split.shift();
|
||||||
|
if (split.length) state.map = split.shift();
|
||||||
|
if (split.length) state.raw.gameMode = split.shift();
|
||||||
|
if (split.length) state.raw.nintendoOnly = !!parseInt(split.shift());
|
||||||
|
if (split.length) state.raw.ipv4Port = split.shift();
|
||||||
|
if (split.length) state.raw.ipv6Port = split.shift();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = MinecraftBedrock;
|
|
@ -47,6 +47,7 @@ class MinecraftVanilla extends Core {
|
||||||
|
|
||||||
state.raw = json;
|
state.raw = json;
|
||||||
state.maxplayers = json.players.max;
|
state.maxplayers = json.players.max;
|
||||||
|
|
||||||
if(json.players.sample) {
|
if(json.players.sample) {
|
||||||
for(const player of json.players.sample) {
|
for(const player of json.players.sample) {
|
||||||
state.players.push({
|
state.players.push({
|
||||||
|
@ -55,7 +56,11 @@ class MinecraftVanilla extends Core {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (let i = 0; i < Math.min(json.players.online, 10000); i++) {
|
|
||||||
|
// players.sample may not contain all players or no players at all, depending on how many players are online.
|
||||||
|
// Insert a dummy player object for every online player that is not listed in players.sample.
|
||||||
|
// Limit player amount to 10.000 players for performance reasons.
|
||||||
|
for (let i = state.players.length; i < Math.min(json.players.online, 10000); i++) {
|
||||||
state.players.push({});
|
state.players.push({});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ class MumblePing extends Core {
|
||||||
state.raw.versionMinor = reader.uint(1);
|
state.raw.versionMinor = reader.uint(1);
|
||||||
state.raw.versionPatch = reader.uint(1);
|
state.raw.versionPatch = reader.uint(1);
|
||||||
reader.skip(8);
|
reader.skip(8);
|
||||||
state.players = reader.uint(4);
|
state.players.setNum(reader.uint(4));
|
||||||
state.maxplayers = reader.uint(4);
|
state.maxplayers = reader.uint(4);
|
||||||
state.raw.allowedbandwidth = reader.uint(4);
|
state.raw.allowedbandwidth = reader.uint(4);
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ class OpenTtd extends Core {
|
||||||
|
|
||||||
state.password = !!reader.uint(1);
|
state.password = !!reader.uint(1);
|
||||||
state.maxplayers = reader.uint(1);
|
state.maxplayers = reader.uint(1);
|
||||||
state.players = reader.uint(1);
|
state.players.setNum(reader.uint(1));
|
||||||
state.raw.numspectators = reader.uint(1);
|
state.raw.numspectators = reader.uint(1);
|
||||||
state.map = reader.string();
|
state.map = reader.string();
|
||||||
state.raw.map_width = reader.uint(2);
|
state.raw.map_width = reader.uint(2);
|
||||||
|
@ -60,9 +60,9 @@ class OpenTtd extends Core {
|
||||||
company.id = reader.uint(1);
|
company.id = reader.uint(1);
|
||||||
company.name = reader.string();
|
company.name = reader.string();
|
||||||
company.year_start = reader.uint(4);
|
company.year_start = reader.uint(4);
|
||||||
company.value = reader.uint(8);
|
company.value = reader.uint(8).toString();
|
||||||
company.money = reader.uint(8);
|
company.money = reader.uint(8).toString();
|
||||||
company.income = reader.uint(8);
|
company.income = reader.uint(8).toString();
|
||||||
company.performance = reader.uint(2);
|
company.performance = reader.uint(2);
|
||||||
company.password = !!reader.uint(1);
|
company.password = !!reader.uint(1);
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ class Rfactor extends Core {
|
||||||
state.raw.ping = reader.uint(2);
|
state.raw.ping = reader.uint(2);
|
||||||
state.raw.packedFlags = reader.uint(1);
|
state.raw.packedFlags = reader.uint(1);
|
||||||
state.raw.rate = reader.uint(1);
|
state.raw.rate = reader.uint(1);
|
||||||
state.players = reader.uint(1);
|
state.players.setNum(reader.uint(1));
|
||||||
state.maxplayers = reader.uint(1);
|
state.maxplayers = reader.uint(1);
|
||||||
state.raw.bots = reader.uint(1);
|
state.raw.bots = reader.uint(1);
|
||||||
state.raw.packedSpecial = reader.uint(1);
|
state.raw.packedSpecial = reader.uint(1);
|
||||||
|
|
|
@ -69,7 +69,7 @@ class Samp extends Core {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!gotPlayerData) {
|
if (!gotPlayerData) {
|
||||||
state.players = state.raw.numplayers;
|
state.players.setNum(state.raw.numplayers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async sendPacket(type,allowTimeout) {
|
async sendPacket(type,allowTimeout) {
|
||||||
|
|
31
protocols/savage2.js
Normal file
31
protocols/savage2.js
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
const Core = require('./core');
|
||||||
|
|
||||||
|
class Savage2 extends Core {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(state) {
|
||||||
|
const buffer = await this.udpSend('\x01',b => b);
|
||||||
|
const reader = this.reader(buffer);
|
||||||
|
|
||||||
|
reader.skip(12);
|
||||||
|
state.name = this.stripColorCodes(reader.string());
|
||||||
|
state.players.setNum(reader.uint(1));
|
||||||
|
state.maxplayers = reader.uint(1);
|
||||||
|
state.raw.time = reader.string();
|
||||||
|
state.map = reader.string();
|
||||||
|
state.raw.nextmap = reader.string();
|
||||||
|
state.raw.location = reader.string();
|
||||||
|
state.raw.minplayers = reader.uint(1);
|
||||||
|
state.raw.gametype = reader.string();
|
||||||
|
state.raw.version = reader.string();
|
||||||
|
state.raw.minlevel = reader.uint(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
stripColorCodes(str) {
|
||||||
|
return str.replace(/\^./g,'');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Savage2;
|
|
@ -1,16 +0,0 @@
|
||||||
const Valve = require('./valve');
|
|
||||||
|
|
||||||
class Squad extends Valve {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
async cleanup(state) {
|
|
||||||
await super.cleanup(state);
|
|
||||||
if (state.raw.rules != null && state.raw.rules.Password_b === "true") {
|
|
||||||
state.password = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Squad;
|
|
|
@ -16,7 +16,7 @@ class Starmade extends Core {
|
||||||
const reader = this.reader(buffer);
|
const reader = this.reader(buffer);
|
||||||
const packetLength = reader.uint(4);
|
const packetLength = reader.uint(4);
|
||||||
this.logger.debug("Received packet length: " + packetLength);
|
this.logger.debug("Received packet length: " + packetLength);
|
||||||
const timestamp = reader.uint(8);
|
const timestamp = reader.uint(8).toString();
|
||||||
this.logger.debug("Received timestamp: " + timestamp);
|
this.logger.debug("Received timestamp: " + timestamp);
|
||||||
if (reader.remaining() < packetLength || reader.remaining() < 5) return;
|
if (reader.remaining() < packetLength || reader.remaining() < 5) return;
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ class Starmade extends Core {
|
||||||
if(typeof data[2] === 'string') state.name = data[2];
|
if(typeof data[2] === 'string') state.name = data[2];
|
||||||
if(typeof data[3] === 'string') state.raw.description = data[3];
|
if(typeof data[3] === 'string') state.raw.description = data[3];
|
||||||
if(typeof data[4] === 'number') state.raw.startTime = data[4];
|
if(typeof data[4] === 'number') state.raw.startTime = data[4];
|
||||||
if(typeof data[5] === 'number') state.players = data[5];
|
if(typeof data[5] === 'number') state.players.setNum(data[5]);
|
||||||
if(typeof data[6] === 'number') state.maxplayers = data[6];
|
if(typeof data[6] === 'number') state.maxplayers = data[6];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,15 +2,15 @@ const Core = require('./core');
|
||||||
|
|
||||||
class Terraria extends Core {
|
class Terraria extends Core {
|
||||||
async run(state) {
|
async run(state) {
|
||||||
const body = await this.request({
|
const json = await this.request({
|
||||||
uri: 'http://'+this.options.address+':'+this.options.port+'/v2/server/status',
|
url: 'http://'+this.options.address+':'+this.options.port+'/v2/server/status',
|
||||||
qs: {
|
searchParams: {
|
||||||
players: 'true',
|
players: 'true',
|
||||||
token: this.options.token
|
token: this.options.token
|
||||||
}
|
},
|
||||||
|
responseType: 'json'
|
||||||
});
|
});
|
||||||
|
|
||||||
const json = JSON.parse(body);
|
|
||||||
if(json.status !== '200') throw new Error('Invalid status');
|
if(json.status !== '200') throw new Error('Invalid status');
|
||||||
|
|
||||||
for (const one of json.players) {
|
for (const one of json.players) {
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
const Bzip2 = require('compressjs').Bzip2,
|
const Bzip2 = require('compressjs').Bzip2,
|
||||||
Core = require('./core');
|
Core = require('./core'),
|
||||||
|
Results = require('../lib/Results');
|
||||||
|
|
||||||
|
const AppId = {
|
||||||
|
Squad: 393380,
|
||||||
|
Bat1944: 489940,
|
||||||
|
Ship: 2400,
|
||||||
|
DayZ: 221100
|
||||||
|
};
|
||||||
|
|
||||||
class Valve extends Core {
|
class Valve extends Core {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -34,11 +42,10 @@ class Valve extends Core {
|
||||||
await this.cleanup(state);
|
await this.cleanup(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
async queryInfo(state) {
|
async queryInfo(/** Results */ state) {
|
||||||
this.debugLog("Requesting info ...");
|
this.debugLog("Requesting info ...");
|
||||||
const b = await this.sendPacket(
|
const b = await this.sendPacket(
|
||||||
0x54,
|
0x54,
|
||||||
false,
|
|
||||||
'Source Engine Query\0',
|
'Source Engine Query\0',
|
||||||
this.goldsrcInfo ? 0x6D : 0x49,
|
this.goldsrcInfo ? 0x6D : 0x49,
|
||||||
false
|
false
|
||||||
|
@ -53,7 +60,7 @@ class Valve extends Core {
|
||||||
state.map = reader.string();
|
state.map = reader.string();
|
||||||
state.raw.folder = reader.string();
|
state.raw.folder = reader.string();
|
||||||
state.raw.game = reader.string();
|
state.raw.game = reader.string();
|
||||||
state.raw.steamappid = reader.uint(2);
|
state.raw.appId = reader.uint(2);
|
||||||
state.raw.numplayers = reader.uint(1);
|
state.raw.numplayers = reader.uint(1);
|
||||||
state.maxplayers = reader.uint(1);
|
state.maxplayers = reader.uint(1);
|
||||||
|
|
||||||
|
@ -85,7 +92,7 @@ class Valve extends Core {
|
||||||
if(this.goldsrcInfo) {
|
if(this.goldsrcInfo) {
|
||||||
state.raw.numbots = reader.uint(1);
|
state.raw.numbots = reader.uint(1);
|
||||||
} else {
|
} else {
|
||||||
if(state.raw.folder === 'ship') {
|
if(state.raw.appId === AppId.Ship) {
|
||||||
state.raw.shipmode = reader.uint(1);
|
state.raw.shipmode = reader.uint(1);
|
||||||
state.raw.shipwitnesses = reader.uint(1);
|
state.raw.shipwitnesses = reader.uint(1);
|
||||||
state.raw.shipduration = reader.uint(1);
|
state.raw.shipduration = reader.uint(1);
|
||||||
|
@ -93,30 +100,35 @@ class Valve extends Core {
|
||||||
state.raw.version = reader.string();
|
state.raw.version = reader.string();
|
||||||
const extraFlag = reader.uint(1);
|
const extraFlag = reader.uint(1);
|
||||||
if(extraFlag & 0x80) state.gamePort = reader.uint(2);
|
if(extraFlag & 0x80) state.gamePort = reader.uint(2);
|
||||||
if(extraFlag & 0x10) state.raw.steamid = reader.uint(8);
|
if(extraFlag & 0x10) state.raw.steamid = reader.uint(8).toString();
|
||||||
if(extraFlag & 0x40) {
|
if(extraFlag & 0x40) {
|
||||||
state.raw.sourcetvport = reader.uint(2);
|
state.raw.sourcetvport = reader.uint(2);
|
||||||
state.raw.sourcetvname = reader.string();
|
state.raw.sourcetvname = reader.string();
|
||||||
}
|
}
|
||||||
if(extraFlag & 0x20) state.raw.tags = reader.string();
|
if(extraFlag & 0x20) state.raw.tags = reader.string().split(',');
|
||||||
if(extraFlag & 0x01) state.raw.gameid = reader.uint(8);
|
if(extraFlag & 0x01) {
|
||||||
|
const gameId = reader.uint(8);
|
||||||
|
const betterAppId = gameId.getLowBitsUnsigned() & 0xffffff;
|
||||||
|
if (betterAppId) {
|
||||||
|
state.raw.appId = betterAppId;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// from https://developer.valvesoftware.com/wiki/Server_queries
|
// from https://developer.valvesoftware.com/wiki/Server_queries
|
||||||
if(
|
if(
|
||||||
state.raw.protocol === 7 && (
|
state.raw.protocol === 7 && (
|
||||||
state.raw.steamappid === 215
|
state.raw.appId === 215
|
||||||
|| state.raw.steamappid === 17550
|
|| state.raw.appId === 17550
|
||||||
|| state.raw.steamappid === 17700
|
|| state.raw.appId === 17700
|
||||||
|| state.raw.steamappid === 240
|
|| state.raw.appId === 240
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
this._skipSizeInSplitHeader = true;
|
this._skipSizeInSplitHeader = true;
|
||||||
}
|
}
|
||||||
this.debugLog("STEAM APPID: "+state.raw.steamappid);
|
this.logger.debug("INFO: ", state.raw);
|
||||||
this.debugLog("PROTOCOL: "+state.raw.protocol);
|
|
||||||
if(state.raw.protocol === 48) {
|
if(state.raw.protocol === 48) {
|
||||||
this.debugLog("GOLDSRC DETECTED - USING MODIFIED SPLIT FORMAT");
|
this.logger.debug("GOLDSRC DETECTED - USING MODIFIED SPLIT FORMAT");
|
||||||
this.goldsrcSplits = true;
|
this.goldsrcSplits = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,7 +140,6 @@ class Valve extends Core {
|
||||||
this.debugLog("Requesting legacy challenge key ...");
|
this.debugLog("Requesting legacy challenge key ...");
|
||||||
await this.sendPacket(
|
await this.sendPacket(
|
||||||
0x57,
|
0x57,
|
||||||
false,
|
|
||||||
null,
|
null,
|
||||||
0x41,
|
0x41,
|
||||||
false
|
false
|
||||||
|
@ -136,22 +147,24 @@ class Valve extends Core {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async queryPlayers(state) {
|
async queryPlayers(/** Results */ state) {
|
||||||
state.raw.players = [];
|
state.raw.players = [];
|
||||||
|
|
||||||
// CSGO doesn't even respond sometimes if host_players_show is not 2
|
|
||||||
// Ignore timeouts in only this case
|
|
||||||
const allowTimeout = state.raw.steamappid === 730;
|
|
||||||
|
|
||||||
this.debugLog("Requesting player list ...");
|
this.debugLog("Requesting player list ...");
|
||||||
const b = await this.sendPacket(
|
const b = await this.sendPacket(
|
||||||
0x55,
|
0x55,
|
||||||
true,
|
|
||||||
null,
|
null,
|
||||||
0x44,
|
0x44,
|
||||||
allowTimeout
|
true
|
||||||
);
|
);
|
||||||
if (b === null) return; // timed out
|
|
||||||
|
if (b === null) {
|
||||||
|
// Player query timed out
|
||||||
|
// CSGO doesn't respond to player query if host_players_show is not 2
|
||||||
|
// Conan Exiles never responds to player query
|
||||||
|
// Just skip it, and we'll fill with dummy objects in cleanup()
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const reader = this.reader(b);
|
const reader = this.reader(b);
|
||||||
const num = reader.uint(1);
|
const num = reader.uint(1);
|
||||||
|
@ -175,42 +188,178 @@ class Valve extends Core {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async queryRules(state) {
|
async queryRules(/** Results */ state) {
|
||||||
state.raw.rules = {};
|
const appId = state.raw.appId;
|
||||||
|
if (appId === AppId.Squad
|
||||||
|
|| appId === AppId.Bat1944
|
||||||
|
|| this.options.requestRules) {
|
||||||
|
// let's get 'em
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rules = {};
|
||||||
|
state.raw.rules = rules;
|
||||||
this.debugLog("Requesting rules ...");
|
this.debugLog("Requesting rules ...");
|
||||||
const b = await this.sendPacket(0x56,true,null,0x45,true);
|
const b = await this.sendPacket(0x56,null,0x45,true);
|
||||||
if (b === null) return; // timed out - the server probably just has rules disabled
|
if (b === null) return; // timed out - the server probably has rules disabled
|
||||||
|
|
||||||
|
const dayZPayload = [];
|
||||||
|
let dayZPayloadEnded = false;
|
||||||
|
|
||||||
const reader = this.reader(b);
|
const reader = this.reader(b);
|
||||||
const num = reader.uint(2);
|
const num = reader.uint(2);
|
||||||
for(let i = 0; i < num; i++) {
|
for(let i = 0; i < num; i++) {
|
||||||
|
if (appId === AppId.DayZ && !dayZPayloadEnded) {
|
||||||
|
const one = reader.uint(1);
|
||||||
|
const two = reader.uint(1);
|
||||||
|
const three = reader.uint(1);
|
||||||
|
if (one !== 0 && two !== 0 && three === 0) {
|
||||||
|
while (true) {
|
||||||
|
const byte = reader.uint(1);
|
||||||
|
if (byte === 0) break;
|
||||||
|
dayZPayload.push(byte);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
reader.skip(-3);
|
||||||
|
dayZPayloadEnded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const key = reader.string();
|
const key = reader.string();
|
||||||
const value = reader.string();
|
const value = reader.string();
|
||||||
state.raw.rules[key] = value;
|
rules[key] = value;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async cleanup(state) {
|
|
||||||
// Battalion 1944 puts its info into rules fields for some reason
|
// Battalion 1944 puts its info into rules fields for some reason
|
||||||
if ('bat_name_s' in state.raw.rules) {
|
if (appId === AppId.Bat1944) {
|
||||||
state.name = state.raw.rules.bat_name_s;
|
if ('bat_name_s' in rules) {
|
||||||
delete state.raw.rules.bat_name_s;
|
state.name = rules.bat_name_s;
|
||||||
if ('bat_player_count_s' in state.raw.rules) {
|
delete rules.bat_name_s;
|
||||||
state.raw.numplayers = parseInt(state.raw.rules.bat_player_count_s);
|
if ('bat_player_count_s' in rules) {
|
||||||
delete state.raw.rules.bat_player_count_s;
|
state.raw.numplayers = parseInt(rules.bat_player_count_s);
|
||||||
|
delete rules.bat_player_count_s;
|
||||||
}
|
}
|
||||||
if ('bat_max_players_i' in state.raw.rules) {
|
if ('bat_max_players_i' in rules) {
|
||||||
state.maxplayers = parseInt(state.raw.rules.bat_max_players_i);
|
state.maxplayers = parseInt(rules.bat_max_players_i);
|
||||||
delete state.raw.rules.bat_max_players_i;
|
delete rules.bat_max_players_i;
|
||||||
}
|
}
|
||||||
if ('bat_has_password_s' in state.raw.rules) {
|
if ('bat_has_password_s' in rules) {
|
||||||
state.password = state.raw.rules.bat_has_password_s === 'Y';
|
state.password = rules.bat_has_password_s === 'Y';
|
||||||
delete state.raw.rules.bat_has_password_s;
|
delete rules.bat_has_password_s;
|
||||||
}
|
}
|
||||||
// apparently map is already right, and this var is often wrong
|
// apparently map is already right, and this var is often wrong
|
||||||
delete state.raw.rules.bat_map_s;
|
delete rules.bat_map_s;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Squad keeps its password in a separate field
|
||||||
|
if (appId === AppId.Squad) {
|
||||||
|
if (rules.Password_b === "true") {
|
||||||
|
state.password = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (appId === AppId.DayZ) {
|
||||||
|
state.raw.dayzMods = this.readDayzMods(Buffer.from(dayZPayload));
|
||||||
|
|
||||||
|
if (state.raw.tags) {
|
||||||
|
for (const tag of state.raw.tags) {
|
||||||
|
if (tag.startsWith('lqs')) {
|
||||||
|
const value = parseInt(tag.replace('lqs', ''));
|
||||||
|
if (!isNaN(value)) {
|
||||||
|
state.raw.queue = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tag.startsWith('etm')) {
|
||||||
|
const value = parseInt(tag.replace('etm', ''));
|
||||||
|
if (!isNaN(value)) {
|
||||||
|
state.raw.dayAcceleration = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tag.startsWith('entm')) {
|
||||||
|
const value = parseInt(tag.replace('entm', ''));
|
||||||
|
if (!isNaN(value)) {
|
||||||
|
state.raw.nightAcceleration = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readDayzMods(/** Buffer */ buffer) {
|
||||||
|
if (!buffer.length) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.debug("DAYZ BUFFER");
|
||||||
|
this.logger.debug(buffer);
|
||||||
|
|
||||||
|
const reader = this.reader(buffer);
|
||||||
|
const version = this.readDayzByte(reader);
|
||||||
|
const overflow = this.readDayzByte(reader);
|
||||||
|
const dlc1 = this.readDayzByte(reader);
|
||||||
|
const dlc2 = this.readDayzByte(reader);
|
||||||
|
this.logger.debug("version " + version);
|
||||||
|
this.logger.debug("overflow " + overflow);
|
||||||
|
this.logger.debug("dlc1 " + dlc1);
|
||||||
|
this.logger.debug("dlc2 " + dlc2);
|
||||||
|
const mods = [];
|
||||||
|
mods.push(...this.readDayzModsSection(reader, true));
|
||||||
|
mods.push(...this.readDayzModsSection(reader, false));
|
||||||
|
return mods;
|
||||||
|
}
|
||||||
|
readDayzModsSection(reader, withHeader) {
|
||||||
|
const out = [];
|
||||||
|
const count = this.readDayzByte(reader);
|
||||||
|
for(let i = 0; i < count; i++) {
|
||||||
|
const mod = {};
|
||||||
|
if (withHeader) {
|
||||||
|
const unknown = this.readDayzUint(reader, 4); // mod hash?
|
||||||
|
if (i !== count - 1) {
|
||||||
|
// For some reason this is 4 on all of them, but doesn't exist on the last one?
|
||||||
|
const flag = this.readDayzByte(reader);
|
||||||
|
//mod.flag = flag;
|
||||||
|
}
|
||||||
|
mod.workshopId = this.readDayzUint(reader, 4);
|
||||||
|
}
|
||||||
|
mod.title = this.readDayzString(reader);
|
||||||
|
out.push(mod);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
readDayzUint(reader, bytes) {
|
||||||
|
const out = [];
|
||||||
|
for (let i = 0; i < bytes; i++) {
|
||||||
|
out.push(this.readDayzByte(reader));
|
||||||
|
}
|
||||||
|
const buf = Buffer.from(out);
|
||||||
|
const r2 = this.reader(buf);
|
||||||
|
return r2.uint(bytes);
|
||||||
|
}
|
||||||
|
readDayzByte(reader) {
|
||||||
|
const byte = reader.uint(1);
|
||||||
|
if (byte === 1) {
|
||||||
|
const byte2 = reader.uint(1);
|
||||||
|
if (byte2 === 1) return 1;
|
||||||
|
if (byte2 === 2) return 0;
|
||||||
|
if (byte2 === 3) return 0xff;
|
||||||
|
return 0; // ?
|
||||||
|
}
|
||||||
|
return byte;
|
||||||
|
}
|
||||||
|
readDayzString(reader) {
|
||||||
|
const length = this.readDayzByte(reader);
|
||||||
|
const out = [];
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
out.push(this.readDayzByte(reader));
|
||||||
|
}
|
||||||
|
return Buffer.from(out).toString('utf8');
|
||||||
|
}
|
||||||
|
|
||||||
|
async cleanup(/** Results */ state) {
|
||||||
// Organize players / hidden players into player / bot arrays
|
// Organize players / hidden players into player / bot arrays
|
||||||
const botProbability = (p) => {
|
const botProbability = (p) => {
|
||||||
if (p.time === -1) return Number.MAX_VALUE;
|
if (p.time === -1) return Number.MAX_VALUE;
|
||||||
|
@ -243,33 +392,29 @@ class Valve extends Core {
|
||||||
**/
|
**/
|
||||||
async sendPacket(
|
async sendPacket(
|
||||||
type,
|
type,
|
||||||
sendChallenge,
|
|
||||||
payload,
|
payload,
|
||||||
expect,
|
expect,
|
||||||
allowTimeout
|
allowTimeout
|
||||||
) {
|
) {
|
||||||
for (let keyRetry = 0; keyRetry < 3; keyRetry++) {
|
for (let keyRetry = 0; keyRetry < 3; keyRetry++) {
|
||||||
let requestKeyChanged = false;
|
let receivedNewChallengeKey = false;
|
||||||
const response = await this.sendPacketRaw(
|
const response = await this.sendPacketRaw(
|
||||||
type, sendChallenge, payload,
|
type, payload,
|
||||||
(payload) => {
|
(payload) => {
|
||||||
const reader = this.reader(payload);
|
const reader = this.reader(payload);
|
||||||
const type = reader.uint(1);
|
const type = reader.uint(1);
|
||||||
this.debugLog(() => "Received " + type.toString(16) + " expected " + expect.toString(16));
|
this.debugLog(() => "Received 0x" + type.toString(16) + " expected 0x" + expect.toString(16));
|
||||||
if (type === 0x41) {
|
if (type === 0x41) {
|
||||||
const key = reader.uint(4);
|
const key = reader.uint(4);
|
||||||
if (this._challenge !== key) {
|
if (this._challenge !== key) {
|
||||||
this.debugLog('Received new challenge key: ' + key);
|
this.debugLog('Received new challenge key: 0x' + key.toString(16));
|
||||||
this._challenge = key;
|
this._challenge = key;
|
||||||
if (sendChallenge) {
|
receivedNewChallengeKey = true;
|
||||||
this.debugLog('Challenge key changed -- allowing query retry if needed');
|
|
||||||
requestKeyChanged = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (type === expect) {
|
if (type === expect) {
|
||||||
return reader.rest();
|
return reader.rest();
|
||||||
} else if (requestKeyChanged) {
|
} else if (receivedNewChallengeKey) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -277,7 +422,7 @@ class Valve extends Core {
|
||||||
if (allowTimeout) return null;
|
if (allowTimeout) return null;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
if (!requestKeyChanged) {
|
if (!receivedNewChallengeKey) {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -294,26 +439,47 @@ class Valve extends Core {
|
||||||
**/
|
**/
|
||||||
async sendPacketRaw(
|
async sendPacketRaw(
|
||||||
type,
|
type,
|
||||||
sendChallenge,
|
|
||||||
payload,
|
payload,
|
||||||
onResponse,
|
onResponse,
|
||||||
onTimeout
|
onTimeout
|
||||||
) {
|
) {
|
||||||
|
const challengeAtBeginning = type === 0x55 || type === 0x56;
|
||||||
|
const challengeAtEnd = type === 0x54 && !!this._challenge;
|
||||||
|
|
||||||
if (typeof payload === 'string') payload = Buffer.from(payload, 'binary');
|
if (typeof payload === 'string') payload = Buffer.from(payload, 'binary');
|
||||||
const challengeLength = sendChallenge ? 4 : 0;
|
|
||||||
const payloadLength = payload ? payload.length : 0;
|
|
||||||
|
|
||||||
const b = Buffer.alloc(5 + challengeLength + payloadLength);
|
const b = Buffer.alloc(5
|
||||||
b.writeInt32LE(-1, 0);
|
+ (challengeAtBeginning ? 4 : 0)
|
||||||
b.writeUInt8(type, 4);
|
+ (challengeAtEnd ? 4 : 0)
|
||||||
|
+ (payload ? payload.length : 0)
|
||||||
|
);
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
if (sendChallenge) {
|
|
||||||
let challenge = this._challenge;
|
let challenge = this._challenge;
|
||||||
if (!challenge) challenge = 0xffffffff;
|
if (!challenge) challenge = 0xffffffff;
|
||||||
if (this.byteorder === 'le') b.writeUInt32LE(challenge, 5);
|
|
||||||
else b.writeUInt32BE(challenge, 5);
|
b.writeInt32LE(-1, offset);
|
||||||
|
offset += 4;
|
||||||
|
|
||||||
|
b.writeUInt8(type, offset);
|
||||||
|
offset += 1;
|
||||||
|
|
||||||
|
if (challengeAtBeginning) {
|
||||||
|
if (this.byteorder === 'le') b.writeUInt32LE(challenge, offset);
|
||||||
|
else b.writeUInt32BE(challenge, offset);
|
||||||
|
offset += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (payload) {
|
||||||
|
payload.copy(b, offset);
|
||||||
|
offset += payload.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (challengeAtEnd) {
|
||||||
|
if (this.byteorder === 'le') b.writeUInt32LE(challenge, offset);
|
||||||
|
else b.writeUInt32BE(challenge, offset);
|
||||||
|
offset += 4;
|
||||||
}
|
}
|
||||||
if (payloadLength) payload.copy(b, 5 + challengeLength);
|
|
||||||
|
|
||||||
const packetStorage = {};
|
const packetStorage = {};
|
||||||
return await this.udpSend(
|
return await this.udpSend(
|
||||||
|
@ -351,7 +517,7 @@ class Valve extends Core {
|
||||||
|
|
||||||
packets[packetNum] = payload;
|
packets[packetNum] = payload;
|
||||||
|
|
||||||
this.debugLog(() => "Received partial packet uid:"+uid+" num:"+packetNum);
|
this.debugLog(() => "Received partial packet uid: 0x"+uid.toString(16)+" num: "+packetNum);
|
||||||
this.debugLog(() => "Received "+Object.keys(packets).length+'/'+numPackets+" packets for this UID");
|
this.debugLog(() => "Received "+Object.keys(packets).length+'/'+numPackets+" packets for this UID");
|
||||||
|
|
||||||
if(Object.keys(packets).length !== numPackets) return;
|
if(Object.keys(packets).length !== numPackets) return;
|
||||||
|
|
Loading…
Reference in a new issue