diff --git a/README.md b/README.md index efe14a1..72bd60e 100644 --- a/README.md +++ b/README.md @@ -1,396 +1,396 @@ -node-GameDig - Game Server Query Library ---- -node-GameDig is a game server query library, capable of querying for the status of -nearly any game or voice server. If a server makes its status publically available, -GameDig can fetch it for you. - -GameDig is available as a node.js module, as well as a -[command line executable](#usage-from-command-line). - -Usage from Node.js ---- - -```shell -npm install gamedig -``` - -```javascript -var Gamedig = require('gamedig'); -Gamedig.query( - { - type: 'minecraft', - host: 'mc.example.com' - }, - function(state) { - if(state.error) console.log("Server is offline"); - else console.log(state); - } -); -``` - -> Is NPM out of date? If you're feeling lucky, you can install the latest code with -> ```shell -> npm install "git+https://github.com/sonicsnes/node-gamedig.git" -> ``` - -### Input Parameters - -* **type**: One of the game IDs listed in the game list below -* **host** -* **port**: (optional) Uses the protocol default if not set -* **notes**: (optional) Passed through to output - -###Callback Function - -The callback function is "guaranteed" to be called exactly once. - -If an error occurs, the returned object will contain an "error" key, indicating the issue. -If the error key exists, it should be assumed that the game server is offline or unreachable. - -Otherwise, the returned object is guaranteed to contain the following keys: - -**Stable, always present:** - -* **name** -* **map** -* **password**: Boolean -* **maxplayers** -* **players**: (array of objects) Each object **may** contain name, ping, score, team, address -* **bots**: Same schema as players -* **notes**: Passed through from the input - -**Unstable, not guaranteed:** - -* **raw**: Contains all information received from the server -* **query**: Details about the query performed - -It can usually be assumed that the number of players online is equal to the length of the players array. -Some servers may return an additional player count number, which may be present in the unstable raw object. - -Games List ---- - -###Supported - - -* Age of Chivalry (ageofchivalry) -* Age of Empires 2 (aoe2) [[Separate Query Port](#separate-query-port)] -* Alien Arena (alienarena) [[Separate Query Port](#separate-query-port)] -* Alien Swarm (alienswarm) -* Aliens vs Predator 2 (avp2) -* Aliens vs Predator 2010 (avp2010) -* America's Army (americasarmy) [[Separate Query Port](#separate-query-port)] -* America's Army 2 (americasarmy2) [[Separate Query Port](#separate-query-port)] -* America's Army 3 (americasarmy3) [[Separate Query Port](#separate-query-port)] -* America's Army: Proving Grounds (americasarmypg) [[Separate Query Port](#separate-query-port)] -* ArmA Armed Assault 1 (arma) -* ArmA Armed Assault 2 (arma2) [[Separate Query Port](#separate-query-port)] -* ArmA Armed Assault 3 (arma3) [[Separate Query Port](#separate-query-port)] -* Armagetron (armagetron) -* Baldur's Gate (baldursgate) [[Separate Query Port](#separate-query-port)] -* Battlefield 1942 (bf1942) [[Separate Query Port](#separate-query-port)] -* Battlefield Vietnam (bfv) [[Separate Query Port](#separate-query-port)] -* Battlefield 2 (bf2) [[Separate Query Port](#separate-query-port)] -* Battlefield 2142 (bf2142) [[Separate Query Port](#separate-query-port)] -* Battlefield: Bad Company 2 (bfbc2) [[Separate Query Port](#separate-query-port)] -* Battlefield 3 (bf3) [[Separate Query Port](#separate-query-port)] -* Battlefield 4 (bf4) [[Separate Query Port](#separate-query-port)] -* Breach (breach) -* Breed (breed) -* Brink (brink) [[Separate Query Port](#separate-query-port)] -* Build and Shoot (buildandshoot) [[Separate Query Port](#separate-query-port)] -* Call of Duty (cod) -* Call of Duty: United Offensive (coduo) -* Call of Duty 2 (cod2) -* Call of Duty 3 (cod3) -* Call of Duty 4: Modern Warfare (cod4) -* Call of Duty: World at War (codwaw) -* Call of Duty: Modern Warfare 2 (codmw2) -* Call of Duty: Modern Warfare 3 (codmw3) [[Separate Query Port](#separate-query-port)] -* Call of Juarez (callofjuarez) [[Separate Query Port](#separate-query-port)] -* Chaser (chaser) [[Separate Query Port](#separate-query-port)] -* Chrome (chrome) [[Separate Query Port](#separate-query-port)] -* Codename Eagle (codenameeagle) [[Separate Query Port](#separate-query-port)] -* Commandos 3: Destination Berlin (commandos3) [[Separate Query Port](#separate-query-port)] -* Command and Conquer: Renegade (cacrenegade) [[Separate Query Port](#separate-query-port)] -* Contact J.A.C.K. (contactjack) [[Separate Query Port](#separate-query-port)] -* Counter-Strike 1.6 (cs16) -* Counter-Strike: Condition Zero (cscz) -* Counter-Strike: Source (css) -* Counter-Strike: Global Offensive (csgo) -* Cross Racing Championship (crossracing) [[Separate Query Port](#separate-query-port)] -* Crysis (crysis) -* Crysis Wars (crysiswars) -* Crysis 2 (crysis2) -* Daikatana (daikatana) [[Separate Query Port](#separate-query-port)] -* Dark Messiah of Might and Magic (dmomam) -* Darkest Hour (darkesthour) [[Separate Query Port](#separate-query-port)] -* DayZ (dayz) [[Separate Query Port](#separate-query-port)] [[Additional Notes](#dayz)] -* Deadly Dozen: Pacific Theater (deadlydozenpt) [[Separate Query Port](#separate-query-port)] -* Deer Hunter 2005 (dh2005) [[Separate Query Port](#separate-query-port)] -* Descent 3 (descent3) [[Separate Query Port](#separate-query-port)] -* Deus Ex (deusex) [[Separate Query Port](#separate-query-port)] -* Devastation (devastation) [[Separate Query Port](#separate-query-port)] -* Dino D-Day (dinodday) -* Dirt Track Racing 2 (dirttrackracing2) [[Separate Query Port](#separate-query-port)] -* Day of Defeat (dod) -* Day of Defeat: Source (dods) -* Doom 3 (doom3) -* DOTA 2 (dota2) -* Drakan (drakan) [[Separate Query Port](#separate-query-port)] -* Enemy Territory Quake Wars (etqw) [[Separate Query Port](#separate-query-port)] -* F.E.A.R. (fear) [[Separate Query Port](#separate-query-port)] -* F1 2002 (f12002) [[Separate Query Port](#separate-query-port)] -* F1 Challenge 99-02 (f1c9902) [[Separate Query Port](#separate-query-port)] -* Far Cry (farcry) [[Separate Query Port](#separate-query-port)] -* Far Cry (farcry2) [[Separate Query Port](#separate-query-port)] -* Fortress Forever (fortressforever) -* Flashpoint (flashpoint) [[Separate Query Port](#separate-query-port)] -* Frontlines: Fuel of War (ffow) [[Separate Query Port](#separate-query-port)] -* Garry's Mod (garrysmod) -* Ghost Recon: Advanced Warfighter (graw) [[Separate Query Port](#separate-query-port)] -* Ghost Recon: Advanced Warfighter 2 (graw2) [[Separate Query Port](#separate-query-port)] -* Giants: Citizen Kabuto (giantscitizenkabuto) [[Separate Query Port](#separate-query-port)] -* Global Operations (globaloperations) [[Separate Query Port](#separate-query-port)] -* Gore (gore) [[Separate Query Port](#separate-query-port)] -* Gunman Chronicles (gunmanchronicles) -* Half-Life 1 Deathmatch (hldm) -* Half-Life 2 Deathmatch (hl2dm) -* Halo (halo) -* Halo 2 (halo2) -* Heretic 2 (heretic2) [[Separate Query Port](#separate-query-port)] -* Hexen World (hexenworld) [[Separate Query Port](#separate-query-port)] -* The Hidden: Source (hidden) -* Hidden and Dangerous 2 (had2) [[Separate Query Port](#separate-query-port)] -* Homefront (homefront) -* Homeworld 2 (homeworld2) [[Separate Query Port](#separate-query-port)] -* IGI-2: Covert Strike (igi2) [[Separate Query Port](#separate-query-port)] -* IL-2 Sturmovik (il2) [[Separate Query Port](#separate-query-port)] -* Insurgency (insurgency) -* Iron Storm (ironstorm) [[Separate Query Port](#separate-query-port)] -* James Bond: Nightfire (jamesbondnightfire) [[Separate Query Port](#separate-query-port)] -* Just Cause 2 Multiplayer (jc2mp) -* Killing Floor (killingfloor) [[Separate Query Port](#separate-query-port)] -* Kingpin: Life of Crime (kingpin) [[Separate Query Port](#separate-query-port)] -* KISS Psycho Circus (kisspc) [[Separate Query Port](#separate-query-port)] -* KzMod (kzmod) -* Left 4 Dead (left4dead) -* Left 4 Dead 2 (left4dead2) -* Mafia 2 Multiplayer (m2mp) [[Separate Query Port](#separate-query-port)] -* Medal of Honor: Allied Assault (mohaa) [[Separate Query Port](#separate-query-port)] -* Medal of Honor: Pacific Assault (mohpa) [[Separate Query Port](#separate-query-port)] -* Medal of Honor: Airborne (mohab) [[Separate Query Port](#separate-query-port)] -* Medal of Honor: Spearhead (mohsh) [[Separate Query Port](#separate-query-port)] -* Medal of Honor: Breakthrough (mohbt) [[Separate Query Port](#separate-query-port)] -* Medal of Honor 2010 (moh2010) [[Separate Query Port](#separate-query-port)] -* Medal of Honor: Warfighter (mohwf) [[Separate Query Port](#separate-query-port)] -* Minecraft (minecraft) [[Additional Notes](#minecraft)] -* Minecraft (minecraftping) [[Additional Notes](#minecraft)] -* Monday Night Combat (mnc) [[Separate Query Port](#separate-query-port)] -* Multi Theft Auto: Vice City (mtavc) [[Separate Query Port](#separate-query-port)] -* Multi Theft Auto: San Andreas (mtasa) [[Separate Query Port](#separate-query-port)] -* Mumble (mumble) [[Separate Query Port](#separate-query-port)] [[Additional Notes](#mumble)] -* Mumble (mumbleping) [[Additional Notes](#mumble)] -* Mutant Factions (mutantfactions) -* Nascar Thunder 2004 (nascarthunder2004) [[Separate Query Port](#separate-query-port)] -* netPanzer (netpanzer) -* No More Room in Hell (nmrih) -* Natural Selection (ns) -* Natural Selection 2 (ns2) [[Separate Query Port](#separate-query-port)] -* Need for Speed: Hot Pursuit 2 (nfshp2) [[Separate Query Port](#separate-query-port)] -* Nerf Arena Blast (nab) [[Separate Query Port](#separate-query-port)] -* Neverwinter Nights (nwn) [[Separate Query Port](#separate-query-port)] -* Neverwinter Nights 2 (nwn2) [[Separate Query Port](#separate-query-port)] -* Nexuiz (nexuiz) [[Separate Query Port](#separate-query-port)] -* Nitro Family (nitrofamily) [[Separate Query Port](#separate-query-port)] -* No One Lives Forever (nolf) [[Separate Query Port](#separate-query-port)] -* No One Lives Forever 2 (nolf2) [[Separate Query Port](#separate-query-port)] -* Nuclear Dawn (nucleardawn) -* OpenArena (openarena) [[Separate Query Port](#separate-query-port)] -* Operation Flashpoint (operationflashpoint) [[Separate Query Port](#separate-query-port)] -* Painkiller (painkiller) [[Separate Query Port](#separate-query-port)] -* Postal 2 (postal2) [[Separate Query Port](#separate-query-port)] -* Prey (prey) [[Separate Query Port](#separate-query-port)] -* Quake 1: QuakeWorld (quake1) -* Quake 2 (quake2) -* Quake 3: Arena (quake3) -* Quake 4 (quake4) -* Rag Doll Kung Fu (ragdollkungfu) -* Rainbow Six (r6) [[Separate Query Port](#separate-query-port)] -* Rainbow Six 2: Rogue Spear (r6roguespear) [[Separate Query Port](#separate-query-port)] -* Rainbow Six 3: Raven Shield (r6ravenshield) [[Separate Query Port](#separate-query-port)] -* RalliSport Challenge (rallisportchallenge) [[Separate Query Port](#separate-query-port)] -* Rally Masters (rallymasters) [[Separate Query Port](#separate-query-port)] -* Red Orchestra (redorchestra) [[Separate Query Port](#separate-query-port)] -* Red Orchestra: Ostfront 41-45 (redorchestraost) [[Separate Query Port](#separate-query-port)] -* Red Orchestra 2 (redorchestra2) [[Separate Query Port](#separate-query-port)] -* Redline (redline) [[Separate Query Port](#separate-query-port)] -* Return to Castle Wolfenstein (rtcw) [[Separate Query Port](#separate-query-port)] -* Ricochet (ricochet) -* Rise of Nations (riseofnations) [[Separate Query Port](#separate-query-port)] -* Rune (rune) [[Separate Query Port](#separate-query-port)] -* Rust (rust) [[Separate Query Port](#separate-query-port)] -* San Andreas Multiplayer (samp) -* Serious Sam (ss) [[Separate Query Port](#separate-query-port)] -* Serious Sam 2 (ss2) -* Shattered Horizon (shatteredhorizon) -* The Ship (ship) -* Shogo (shogo) [[Separate Query Port](#separate-query-port)] -* Shootmania (shootmania) [[Additional Notes](#nadeo-shootmania--trackmania--etc)] -* SiN (sin) [[Separate Query Port](#separate-query-port)] -* SiN Episodes (sinep) -* Soldat (soldat) [[Separate Query Port](#separate-query-port)] -* Soldier of Fortune (sof) [[Separate Query Port](#separate-query-port)] -* Soldier of Fortune 2 (sof2) [[Separate Query Port](#separate-query-port)] -* S.T.A.L.K.E.R. (stalker) [[Separate Query Port](#separate-query-port)] -* Star Trek: Bridge Commander (stbc) [[Separate Query Port](#separate-query-port)] -* Star Trek: Voyager - Elite Force (stvef) [[Separate Query Port](#separate-query-port)] -* Star Trek: Voyager - Elite Force 2 (stvef2) [[Separate Query Port](#separate-query-port)] -* Star Wars: Battlefront (swbf) [[Separate Query Port](#separate-query-port)] -* Star Wars: Battlefront 2 (swbf2) [[Separate Query Port](#separate-query-port)] -* Star Wars: Jedi Knight (swjk) [[Separate Query Port](#separate-query-port)] -* Star Wars: Jedi Knight 2 (swjk2) [[Separate Query Port](#separate-query-port)] -* Star Wars: Republic Commando (swrc) [[Separate Query Port](#separate-query-port)] -* Starbound (starbound) -* Suicide Survival (suicidesurvival) -* SWAT 4 (swat4) [[Separate Query Port](#separate-query-port)] -* Sven Coop (svencoop) -* Synergy (synergy) -* Tactical Ops (tacticalops) [[Separate Query Port](#separate-query-port)] -* Team Factor (teamfactor) [[Separate Query Port](#separate-query-port)] -* Team Fortress Classic (tfc) -* Team Fortress 2 (tf2) -* Teamspeak 2 (teamspeak2) [[Separate Query Port](#separate-query-port)] -* Teamspeak 3 (teamspeak3) [[Separate Query Port](#separate-query-port)] -* Terminus (terminus) [[Separate Query Port](#separate-query-port)] -* Terraria (terraria) [[Separate Query Port](#separate-query-port)] [[Additional Notes](#terraria)] -* Tony Hawk's Pro Skater 3 (thps3) [[Separate Query Port](#separate-query-port)] -* Tony Hawk's Pro Skater 4 (thps4) [[Separate Query Port](#separate-query-port)] -* Tony Hawk's Underground 2 (thu2) [[Separate Query Port](#separate-query-port)] -* Trackmania 2 (trackmania2) [[Additional Notes](#nadeo-shootmania--trackmania--etc)] -* Trackmania Forever (trackmaniaforever) [[Additional Notes](#nadeo-shootmania--trackmania--etc)] -* Tremulous (tremulous) [[Separate Query Port](#separate-query-port)] -* Tribes: Vengeance (tribesvengeance) [[Separate Query Port](#separate-query-port)] -* Tron 2.0 (tron20) [[Separate Query Port](#separate-query-port)] -* Turok 2 (turok2) [[Separate Query Port](#separate-query-port)] -* Universal Combat (universalcombat) [[Separate Query Port](#separate-query-port)] -* Unreal (unreal) [[Separate Query Port](#separate-query-port)] -* Unreal Tournament (ut) [[Separate Query Port](#separate-query-port)] -* Unreal Tournament 2003 (ut2003) [[Separate Query Port](#separate-query-port)] -* Unreal Tournament 2004 (ut2004) [[Separate Query Port](#separate-query-port)] -* Unreal Tournament 3 (ut3) [[Separate Query Port](#separate-query-port)] -* Urban Terror (urbanterror) [[Separate Query Port](#separate-query-port)] -* V8 Supercar Challenge (v8supercar) [[Separate Query Port](#separate-query-port)] -* Ventrilo (ventrilo) -* Vietcong (vietcong) [[Separate Query Port](#separate-query-port)] -* Vietcong 2 (vietcong2) [[Separate Query Port](#separate-query-port)] -* Warsow (warsow) -* Wheel of Time (wheeloftime) [[Separate Query Port](#separate-query-port)] -* Wolfenstein 2009 (wolfenstein2009) [[Separate Query Port](#separate-query-port)] -* Wolfenstein: Enemy Territory (wolfensteinet) [[Separate Query Port](#separate-query-port)] -* Xpand Rally (xpandrally) [[Separate Query Port](#separate-query-port)] -* Zombie Master (zombiemaster) -* Zombie Panic: Source (zps) - - - -###Not supported (yet) - -* rFactor Engine (rfactor): - * rFactor - * Arca Sim Racing -* Cube Engine (cube): - * Cube 1 - * Assault Cube - * Cube 2: Sauerbraten - * Blood Frontier -* BFRIS -* Call of Duty: Black Ops 1 and 2 (no documentation, may require rcon) -* Counter-Strike 2D -* Freelancer -* Ghost Recon -* GTR2 -* Haze -* Hexen 2 -* OpenTTD -* Plain Sight -* Red Faction -* Savage: Battle for Newerth -* Savage 2: A Tortured Soul -* Sum of All Fears -* Teeworlds -* Tribes 1: Starsiege -* Tribes 2 -* Vice City Multiplayer -* World in Conflict - -> Want support for one of these games? Please open an issue to show your interest! -> __Know how to code?__ Protocols for most of the games above are documented -> in the /reference folder, ready for you to develop into GameDig! - - - -> Don't see your game listed here? -> -> First, let us know so we can fix it. Then, you can try using some common query -> protocols directly by using one of these server types: -> * protocol-ase -> * protocol-battlefield -> * protocol-doom3 -> * protocol-gamespy1 -> * protocol-gamespy2 -> * protocol-gamespy3 -> * protocol-nadeo -> * protocol-quake2 -> * protocol-quake3 -> * protocol-unreal2 -> * protocol-valve - -Games with Additional Notes ---- - -### DayZ -DayZ uses a query port that is separate from its main game port. The query port is usually -the game port PLUS 24714 or 24715. You may need to pass this port in as the 'port_query' request option. - -### Minecraft -Some minecraft servers may not respond to a typical status query. If this is the case, try using the -'minecraftping' server type instead, which uses a less accurate but more reliable solution. - -### Mumble -For full query results from Mumble, you must be running the -[GTmurmur plugin](http://www.gametracker.com/downloads/gtmurmurplugin.php). -If you do not wish to run the plugin, or do not require details such as channel and user lists, -you can use the 'mumbleping' server type instead, which uses a less accurate but more reliable solution - -### Nadeo (ShootMania / TrackMania / etc) -The server must have xmlrpc enabled, and you must pass the xmlrpc port to GameDig, not the connection port. -You must have a user account on the server with access level User or higher. -Pass the login into to GameDig with the additional options: login, password - -### Terraria -Requires tshock server mod, and a REST user token, which can be passed to GameDig with the -additional option: token - -### Separate Query Port -Games with this note use a query port which is usually not the same as the game's connection port. -Usually, no action will be required from you. The 'port' option you pass GameDig should be the game's -connection port. GameDig will attempt to calculate the query port automatically. If the query still fails, -you may need to pass the 'port_query' option to GameDig as well, indicating the separate query port. - -Usage from Command Line ---- - -Want to integrate server queries from a batch script or other programming language? -You'll still need npm to install gamedig: -```shell -npm install gamedig -g -``` - -After installing gamedig globally, you can call gamedig via the command line -using the same parameters mentioned in the API above: -```shell -gamedig --type minecraft --host mc.example.com --port 11234 -``` - -The output of the command will be in JSON format. +node-GameDig - Game Server Query Library +--- +node-GameDig is a game server query library, capable of querying for the status of +nearly any game or voice server. If a server makes its status publically available, +GameDig can fetch it for you. + +GameDig is available as a node.js module, as well as a +[command line executable](#usage-from-command-line). + +Usage from Node.js +--- + +```shell +npm install gamedig +``` + +```javascript +var Gamedig = require('gamedig'); +Gamedig.query( + { + type: 'minecraft', + host: 'mc.example.com' + }, + function(state) { + if(state.error) console.log("Server is offline"); + else console.log(state); + } +); +``` + +> Is NPM out of date? If you're feeling lucky, you can install the latest code with +> ```shell +> npm install "git+https://github.com/sonicsnes/node-gamedig.git" +> ``` + +### Input Parameters + +* **type**: One of the game IDs listed in the game list below +* **host** +* **port**: (optional) Uses the protocol default if not set +* **notes**: (optional) Passed through to output + +###Callback Function + +The callback function is "guaranteed" to be called exactly once. + +If an error occurs, the returned object will contain an "error" key, indicating the issue. +If the error key exists, it should be assumed that the game server is offline or unreachable. + +Otherwise, the returned object is guaranteed to contain the following keys: + +**Stable, always present:** + +* **name** +* **map** +* **password**: Boolean +* **maxplayers** +* **players**: (array of objects) Each object **may** contain name, ping, score, team, address +* **bots**: Same schema as players +* **notes**: Passed through from the input + +**Unstable, not guaranteed:** + +* **raw**: Contains all information received from the server +* **query**: Details about the query performed + +It can usually be assumed that the number of players online is equal to the length of the players array. +Some servers may return an additional player count number, which may be present in the unstable raw object. + +Games List +--- + +###Supported + + +* Age of Chivalry (ageofchivalry) +* Age of Empires 2 (aoe2) [[Separate Query Port](#separate-query-port)] +* Alien Arena (alienarena) [[Separate Query Port](#separate-query-port)] +* Alien Swarm (alienswarm) +* Aliens vs Predator 2 (avp2) +* Aliens vs Predator 2010 (avp2010) +* America's Army (americasarmy) [[Separate Query Port](#separate-query-port)] +* America's Army 2 (americasarmy2) [[Separate Query Port](#separate-query-port)] +* America's Army 3 (americasarmy3) [[Separate Query Port](#separate-query-port)] +* America's Army: Proving Grounds (americasarmypg) [[Separate Query Port](#separate-query-port)] +* ArmA Armed Assault 1 (arma) +* ArmA Armed Assault 2 (arma2) [[Separate Query Port](#separate-query-port)] +* ArmA Armed Assault 3 (arma3) [[Separate Query Port](#separate-query-port)] +* Armagetron (armagetron) +* Baldur's Gate (baldursgate) [[Separate Query Port](#separate-query-port)] +* Battlefield 1942 (bf1942) [[Separate Query Port](#separate-query-port)] +* Battlefield Vietnam (bfv) [[Separate Query Port](#separate-query-port)] +* Battlefield 2 (bf2) [[Separate Query Port](#separate-query-port)] +* Battlefield 2142 (bf2142) [[Separate Query Port](#separate-query-port)] +* Battlefield: Bad Company 2 (bfbc2) [[Separate Query Port](#separate-query-port)] +* Battlefield 3 (bf3) [[Separate Query Port](#separate-query-port)] +* Battlefield 4 (bf4) [[Separate Query Port](#separate-query-port)] +* Breach (breach) +* Breed (breed) +* Brink (brink) [[Separate Query Port](#separate-query-port)] +* Build and Shoot (buildandshoot) [[Separate Query Port](#separate-query-port)] +* Call of Duty (cod) +* Call of Duty: United Offensive (coduo) +* Call of Duty 2 (cod2) +* Call of Duty 3 (cod3) +* Call of Duty 4: Modern Warfare (cod4) +* Call of Duty: World at War (codwaw) +* Call of Duty: Modern Warfare 2 (codmw2) +* Call of Duty: Modern Warfare 3 (codmw3) [[Separate Query Port](#separate-query-port)] +* Call of Juarez (callofjuarez) [[Separate Query Port](#separate-query-port)] +* Chaser (chaser) [[Separate Query Port](#separate-query-port)] +* Chrome (chrome) [[Separate Query Port](#separate-query-port)] +* Codename Eagle (codenameeagle) [[Separate Query Port](#separate-query-port)] +* Commandos 3: Destination Berlin (commandos3) [[Separate Query Port](#separate-query-port)] +* Command and Conquer: Renegade (cacrenegade) [[Separate Query Port](#separate-query-port)] +* Contact J.A.C.K. (contactjack) [[Separate Query Port](#separate-query-port)] +* Counter-Strike 1.6 (cs16) +* Counter-Strike: Condition Zero (cscz) +* Counter-Strike: Source (css) +* Counter-Strike: Global Offensive (csgo) +* Cross Racing Championship (crossracing) [[Separate Query Port](#separate-query-port)] +* Crysis (crysis) +* Crysis Wars (crysiswars) +* Crysis 2 (crysis2) +* Daikatana (daikatana) [[Separate Query Port](#separate-query-port)] +* Dark Messiah of Might and Magic (dmomam) +* Darkest Hour (darkesthour) [[Separate Query Port](#separate-query-port)] +* DayZ (dayz) [[Separate Query Port](#separate-query-port)] [[Additional Notes](#dayz)] +* Deadly Dozen: Pacific Theater (deadlydozenpt) [[Separate Query Port](#separate-query-port)] +* Deer Hunter 2005 (dh2005) [[Separate Query Port](#separate-query-port)] +* Descent 3 (descent3) [[Separate Query Port](#separate-query-port)] +* Deus Ex (deusex) [[Separate Query Port](#separate-query-port)] +* Devastation (devastation) [[Separate Query Port](#separate-query-port)] +* Dino D-Day (dinodday) +* Dirt Track Racing 2 (dirttrackracing2) [[Separate Query Port](#separate-query-port)] +* Day of Defeat (dod) +* Day of Defeat: Source (dods) +* Doom 3 (doom3) +* DOTA 2 (dota2) +* Drakan (drakan) [[Separate Query Port](#separate-query-port)] +* Enemy Territory Quake Wars (etqw) [[Separate Query Port](#separate-query-port)] +* F.E.A.R. (fear) [[Separate Query Port](#separate-query-port)] +* F1 2002 (f12002) [[Separate Query Port](#separate-query-port)] +* F1 Challenge 99-02 (f1c9902) [[Separate Query Port](#separate-query-port)] +* Far Cry (farcry) [[Separate Query Port](#separate-query-port)] +* Far Cry (farcry2) [[Separate Query Port](#separate-query-port)] +* Fortress Forever (fortressforever) +* Flashpoint (flashpoint) [[Separate Query Port](#separate-query-port)] +* Frontlines: Fuel of War (ffow) [[Separate Query Port](#separate-query-port)] +* Garry's Mod (garrysmod) +* Ghost Recon: Advanced Warfighter (graw) [[Separate Query Port](#separate-query-port)] +* Ghost Recon: Advanced Warfighter 2 (graw2) [[Separate Query Port](#separate-query-port)] +* Giants: Citizen Kabuto (giantscitizenkabuto) [[Separate Query Port](#separate-query-port)] +* Global Operations (globaloperations) [[Separate Query Port](#separate-query-port)] +* Gore (gore) [[Separate Query Port](#separate-query-port)] +* Gunman Chronicles (gunmanchronicles) +* Half-Life 1 Deathmatch (hldm) +* Half-Life 2 Deathmatch (hl2dm) +* Halo (halo) +* Halo 2 (halo2) +* Heretic 2 (heretic2) [[Separate Query Port](#separate-query-port)] +* Hexen World (hexenworld) [[Separate Query Port](#separate-query-port)] +* The Hidden: Source (hidden) +* Hidden and Dangerous 2 (had2) [[Separate Query Port](#separate-query-port)] +* Homefront (homefront) +* Homeworld 2 (homeworld2) [[Separate Query Port](#separate-query-port)] +* IGI-2: Covert Strike (igi2) [[Separate Query Port](#separate-query-port)] +* IL-2 Sturmovik (il2) [[Separate Query Port](#separate-query-port)] +* Insurgency (insurgency) +* Iron Storm (ironstorm) [[Separate Query Port](#separate-query-port)] +* James Bond: Nightfire (jamesbondnightfire) [[Separate Query Port](#separate-query-port)] +* Just Cause 2 Multiplayer (jc2mp) +* Killing Floor (killingfloor) [[Separate Query Port](#separate-query-port)] +* Kingpin: Life of Crime (kingpin) [[Separate Query Port](#separate-query-port)] +* KISS Psycho Circus (kisspc) [[Separate Query Port](#separate-query-port)] +* KzMod (kzmod) +* Left 4 Dead (left4dead) +* Left 4 Dead 2 (left4dead2) +* Mafia 2 Multiplayer (m2mp) [[Separate Query Port](#separate-query-port)] +* Medal of Honor: Allied Assault (mohaa) [[Separate Query Port](#separate-query-port)] +* Medal of Honor: Pacific Assault (mohpa) [[Separate Query Port](#separate-query-port)] +* Medal of Honor: Airborne (mohab) [[Separate Query Port](#separate-query-port)] +* Medal of Honor: Spearhead (mohsh) [[Separate Query Port](#separate-query-port)] +* Medal of Honor: Breakthrough (mohbt) [[Separate Query Port](#separate-query-port)] +* Medal of Honor 2010 (moh2010) [[Separate Query Port](#separate-query-port)] +* Medal of Honor: Warfighter (mohwf) [[Separate Query Port](#separate-query-port)] +* Minecraft (minecraft) [[Additional Notes](#minecraft)] +* Minecraft (minecraftping) [[Additional Notes](#minecraft)] +* Monday Night Combat (mnc) [[Separate Query Port](#separate-query-port)] +* Multi Theft Auto: Vice City (mtavc) [[Separate Query Port](#separate-query-port)] +* Multi Theft Auto: San Andreas (mtasa) [[Separate Query Port](#separate-query-port)] +* Mumble (mumble) [[Separate Query Port](#separate-query-port)] [[Additional Notes](#mumble)] +* Mumble (mumbleping) [[Additional Notes](#mumble)] +* Mutant Factions (mutantfactions) +* Nascar Thunder 2004 (nascarthunder2004) [[Separate Query Port](#separate-query-port)] +* netPanzer (netpanzer) +* No More Room in Hell (nmrih) +* Natural Selection (ns) +* Natural Selection 2 (ns2) [[Separate Query Port](#separate-query-port)] +* Need for Speed: Hot Pursuit 2 (nfshp2) [[Separate Query Port](#separate-query-port)] +* Nerf Arena Blast (nab) [[Separate Query Port](#separate-query-port)] +* Neverwinter Nights (nwn) [[Separate Query Port](#separate-query-port)] +* Neverwinter Nights 2 (nwn2) [[Separate Query Port](#separate-query-port)] +* Nexuiz (nexuiz) [[Separate Query Port](#separate-query-port)] +* Nitro Family (nitrofamily) [[Separate Query Port](#separate-query-port)] +* No One Lives Forever (nolf) [[Separate Query Port](#separate-query-port)] +* No One Lives Forever 2 (nolf2) [[Separate Query Port](#separate-query-port)] +* Nuclear Dawn (nucleardawn) +* OpenArena (openarena) [[Separate Query Port](#separate-query-port)] +* Operation Flashpoint (operationflashpoint) [[Separate Query Port](#separate-query-port)] +* Painkiller (painkiller) [[Separate Query Port](#separate-query-port)] +* Postal 2 (postal2) [[Separate Query Port](#separate-query-port)] +* Prey (prey) [[Separate Query Port](#separate-query-port)] +* Quake 1: QuakeWorld (quake1) +* Quake 2 (quake2) +* Quake 3: Arena (quake3) +* Quake 4 (quake4) +* Rag Doll Kung Fu (ragdollkungfu) +* Rainbow Six (r6) [[Separate Query Port](#separate-query-port)] +* Rainbow Six 2: Rogue Spear (r6roguespear) [[Separate Query Port](#separate-query-port)] +* Rainbow Six 3: Raven Shield (r6ravenshield) [[Separate Query Port](#separate-query-port)] +* RalliSport Challenge (rallisportchallenge) [[Separate Query Port](#separate-query-port)] +* Rally Masters (rallymasters) [[Separate Query Port](#separate-query-port)] +* Red Orchestra (redorchestra) [[Separate Query Port](#separate-query-port)] +* Red Orchestra: Ostfront 41-45 (redorchestraost) [[Separate Query Port](#separate-query-port)] +* Red Orchestra 2 (redorchestra2) [[Separate Query Port](#separate-query-port)] +* Redline (redline) [[Separate Query Port](#separate-query-port)] +* Return to Castle Wolfenstein (rtcw) [[Separate Query Port](#separate-query-port)] +* Ricochet (ricochet) +* Rise of Nations (riseofnations) [[Separate Query Port](#separate-query-port)] +* Rune (rune) [[Separate Query Port](#separate-query-port)] +* Rust (rust) [[Separate Query Port](#separate-query-port)] +* San Andreas Multiplayer (samp) +* Serious Sam (ss) [[Separate Query Port](#separate-query-port)] +* Serious Sam 2 (ss2) +* Shattered Horizon (shatteredhorizon) +* The Ship (ship) +* Shogo (shogo) [[Separate Query Port](#separate-query-port)] +* Shootmania (shootmania) [[Additional Notes](#nadeo-shootmania--trackmania--etc)] +* SiN (sin) [[Separate Query Port](#separate-query-port)] +* SiN Episodes (sinep) +* Soldat (soldat) [[Separate Query Port](#separate-query-port)] +* Soldier of Fortune (sof) [[Separate Query Port](#separate-query-port)] +* Soldier of Fortune 2 (sof2) [[Separate Query Port](#separate-query-port)] +* S.T.A.L.K.E.R. (stalker) [[Separate Query Port](#separate-query-port)] +* Star Trek: Bridge Commander (stbc) [[Separate Query Port](#separate-query-port)] +* Star Trek: Voyager - Elite Force (stvef) [[Separate Query Port](#separate-query-port)] +* Star Trek: Voyager - Elite Force 2 (stvef2) [[Separate Query Port](#separate-query-port)] +* Star Wars: Battlefront (swbf) [[Separate Query Port](#separate-query-port)] +* Star Wars: Battlefront 2 (swbf2) [[Separate Query Port](#separate-query-port)] +* Star Wars: Jedi Knight (swjk) [[Separate Query Port](#separate-query-port)] +* Star Wars: Jedi Knight 2 (swjk2) [[Separate Query Port](#separate-query-port)] +* Star Wars: Republic Commando (swrc) [[Separate Query Port](#separate-query-port)] +* Starbound (starbound) +* Suicide Survival (suicidesurvival) +* SWAT 4 (swat4) [[Separate Query Port](#separate-query-port)] +* Sven Coop (svencoop) +* Synergy (synergy) +* Tactical Ops (tacticalops) [[Separate Query Port](#separate-query-port)] +* Team Factor (teamfactor) [[Separate Query Port](#separate-query-port)] +* Team Fortress Classic (tfc) +* Team Fortress 2 (tf2) +* Teamspeak 2 (teamspeak2) [[Separate Query Port](#separate-query-port)] +* Teamspeak 3 (teamspeak3) [[Separate Query Port](#separate-query-port)] +* Terminus (terminus) [[Separate Query Port](#separate-query-port)] +* Terraria (terraria) [[Separate Query Port](#separate-query-port)] [[Additional Notes](#terraria)] +* Tony Hawk's Pro Skater 3 (thps3) [[Separate Query Port](#separate-query-port)] +* Tony Hawk's Pro Skater 4 (thps4) [[Separate Query Port](#separate-query-port)] +* Tony Hawk's Underground 2 (thu2) [[Separate Query Port](#separate-query-port)] +* Trackmania 2 (trackmania2) [[Additional Notes](#nadeo-shootmania--trackmania--etc)] +* Trackmania Forever (trackmaniaforever) [[Additional Notes](#nadeo-shootmania--trackmania--etc)] +* Tremulous (tremulous) [[Separate Query Port](#separate-query-port)] +* Tribes: Vengeance (tribesvengeance) [[Separate Query Port](#separate-query-port)] +* Tron 2.0 (tron20) [[Separate Query Port](#separate-query-port)] +* Turok 2 (turok2) [[Separate Query Port](#separate-query-port)] +* Universal Combat (universalcombat) [[Separate Query Port](#separate-query-port)] +* Unreal (unreal) [[Separate Query Port](#separate-query-port)] +* Unreal Tournament (ut) [[Separate Query Port](#separate-query-port)] +* Unreal Tournament 2003 (ut2003) [[Separate Query Port](#separate-query-port)] +* Unreal Tournament 2004 (ut2004) [[Separate Query Port](#separate-query-port)] +* Unreal Tournament 3 (ut3) [[Separate Query Port](#separate-query-port)] +* Urban Terror (urbanterror) [[Separate Query Port](#separate-query-port)] +* V8 Supercar Challenge (v8supercar) [[Separate Query Port](#separate-query-port)] +* Ventrilo (ventrilo) +* Vietcong (vietcong) [[Separate Query Port](#separate-query-port)] +* Vietcong 2 (vietcong2) [[Separate Query Port](#separate-query-port)] +* Warsow (warsow) +* Wheel of Time (wheeloftime) [[Separate Query Port](#separate-query-port)] +* Wolfenstein 2009 (wolfenstein2009) [[Separate Query Port](#separate-query-port)] +* Wolfenstein: Enemy Territory (wolfensteinet) [[Separate Query Port](#separate-query-port)] +* Xpand Rally (xpandrally) [[Separate Query Port](#separate-query-port)] +* Zombie Master (zombiemaster) +* Zombie Panic: Source (zps) + + + +###Not supported (yet) + +* rFactor Engine (rfactor): + * rFactor + * Arca Sim Racing +* Cube Engine (cube): + * Cube 1 + * Assault Cube + * Cube 2: Sauerbraten + * Blood Frontier +* BFRIS +* Call of Duty: Black Ops 1 and 2 (no documentation, may require rcon) +* Counter-Strike 2D +* Freelancer +* Ghost Recon +* GTR2 +* Haze +* Hexen 2 +* OpenTTD +* Plain Sight +* Red Faction +* Savage: Battle for Newerth +* Savage 2: A Tortured Soul +* Sum of All Fears +* Teeworlds +* Tribes 1: Starsiege +* Tribes 2 +* Vice City Multiplayer +* World in Conflict + +> Want support for one of these games? Please open an issue to show your interest! +> __Know how to code?__ Protocols for most of the games above are documented +> in the /reference folder, ready for you to develop into GameDig! + + + +> Don't see your game listed here? +> +> First, let us know so we can fix it. Then, you can try using some common query +> protocols directly by using one of these server types: +> * protocol-ase +> * protocol-battlefield +> * protocol-doom3 +> * protocol-gamespy1 +> * protocol-gamespy2 +> * protocol-gamespy3 +> * protocol-nadeo +> * protocol-quake2 +> * protocol-quake3 +> * protocol-unreal2 +> * protocol-valve + +Games with Additional Notes +--- + +### DayZ +DayZ uses a query port that is separate from its main game port. The query port is usually +the game port PLUS 24714 or 24715. You may need to pass this port in as the 'port_query' request option. + +### Minecraft +Some minecraft servers may not respond to a typical status query. If this is the case, try using the +'minecraftping' server type instead, which uses a less accurate but more reliable solution. + +### Mumble +For full query results from Mumble, you must be running the +[GTmurmur plugin](http://www.gametracker.com/downloads/gtmurmurplugin.php). +If you do not wish to run the plugin, or do not require details such as channel and user lists, +you can use the 'mumbleping' server type instead, which uses a less accurate but more reliable solution + +### Nadeo (ShootMania / TrackMania / etc) +The server must have xmlrpc enabled, and you must pass the xmlrpc port to GameDig, not the connection port. +You must have a user account on the server with access level User or higher. +Pass the login into to GameDig with the additional options: login, password + +### Terraria +Requires tshock server mod, and a REST user token, which can be passed to GameDig with the +additional option: token + +### Separate Query Port +Games with this note use a query port which is usually not the same as the game's connection port. +Usually, no action will be required from you. The 'port' option you pass GameDig should be the game's +connection port. GameDig will attempt to calculate the query port automatically. If the query still fails, +you may need to pass the 'port_query' option to GameDig as well, indicating the separate query port. + +Usage from Command Line +--- + +Want to integrate server queries from a batch script or other programming language? +You'll still need npm to install gamedig: +```shell +npm install gamedig -g +``` + +After installing gamedig globally, you can call gamedig via the command line +using the same parameters mentioned in the API above: +```shell +gamedig --type minecraft --host mc.example.com --port 11234 +``` + +The output of the command will be in JSON format. diff --git a/bin/gamedig.js b/bin/gamedig.js index 37d9a1d..87c3ff6 100644 --- a/bin/gamedig.js +++ b/bin/gamedig.js @@ -1,34 +1,34 @@ -#!/usr/bin/env node - -var argv = require('optimist').argv; - -var debug = argv.debug; -delete argv.debug; -var outputFormat = argv.output; -delete argv.output; - -var options = {}; -for(var key in argv) { - var value = argv[key]; - if( - key == '_' - || key.charAt(0) == '$' - || (typeof value != 'string' && typeof value != 'number') - ) - continue; - options[key] = value; -} - -var Gamedig = require('../lib/index'); -if(debug) Gamedig.debug = true; -Gamedig.isCommandLine = true; -Gamedig.query( - options, - function(state) { - if(outputFormat == 'pretty') { - console.log(JSON.stringify(state,null,' ')); - } else { - console.log(JSON.stringify(state)); - } - } -); +#!/usr/bin/env node + +var argv = require('optimist').argv; + +var debug = argv.debug; +delete argv.debug; +var outputFormat = argv.output; +delete argv.output; + +var options = {}; +for(var key in argv) { + var value = argv[key]; + if( + key == '_' + || key.charAt(0) == '$' + || (typeof value != 'string' && typeof value != 'number') + ) + continue; + options[key] = value; +} + +var Gamedig = require('../lib/index'); +if(debug) Gamedig.debug = true; +Gamedig.isCommandLine = true; +Gamedig.query( + options, + function(state) { + if(outputFormat == 'pretty') { + console.log(JSON.stringify(state,null,' ')); + } else { + console.log(JSON.stringify(state)); + } + } +); diff --git a/bin/genreadme.js b/bin/genreadme.js index 81cee99..77832f0 100644 --- a/bin/genreadme.js +++ b/bin/genreadme.js @@ -1,19 +1,19 @@ -#!/usr/bin/env node - -var fs = require('fs'); - -var TypeResolver = require('../lib/typeresolver'); -var generated = TypeResolver.printReadme(); - -var readmeFilename = __dirname+'/../README.md'; -var readme = fs.readFileSync(readmeFilename, {encoding:'utf8'}); - -var marker_top = ''; -var marker_bottom = ''; - -var start = readme.indexOf(marker_top); -start += marker_top.length; -var end = readme.indexOf(marker_bottom); - -var updated = readme.substr(0,start)+"\n\n"+generated+"\n"+readme.substr(end); -fs.writeFileSync(readmeFilename, updated); +#!/usr/bin/env node + +var fs = require('fs'); + +var TypeResolver = require('../lib/typeresolver'); +var generated = TypeResolver.printReadme(); + +var readmeFilename = __dirname+'/../README.md'; +var readme = fs.readFileSync(readmeFilename, {encoding:'utf8'}); + +var marker_top = ''; +var marker_bottom = ''; + +var start = readme.indexOf(marker_top); +start += marker_top.length; +var end = readme.indexOf(marker_bottom); + +var updated = readme.substr(0,start)+"\n\n"+generated+"\n"+readme.substr(end); +fs.writeFileSync(readmeFilename, updated); diff --git a/games.txt b/games.txt index c1dc43e..26fabe0 100644 --- a/games.txt +++ b/games.txt @@ -1,273 +1,273 @@ -# id | pretty | protocol | options | parameters - -#### TODO: -# cube1|Cube 1|cube|port=28786,port_query_offset=1 -# assaultcube|Assault Cube|cube|port_query=28764 -# cube2|Cube 2: Sauerbraten|cube|port=28785,port_query_offset=1 -# bloodfrontier|Blood Frontier|cube - -# arcasimracing|Arca Sim Racing|rfactor|port=34397,port_query_offset=-100 -# rfactor|rFactor|rfactor|port=34397,port_query_offset=-100 - -# bfris|BFRIS|bfris|port=44001 -# cs2d|Counter-Strike: 2D|cs2d|port_query=36963 -# freelancer|Freelancer|freelancer|port_query=2302 -# gr|Ghost Recon|ghostrecon|port=2346,port_query_offset=2 -# gtr2|GTR2|gtr2|port=34297,port_query_offset=1 -# haze|Haze|haze -# hexen2|Hexen 2|hexen2|port_query=26900 -# openttd|OpenTTD|openttd|port=3979 -# plainsight|Plain Sight|plainsight -# redfaction|Red Faction|redfaction|port_query=7755 -# savage|Savage|savage|port_query=11235 -# savage2|Savage 2|savage2|port_query=11235 -# teeworlds|Teeworlds|teeworlds|port=8303 -# tribes|Tribes 1: Starsiege|tribes|port_query=28001 -# tribes2|Tribes 2|tribes2|port_query=28000 -# vcmp|Vice City Multiplayer|vcmp -# worldinconflict|World in Conflict|worldinconflict - - - -ageofchivalry|Age of Chivalry|valve -aoe2|Age of Empires 2|ase|port_query=27224 -alienarena|Alien Arena|quake2|port_query=27910 -alienswarm|Alien Swarm|valve -avp2|Aliens vs Predator 2|gamespy1|port=27888 -# avp2010 doesn't really... have a default port or query port -# both port and port_query should be specified when used -avp2010|Aliens vs Predator 2010|valve - -americasarmy|America's Army|americasarmy|port=1716,port_query_offset=1 -americasarmy2|America's Army 2|americasarmy|port=1716,port_query_offset=1 -americasarmy3|America's Army 3|valve|port=8777,port_query=27020 -americasarmypg|America's Army: Proving Grounds|valve|port=8777,port_query=27020 - -arma|ArmA Armed Assault 1|gamespy2|port=2302 -arma2|ArmA Armed Assault 2|valve|port=2302,port_query_offset=1 -arma3|ArmA Armed Assault 3|valve|port=2302,port_query_offset=1 - -armagetron|Armagetron|armagetron|port=4534 -baldursgate|Baldur's Gate|gamespy1|port=6073,port_query=1470 - -bf1942|Battlefield 1942|gamespy1|port=14567,port_query=23000 -bfv|Battlefield Vietnam|gamespy2|port=15567,port_query=23000 -bf2|Battlefield 2|gamespy3|port=16567,port_query=29900|noChallenge -bf2142|Battlefield 2142|gamespy3|port=16567,port_query=29900 -bfbc2|Battlefield: Bad Company 2|battlefield|port=19567,port_query=48888|isBadCompany2 -bf3|Battlefield 3|battlefield|port=25200,port_query_offset=22000 -bf4|Battlefield 4|battlefield|port=25200,port_query_offset=22000 - -breach|Breach|valve|port=27016 -breed|Breed|gamespy2|port=7649 -brink|Brink|valve|port_query_offset=1 -buildandshoot|Build and Shoot|buildandshoot|port=32887,port_query=32886 - -cod|Call of Duty|quake3|port=28960 -coduo|Call of Duty: United Offensive|quake3|port=28960 -cod2|Call of Duty 2|quake3|port=28960 -cod3|Call of Duty 3|quake3|port=28960 -cod4|Call of Duty 4: Modern Warfare|quake3|port=28960 -codwaw|Call of Duty: World at War|quake3|port=28960 -codmw2|Call of Duty: Modern Warfare 2|quake3|port=28960 -codmw3|Call of Duty: Modern Warfare 3|valve|port_query_offset=2 - -callofjuarez|Call of Juarez|ase|port_query=26000 -chaser|Chaser|ase|port=3000,port_query_offset=123 -chrome|Chrome|ase|port=27015,port_query_offset=123 -codenameeagle|Codename Eagle|gamespy1|port_query=4711 -commandos3|Commandos 3: Destination Berlin|gamespy1|port_query=6500 -cacrenegade|Command and Conquer: Renegade|gamespy1|port=4848,port_query=25300 -contactjack|Contact J.A.C.K.|gamespy1|port_query=27888 - -cs16|Counter-Strike 1.6|valve -cscz|Counter-Strike: Condition Zero|valve -css|Counter-Strike: Source|valve -csgo|Counter-Strike: Global Offensive|valve||isCsGo - -crossracing|Cross Racing Championship|ase|port=12321,port_query_offset=123 - -crysis|Crysis|gamespy3|port=64087 -crysiswars|Crysis Wars|gamespy3|port=64100 -crysis2|Crysis 2|gamespy3|port=64000 - -daikatana|Daikatana|quake2|port=27982,port_query_offset=10 -dmomam|Dark Messiah of Might and Magic|valve -darkesthour|Darkest Hour|unreal2|port=7757,port_query_offset=1 -dayz|DayZ|valve|port=2302,port_query_offset=24714|doc_notes=dayz -deadlydozenpt|Deadly Dozen: Pacific Theater|gamespy1|port_query=25300 -dh2005|Deer Hunter 2005|gamespy2|port=23459,port_query=34567 -descent3|Descent 3|gamespy1|port=2092,port_query=20142 -deusex|Deus Ex|gamespy2|port=7791,port_query_offset=1 -devastation|Devastation|unreal2|port=7777,port_query_offset=1 -dinodday|Dino D-Day|valve -dirttrackracing2|Dirt Track Racing 2|gamespy1|port=32240,port_query_offset=-100 -dod|Day of Defeat|valve -dods|Day of Defeat: Source|valve -doom3|Doom 3|doom3|port=27666 -dota2|DOTA 2|valve -drakan|Drakan|gamespy1|port=27045,port_query_offset=1 -etqw|Enemy Territory Quake Wars|doom3|port=3074,port_query=27733|isEtqw,hasSpaceBeforeClanTag,hasClanTag,hasTypeFlag -fear|F.E.A.R.|gamespy2|port_query=27888 -f12002|F1 2002|gamespy1|port_query=3297 -f1c9902|F1 Challenge 99-02|gamespy1|port_query=34397 -farcry|Far Cry|ase|port=49001,port_query_offset=123 -farcry2|Far Cry|ase|port_query=14001 -fortressforever|Fortress Forever|valve -flashpoint|Flashpoint|gamespy1|port=2302,port_query_offset=1 -ffow|Frontlines: Fuel of War|ffow|port=5476,port_query_offset=2 -garrysmod|Garry's Mod|valve -graw|Ghost Recon: Advanced Warfighter|gamespy2|port_query=15250 -graw2|Ghost Recon: Advanced Warfighter 2|gamespy2|port_query=16250 -giantscitizenkabuto|Giants: Citizen Kabuto|gamespy1|port_query=8911 -globaloperations|Global Operations|gamespy1|port_query=28672 -gore|Gore|gamespy1|port=27777,port_query_offset=1 -gunmanchronicles|Gunman Chronicles|valve -hldm|Half-Life 1 Deathmatch|valve -hl2dm|Half-Life 2 Deathmatch|valve -halo|Halo|gamespy2|port=2302 -halo2|Halo 2|gamespy2|port=2302 -heretic2|Heretic 2|gamespy1|port_query=28910 -hexenworld|Hexen World|hexenworld|port_query=26950 -hidden|The Hidden: Source|valve -had2|Hidden and Dangerous 2|gamespy1|port=11001,port_query_offset=3 -homefront|Homefront|valve -homeworld2|Homeworld 2|gamespy1|port_query=6500 -igi2|IGI-2: Covert Strike|gamespy1|port_query=26001 -il2|IL-2 Sturmovik|gamespy1|port_query=21000 -insurgency|Insurgency|valve -ironstorm|Iron Storm|gamespy1|port_query=3505 -jamesbondnightfire|James Bond: Nightfire|gamespy1|port_query=6550 -jc2mp|Just Cause 2 Multiplayer|jc2mp|port=7777 -killingfloor|Killing Floor|killingfloor|port=7707,port_query_offset=1 -kingpin|Kingpin: Life of Crime|gamespy1|port=31510,port_query_offset=-10 -kisspc|KISS Psycho Circus|gamespy1|port=7777,port_query_offset=1 -kzmod|KzMod|valve -left4dead|Left 4 Dead|valve -left4dead2|Left 4 Dead 2|valve -m2mp|Mafia 2 Multiplayer|m2mp|port=27016,port_query_offset=1 - -mohaa|Medal of Honor: Allied Assault|gamespy1|port=12203,port_query_offset=97 -mohpa|Medal of Honor: Pacific Assault|gamespy1|port=13203,port_query_offset=97 -mohab|Medal of Honor: Airborne|gamespy1|port=12203,port_query_offset=97 -mohsh|Medal of Honor: Spearhead|gamespy1|port=12203,port_query_offset=97 -mohbt|Medal of Honor: Breakthrough|gamespy1|port=12203,port_query_offset=97 -moh2010|Medal of Honor 2010|battlefield|port=7673,port_query=48888 -mohwf|Medal of Honor: Warfighter|battlefield|port=25200,port_query_offset=22000 - -minecraft|Minecraft|gamespy3|port=25565|maxAttempts=2,srvRecord=_minecraft._tcp,doc_notes=minecraft -minecraftping|Minecraft|minecraftping|port=25565|srvRecord=_minecraft._tcp,doc_notes=minecraft -mnc|Monday Night Combat|valve|port=7777,port_query=27016 -mtavc|Multi Theft Auto: Vice City|ase|port=22003,port_query_offset=123 -mtasa|Multi Theft Auto: San Andreas|ase|port=22003,port_query_offset=123 -mumble|Mumble|mumble|port=64738,port_query=27800|doc_notes=mumble -mumbleping|Mumble|mumbleping|port=64738|doc_notes=mumble -mutantfactions|Mutant Factions|mutantfactions|port=11235 -nascarthunder2004|Nascar Thunder 2004|gamespy2|port_query=13333 -netpanzer|netPanzer|gamespy1|3030 -nmrih|No More Room in Hell|valve -ns|Natural Selection|valve -ns2|Natural Selection 2|valve|port_query_offset=1 -nfshp2|Need for Speed: Hot Pursuit 2|gamespy1|port_query=61220 -nab|Nerf Arena Blast|gamespy1|port=4444,port_query_offset=1 -nwn|Neverwinter Nights|gamespy2|port_query=5121 -nwn2|Neverwinter Nights 2|gamespy2|port=5121,port_query=6500 -nexuiz|Nexuiz|quake3|port_query=26000 -nitrofamily|Nitro Family|gamespy1|port_query=25601 -nolf|No One Lives Forever|gamespy1|port_query=27888 -nolf2|No One Lives Forever 2|gamespy1|port_query=27890 -nucleardawn|Nuclear Dawn|valve -openarena|OpenArena|quake3|port_query=27960 -operationflashpoint|Operation Flashpoint|gamespy1|port=2234,port_query_offset=1 -painkiller|Painkiller|ase|port=3455,port_query_offset=123 -postal2|Postal 2|gamespy1|port=7777,port_query_offset=1 -prey|Prey|doom3|port_query=27719 - -quake1|Quake 1: QuakeWorld|quake1|port=27500 -quake2|Quake 2|quake2|port=27910 -quake3|Quake 3: Arena|quake3|port=27960 -quake4|Quake 4|doom3|port=28004|hasClanTag - -ragdollkungfu|Rag Doll Kung Fu|valve - -r6|Rainbow Six|gamespy1|port_query=2348 -r6roguespear|Rainbow Six 2: Rogue Spear|gamespy1|port_query=2346 -r6ravenshield|Rainbow Six 3: Raven Shield|gamespy1|port=7777,port_query_offset=1000 - -rallisportchallenge|RalliSport Challenge|gamespy1|port_query=17500 -rallymasters|Rally Masters|gamespy1|port_query=16666 -redorchestra|Red Orchestra|unreal2|port=7758,port_query_offset=1 -redorchestraost|Red Orchestra: Ostfront 41-45|gamespy1|port=7757,port_query_offset=10 -redorchestra2|Red Orchestra 2|valve|port=7777,port_query=27015 -redline|Redline|gamespy1|port_query=25252 -rtcw|Return to Castle Wolfenstein|quake3|port_query=27960 -ricochet|Ricochet|valve -riseofnations|Rise of Nations|gamespy1|port_query=6501 -rune|Rune|gamespy1|port=7777,port_query_offset=1 -rust|Rust|valve|port=28015,port_query_offset=1 -samp|San Andreas Multiplayer|samp|port=7777 -ss|Serious Sam|gamespy1|port=25600,port_query_offset=1 -ss2|Serious Sam 2|gamespy2|port=25600 -shatteredhorizon|Shattered Horizon|valve -ship|The Ship|valve -shogo|Shogo|gamespy1|port_query=27888 -shootmania|Shootmania|nadeo||doc_notes=nadeo-shootmania--trackmania--etc -sin|SiN|gamespy1|port_query=22450 -sinep|SiN Episodes|valve -soldat|Soldat|ase|port=13073,port_query_offset=123 -sof|Soldier of Fortune|quake1|port_query=28910 -sof2|Soldier of Fortune 2|quake3|port_query=20100 -stalker|S.T.A.L.K.E.R.|gamespy3|port=5445,port_query_offset=2 - -stbc|Star Trek: Bridge Commander|gamespy1|port_query=22101 -stvef|Star Trek: Voyager - Elite Force|quake3|port_query=27960 -stvef2|Star Trek: Voyager - Elite Force 2|quake3|port_query=29253 - -swbf|Star Wars: Battlefront|gamespy2|port_query=3658 -swbf2|Star Wars: Battlefront 2|gamespy2|port_query=3658 -swjk|Star Wars: Jedi Knight|quake3|port_query=29070 -swjk2|Star Wars: Jedi Knight 2|quake3|port_query=28070 -swrc|Star Wars: Republic Commando|gamespy2|port=7777,port_query=11138 - -starbound|Starbound|valve|port=21025 -suicidesurvival|Suicide Survival|valve -swat4|SWAT 4|gamespy2|port=10480,port_query_offset=2 -svencoop|Sven Coop|valve -synergy|Synergy|valve -tacticalops|Tactical Ops|gamespy1|port=7777,port_query_offset=1 -teamfactor|Team Factor|gamespy1|port_query=57778 -tfc|Team Fortress Classic|valve -tf2|Team Fortress 2|valve -teamspeak2|Teamspeak 2|teamspeak2|port=8767,port_query=51234 -teamspeak3|Teamspeak 3|teamspeak3|port=9987,port_query=10011 -terminus|Terminus|gamespy1|port_query=12286 -terraria|Terraria|terraria|port=7777,port_query_offset=101|doc_notes=terraria -thps3|Tony Hawk's Pro Skater 3|gamespy1|port_query=6500 -thps4|Tony Hawk's Pro Skater 4|gamespy1|port_query=6500 -thu2|Tony Hawk's Underground 2|gamespy1|port_query=5153 -trackmania2|Trackmania 2|nadeo||doc_notes=nadeo-shootmania--trackmania--etc -trackmaniaforever|Trackmania Forever|nadeo||doc_notes=nadeo-shootmania--trackmania--etc -tremulous|Tremulous|quake3|port_query=30720 -tribesvengeance|Tribes: Vengeance|gamespy2|port=7777,port_query_offset=1 -tron20|Tron 2.0|gamespy2|port_query=27888 -turok2|Turok 2|gamespy1|port_query=12880 -universalcombat|Universal Combat|ase|port=1135,port_query_offset=123 - -unreal|Unreal|gamespy1|port=7777,port_query_offset=1 -ut|Unreal Tournament|gamespy1|port=7777,port_query_offset=1 -ut2003|Unreal Tournament 2003|unreal2|port=7757,port_query_offset=1 -ut2004|Unreal Tournament 2004|ut2004|port=7777,port_query_offset=1 -ut3|Unreal Tournament 3|ut3|port=7777,port_query_offset=-1277 - -urbanterror|Urban Terror|quake3|port_query=27960 -v8supercar|V8 Supercar Challenge|gamespy1|port_query=16700 -ventrilo|Ventrilo|ventrilo|port=3784 -vietcong|Vietcong|gamespy1|port=5425,port_query=15425 -vietcong2|Vietcong 2|gamespy2|port=5001,port_query=19967 -warsow|Warsow|warsow|port=44400 -wheeloftime|Wheel of Time|gamespy1|port=7777,port_query_offset=1 -wolfenstein2009|Wolfenstein 2009|doom3|port_query=27666|hasSpaceBeforeClanTag,hasClanTag,hasTypeFlag -wolfensteinet|Wolfenstein: Enemy Territory|quake3|port_query=27960 -xpandrally|Xpand Rally|ase|port=28015,port_query_offset=123 -zombiemaster|Zombie Master|valve -zps|Zombie Panic: Source|valve +# id | pretty | protocol | options | parameters + +#### TODO: +# cube1|Cube 1|cube|port=28786,port_query_offset=1 +# assaultcube|Assault Cube|cube|port_query=28764 +# cube2|Cube 2: Sauerbraten|cube|port=28785,port_query_offset=1 +# bloodfrontier|Blood Frontier|cube + +# arcasimracing|Arca Sim Racing|rfactor|port=34397,port_query_offset=-100 +# rfactor|rFactor|rfactor|port=34397,port_query_offset=-100 + +# bfris|BFRIS|bfris|port=44001 +# cs2d|Counter-Strike: 2D|cs2d|port_query=36963 +# freelancer|Freelancer|freelancer|port_query=2302 +# gr|Ghost Recon|ghostrecon|port=2346,port_query_offset=2 +# gtr2|GTR2|gtr2|port=34297,port_query_offset=1 +# haze|Haze|haze +# hexen2|Hexen 2|hexen2|port_query=26900 +# openttd|OpenTTD|openttd|port=3979 +# plainsight|Plain Sight|plainsight +# redfaction|Red Faction|redfaction|port_query=7755 +# savage|Savage|savage|port_query=11235 +# savage2|Savage 2|savage2|port_query=11235 +# teeworlds|Teeworlds|teeworlds|port=8303 +# tribes|Tribes 1: Starsiege|tribes|port_query=28001 +# tribes2|Tribes 2|tribes2|port_query=28000 +# vcmp|Vice City Multiplayer|vcmp +# worldinconflict|World in Conflict|worldinconflict + + + +ageofchivalry|Age of Chivalry|valve +aoe2|Age of Empires 2|ase|port_query=27224 +alienarena|Alien Arena|quake2|port_query=27910 +alienswarm|Alien Swarm|valve +avp2|Aliens vs Predator 2|gamespy1|port=27888 +# avp2010 doesn't really... have a default port or query port +# both port and port_query should be specified when used +avp2010|Aliens vs Predator 2010|valve + +americasarmy|America's Army|americasarmy|port=1716,port_query_offset=1 +americasarmy2|America's Army 2|americasarmy|port=1716,port_query_offset=1 +americasarmy3|America's Army 3|valve|port=8777,port_query=27020 +americasarmypg|America's Army: Proving Grounds|valve|port=8777,port_query=27020 + +arma|ArmA Armed Assault 1|gamespy2|port=2302 +arma2|ArmA Armed Assault 2|valve|port=2302,port_query_offset=1 +arma3|ArmA Armed Assault 3|valve|port=2302,port_query_offset=1 + +armagetron|Armagetron|armagetron|port=4534 +baldursgate|Baldur's Gate|gamespy1|port=6073,port_query=1470 + +bf1942|Battlefield 1942|gamespy1|port=14567,port_query=23000 +bfv|Battlefield Vietnam|gamespy2|port=15567,port_query=23000 +bf2|Battlefield 2|gamespy3|port=16567,port_query=29900|noChallenge +bf2142|Battlefield 2142|gamespy3|port=16567,port_query=29900 +bfbc2|Battlefield: Bad Company 2|battlefield|port=19567,port_query=48888|isBadCompany2 +bf3|Battlefield 3|battlefield|port=25200,port_query_offset=22000 +bf4|Battlefield 4|battlefield|port=25200,port_query_offset=22000 + +breach|Breach|valve|port=27016 +breed|Breed|gamespy2|port=7649 +brink|Brink|valve|port_query_offset=1 +buildandshoot|Build and Shoot|buildandshoot|port=32887,port_query=32886 + +cod|Call of Duty|quake3|port=28960 +coduo|Call of Duty: United Offensive|quake3|port=28960 +cod2|Call of Duty 2|quake3|port=28960 +cod3|Call of Duty 3|quake3|port=28960 +cod4|Call of Duty 4: Modern Warfare|quake3|port=28960 +codwaw|Call of Duty: World at War|quake3|port=28960 +codmw2|Call of Duty: Modern Warfare 2|quake3|port=28960 +codmw3|Call of Duty: Modern Warfare 3|valve|port_query_offset=2 + +callofjuarez|Call of Juarez|ase|port_query=26000 +chaser|Chaser|ase|port=3000,port_query_offset=123 +chrome|Chrome|ase|port=27015,port_query_offset=123 +codenameeagle|Codename Eagle|gamespy1|port_query=4711 +commandos3|Commandos 3: Destination Berlin|gamespy1|port_query=6500 +cacrenegade|Command and Conquer: Renegade|gamespy1|port=4848,port_query=25300 +contactjack|Contact J.A.C.K.|gamespy1|port_query=27888 + +cs16|Counter-Strike 1.6|valve +cscz|Counter-Strike: Condition Zero|valve +css|Counter-Strike: Source|valve +csgo|Counter-Strike: Global Offensive|valve||isCsGo + +crossracing|Cross Racing Championship|ase|port=12321,port_query_offset=123 + +crysis|Crysis|gamespy3|port=64087 +crysiswars|Crysis Wars|gamespy3|port=64100 +crysis2|Crysis 2|gamespy3|port=64000 + +daikatana|Daikatana|quake2|port=27982,port_query_offset=10 +dmomam|Dark Messiah of Might and Magic|valve +darkesthour|Darkest Hour|unreal2|port=7757,port_query_offset=1 +dayz|DayZ|valve|port=2302,port_query_offset=24714|doc_notes=dayz +deadlydozenpt|Deadly Dozen: Pacific Theater|gamespy1|port_query=25300 +dh2005|Deer Hunter 2005|gamespy2|port=23459,port_query=34567 +descent3|Descent 3|gamespy1|port=2092,port_query=20142 +deusex|Deus Ex|gamespy2|port=7791,port_query_offset=1 +devastation|Devastation|unreal2|port=7777,port_query_offset=1 +dinodday|Dino D-Day|valve +dirttrackracing2|Dirt Track Racing 2|gamespy1|port=32240,port_query_offset=-100 +dod|Day of Defeat|valve +dods|Day of Defeat: Source|valve +doom3|Doom 3|doom3|port=27666 +dota2|DOTA 2|valve +drakan|Drakan|gamespy1|port=27045,port_query_offset=1 +etqw|Enemy Territory Quake Wars|doom3|port=3074,port_query=27733|isEtqw,hasSpaceBeforeClanTag,hasClanTag,hasTypeFlag +fear|F.E.A.R.|gamespy2|port_query=27888 +f12002|F1 2002|gamespy1|port_query=3297 +f1c9902|F1 Challenge 99-02|gamespy1|port_query=34397 +farcry|Far Cry|ase|port=49001,port_query_offset=123 +farcry2|Far Cry|ase|port_query=14001 +fortressforever|Fortress Forever|valve +flashpoint|Flashpoint|gamespy1|port=2302,port_query_offset=1 +ffow|Frontlines: Fuel of War|ffow|port=5476,port_query_offset=2 +garrysmod|Garry's Mod|valve +graw|Ghost Recon: Advanced Warfighter|gamespy2|port_query=15250 +graw2|Ghost Recon: Advanced Warfighter 2|gamespy2|port_query=16250 +giantscitizenkabuto|Giants: Citizen Kabuto|gamespy1|port_query=8911 +globaloperations|Global Operations|gamespy1|port_query=28672 +gore|Gore|gamespy1|port=27777,port_query_offset=1 +gunmanchronicles|Gunman Chronicles|valve +hldm|Half-Life 1 Deathmatch|valve +hl2dm|Half-Life 2 Deathmatch|valve +halo|Halo|gamespy2|port=2302 +halo2|Halo 2|gamespy2|port=2302 +heretic2|Heretic 2|gamespy1|port_query=28910 +hexenworld|Hexen World|hexenworld|port_query=26950 +hidden|The Hidden: Source|valve +had2|Hidden and Dangerous 2|gamespy1|port=11001,port_query_offset=3 +homefront|Homefront|valve +homeworld2|Homeworld 2|gamespy1|port_query=6500 +igi2|IGI-2: Covert Strike|gamespy1|port_query=26001 +il2|IL-2 Sturmovik|gamespy1|port_query=21000 +insurgency|Insurgency|valve +ironstorm|Iron Storm|gamespy1|port_query=3505 +jamesbondnightfire|James Bond: Nightfire|gamespy1|port_query=6550 +jc2mp|Just Cause 2 Multiplayer|jc2mp|port=7777 +killingfloor|Killing Floor|killingfloor|port=7707,port_query_offset=1 +kingpin|Kingpin: Life of Crime|gamespy1|port=31510,port_query_offset=-10 +kisspc|KISS Psycho Circus|gamespy1|port=7777,port_query_offset=1 +kzmod|KzMod|valve +left4dead|Left 4 Dead|valve +left4dead2|Left 4 Dead 2|valve +m2mp|Mafia 2 Multiplayer|m2mp|port=27016,port_query_offset=1 + +mohaa|Medal of Honor: Allied Assault|gamespy1|port=12203,port_query_offset=97 +mohpa|Medal of Honor: Pacific Assault|gamespy1|port=13203,port_query_offset=97 +mohab|Medal of Honor: Airborne|gamespy1|port=12203,port_query_offset=97 +mohsh|Medal of Honor: Spearhead|gamespy1|port=12203,port_query_offset=97 +mohbt|Medal of Honor: Breakthrough|gamespy1|port=12203,port_query_offset=97 +moh2010|Medal of Honor 2010|battlefield|port=7673,port_query=48888 +mohwf|Medal of Honor: Warfighter|battlefield|port=25200,port_query_offset=22000 + +minecraft|Minecraft|gamespy3|port=25565|maxAttempts=2,srvRecord=_minecraft._tcp,doc_notes=minecraft +minecraftping|Minecraft|minecraftping|port=25565|srvRecord=_minecraft._tcp,doc_notes=minecraft +mnc|Monday Night Combat|valve|port=7777,port_query=27016 +mtavc|Multi Theft Auto: Vice City|ase|port=22003,port_query_offset=123 +mtasa|Multi Theft Auto: San Andreas|ase|port=22003,port_query_offset=123 +mumble|Mumble|mumble|port=64738,port_query=27800|doc_notes=mumble +mumbleping|Mumble|mumbleping|port=64738|doc_notes=mumble +mutantfactions|Mutant Factions|mutantfactions|port=11235 +nascarthunder2004|Nascar Thunder 2004|gamespy2|port_query=13333 +netpanzer|netPanzer|gamespy1|3030 +nmrih|No More Room in Hell|valve +ns|Natural Selection|valve +ns2|Natural Selection 2|valve|port_query_offset=1 +nfshp2|Need for Speed: Hot Pursuit 2|gamespy1|port_query=61220 +nab|Nerf Arena Blast|gamespy1|port=4444,port_query_offset=1 +nwn|Neverwinter Nights|gamespy2|port_query=5121 +nwn2|Neverwinter Nights 2|gamespy2|port=5121,port_query=6500 +nexuiz|Nexuiz|quake3|port_query=26000 +nitrofamily|Nitro Family|gamespy1|port_query=25601 +nolf|No One Lives Forever|gamespy1|port_query=27888 +nolf2|No One Lives Forever 2|gamespy1|port_query=27890 +nucleardawn|Nuclear Dawn|valve +openarena|OpenArena|quake3|port_query=27960 +operationflashpoint|Operation Flashpoint|gamespy1|port=2234,port_query_offset=1 +painkiller|Painkiller|ase|port=3455,port_query_offset=123 +postal2|Postal 2|gamespy1|port=7777,port_query_offset=1 +prey|Prey|doom3|port_query=27719 + +quake1|Quake 1: QuakeWorld|quake1|port=27500 +quake2|Quake 2|quake2|port=27910 +quake3|Quake 3: Arena|quake3|port=27960 +quake4|Quake 4|doom3|port=28004|hasClanTag + +ragdollkungfu|Rag Doll Kung Fu|valve + +r6|Rainbow Six|gamespy1|port_query=2348 +r6roguespear|Rainbow Six 2: Rogue Spear|gamespy1|port_query=2346 +r6ravenshield|Rainbow Six 3: Raven Shield|gamespy1|port=7777,port_query_offset=1000 + +rallisportchallenge|RalliSport Challenge|gamespy1|port_query=17500 +rallymasters|Rally Masters|gamespy1|port_query=16666 +redorchestra|Red Orchestra|unreal2|port=7758,port_query_offset=1 +redorchestraost|Red Orchestra: Ostfront 41-45|gamespy1|port=7757,port_query_offset=10 +redorchestra2|Red Orchestra 2|valve|port=7777,port_query=27015 +redline|Redline|gamespy1|port_query=25252 +rtcw|Return to Castle Wolfenstein|quake3|port_query=27960 +ricochet|Ricochet|valve +riseofnations|Rise of Nations|gamespy1|port_query=6501 +rune|Rune|gamespy1|port=7777,port_query_offset=1 +rust|Rust|valve|port=28015,port_query_offset=1 +samp|San Andreas Multiplayer|samp|port=7777 +ss|Serious Sam|gamespy1|port=25600,port_query_offset=1 +ss2|Serious Sam 2|gamespy2|port=25600 +shatteredhorizon|Shattered Horizon|valve +ship|The Ship|valve +shogo|Shogo|gamespy1|port_query=27888 +shootmania|Shootmania|nadeo||doc_notes=nadeo-shootmania--trackmania--etc +sin|SiN|gamespy1|port_query=22450 +sinep|SiN Episodes|valve +soldat|Soldat|ase|port=13073,port_query_offset=123 +sof|Soldier of Fortune|quake1|port_query=28910 +sof2|Soldier of Fortune 2|quake3|port_query=20100 +stalker|S.T.A.L.K.E.R.|gamespy3|port=5445,port_query_offset=2 + +stbc|Star Trek: Bridge Commander|gamespy1|port_query=22101 +stvef|Star Trek: Voyager - Elite Force|quake3|port_query=27960 +stvef2|Star Trek: Voyager - Elite Force 2|quake3|port_query=29253 + +swbf|Star Wars: Battlefront|gamespy2|port_query=3658 +swbf2|Star Wars: Battlefront 2|gamespy2|port_query=3658 +swjk|Star Wars: Jedi Knight|quake3|port_query=29070 +swjk2|Star Wars: Jedi Knight 2|quake3|port_query=28070 +swrc|Star Wars: Republic Commando|gamespy2|port=7777,port_query=11138 + +starbound|Starbound|valve|port=21025 +suicidesurvival|Suicide Survival|valve +swat4|SWAT 4|gamespy2|port=10480,port_query_offset=2 +svencoop|Sven Coop|valve +synergy|Synergy|valve +tacticalops|Tactical Ops|gamespy1|port=7777,port_query_offset=1 +teamfactor|Team Factor|gamespy1|port_query=57778 +tfc|Team Fortress Classic|valve +tf2|Team Fortress 2|valve +teamspeak2|Teamspeak 2|teamspeak2|port=8767,port_query=51234 +teamspeak3|Teamspeak 3|teamspeak3|port=9987,port_query=10011 +terminus|Terminus|gamespy1|port_query=12286 +terraria|Terraria|terraria|port=7777,port_query_offset=101|doc_notes=terraria +thps3|Tony Hawk's Pro Skater 3|gamespy1|port_query=6500 +thps4|Tony Hawk's Pro Skater 4|gamespy1|port_query=6500 +thu2|Tony Hawk's Underground 2|gamespy1|port_query=5153 +trackmania2|Trackmania 2|nadeo||doc_notes=nadeo-shootmania--trackmania--etc +trackmaniaforever|Trackmania Forever|nadeo||doc_notes=nadeo-shootmania--trackmania--etc +tremulous|Tremulous|quake3|port_query=30720 +tribesvengeance|Tribes: Vengeance|gamespy2|port=7777,port_query_offset=1 +tron20|Tron 2.0|gamespy2|port_query=27888 +turok2|Turok 2|gamespy1|port_query=12880 +universalcombat|Universal Combat|ase|port=1135,port_query_offset=123 + +unreal|Unreal|gamespy1|port=7777,port_query_offset=1 +ut|Unreal Tournament|gamespy1|port=7777,port_query_offset=1 +ut2003|Unreal Tournament 2003|unreal2|port=7757,port_query_offset=1 +ut2004|Unreal Tournament 2004|ut2004|port=7777,port_query_offset=1 +ut3|Unreal Tournament 3|ut3|port=7777,port_query_offset=-1277 + +urbanterror|Urban Terror|quake3|port_query=27960 +v8supercar|V8 Supercar Challenge|gamespy1|port_query=16700 +ventrilo|Ventrilo|ventrilo|port=3784 +vietcong|Vietcong|gamespy1|port=5425,port_query=15425 +vietcong2|Vietcong 2|gamespy2|port=5001,port_query=19967 +warsow|Warsow|warsow|port=44400 +wheeloftime|Wheel of Time|gamespy1|port=7777,port_query_offset=1 +wolfenstein2009|Wolfenstein 2009|doom3|port_query=27666|hasSpaceBeforeClanTag,hasClanTag,hasTypeFlag +wolfensteinet|Wolfenstein: Enemy Territory|quake3|port_query=27960 +xpandrally|Xpand Rally|ase|port=28015,port_query_offset=123 +zombiemaster|Zombie Master|valve +zps|Zombie Panic: Source|valve diff --git a/lib/Class.js b/lib/Class.js index 6e8efbc..1bfd518 100644 --- a/lib/Class.js +++ b/lib/Class.js @@ -1,74 +1,74 @@ -/* based on Simple JavaScript Inheritance -* By John Resig http://ejohn.org/ -* MIT Licensed. -*/ - -var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; - -// The base Class implementation (does nothing) -var Class = function(){}; - -// Create a new Class that inherits from this class -Class.extend = function() { - var args = Array.prototype.slice.call(arguments); - var name = 'Class'; - var parent = this; - var prop = {}; - if(typeof args[0] == 'string') name = args.shift(); - if(args.length >= 2) parent = args.shift(); - prop = args.shift(); - - // Copy prototype from the parent object - var prototype = {}; - for(var name in parent.prototype) { - prototype[name] = parent.prototype[name]; - } - - // Copy the properties over onto the new prototype - for(var name in prop) { - if(typeof prop[name] == "function" && fnTest.test(prop[name])) { - // this is a function that references _super, so we have to wrap it - // and provide it with its super function - prototype[name] = (function(name, fn){ - return function() { - var tmp = this._super; - - // Add a new ._super() method that is the same method - // but on the super-class - if(typeof parent.prototype[name] == 'undefined') { - if(name == 'init') this._super = parent.prototype.constructor; - else this._super = function() { throw new Error('Called _super in method without a parent'); } - } else this._super = parent.prototype[name]; - - // The method only need to be bound temporarily, so we - // remove it when we're done executing - var ret = fn.apply(this, arguments); - this._super = tmp; - - return ret; - }; - })(name, prop[name]); - } else { - prototype[name] = prop[name]; - } - } - - // The dummy class constructor - function Class() { - // All construction is actually done in the init method - if(this.init) this.init.apply(this, arguments); - } - - // Populate our constructed prototype object - Class.prototype = prototype; - - // Enforce the constructor to be what we expect - Class.prototype.constructor = Class; - - // And make this class extendable - Class.extend = arguments.callee; - - return Class; -}; - -module.exports = Class; +/* based on Simple JavaScript Inheritance +* By John Resig http://ejohn.org/ +* MIT Licensed. +*/ + +var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; + +// The base Class implementation (does nothing) +var Class = function(){}; + +// Create a new Class that inherits from this class +Class.extend = function() { + var args = Array.prototype.slice.call(arguments); + var name = 'Class'; + var parent = this; + var prop = {}; + if(typeof args[0] == 'string') name = args.shift(); + if(args.length >= 2) parent = args.shift(); + prop = args.shift(); + + // Copy prototype from the parent object + var prototype = {}; + for(var name in parent.prototype) { + prototype[name] = parent.prototype[name]; + } + + // Copy the properties over onto the new prototype + for(var name in prop) { + if(typeof prop[name] == "function" && fnTest.test(prop[name])) { + // this is a function that references _super, so we have to wrap it + // and provide it with its super function + prototype[name] = (function(name, fn){ + return function() { + var tmp = this._super; + + // Add a new ._super() method that is the same method + // but on the super-class + if(typeof parent.prototype[name] == 'undefined') { + if(name == 'init') this._super = parent.prototype.constructor; + else this._super = function() { throw new Error('Called _super in method without a parent'); } + } else this._super = parent.prototype[name]; + + // The method only need to be bound temporarily, so we + // remove it when we're done executing + var ret = fn.apply(this, arguments); + this._super = tmp; + + return ret; + }; + })(name, prop[name]); + } else { + prototype[name] = prop[name]; + } + } + + // The dummy class constructor + function Class() { + // All construction is actually done in the init method + if(this.init) this.init.apply(this, arguments); + } + + // Populate our constructed prototype object + Class.prototype = prototype; + + // Enforce the constructor to be what we expect + Class.prototype.constructor = Class; + + // And make this class extendable + Class.extend = arguments.callee; + + return Class; +}; + +module.exports = Class; diff --git a/lib/index.js b/lib/index.js index 66c6846..79dd1c5 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,80 +1,80 @@ -var dgram = require('dgram'), - EventEmitter = require('events').EventEmitter, - util = require('util'), - dns = require('dns'), - TypeResolver = require('./typeresolver'); - -var activeQueries = []; - -var udpSocket = dgram.createSocket('udp4'); -udpSocket.unref(); -udpSocket.bind(21943); -udpSocket.on('message', function(buffer, rinfo) { - if(Gamedig.debug) console.log(rinfo.address+':'+rinfo.port+" <--UDP "+buffer.toString('hex')); - for(var i = 0; i < activeQueries.length; i++) { - var query = activeQueries[i]; - if( - query.options.address != rinfo.address - && query.options.altaddress != rinfo.address - ) continue; - if(query.options.port_query != rinfo.port) continue; - query._udpResponse(buffer); - break; - } -}); -udpSocket.on('error', function(e) { - if(Gamedig.debug) console.log("UDP ERROR: "+e); -}); - -Gamedig = { - - query: function(options,callback) { - if(callback) options.callback = callback; - - var query; - try { - query = TypeResolver.lookup(options.type); - } catch(e) { - process.nextTick(function() { - callback({error:e.message}); - }); - return; - } - query.debug = Gamedig.debug; - query.udpSocket = udpSocket; - query.type = options.type; - - if(!('port' in query.options) && ('port_query' in query.options)) { - if(Gamedig.isCommandLine) { - process.stderr.write( - "Warning! This game is so old, that we don't know" - +" what the server's connection port is. We've guessed that" - +" the query port for "+query.type+" is "+query.options.port_query+"." - +" If you know the connection port for this type of server, please let" - +" us know on the GameDig issue tracker, thanks!\n" - ); - } - query.options.port = query.options.port_query; - delete query.options.port_query; - } - - // copy over options - for(var i in options) query.options[i] = options[i]; - - activeQueries.push(query); - - query.on('finished',function(state) { - var i = activeQueries.indexOf(query); - if(i >= 0) activeQueries.splice(i, 1); - }); - - process.nextTick(function() { - query.start(); - }); - - return query; - } - -}; - -module.exports = Gamedig; +var dgram = require('dgram'), + EventEmitter = require('events').EventEmitter, + util = require('util'), + dns = require('dns'), + TypeResolver = require('./typeresolver'); + +var activeQueries = []; + +var udpSocket = dgram.createSocket('udp4'); +udpSocket.unref(); +udpSocket.bind(21943); +udpSocket.on('message', function(buffer, rinfo) { + if(Gamedig.debug) console.log(rinfo.address+':'+rinfo.port+" <--UDP "+buffer.toString('hex')); + for(var i = 0; i < activeQueries.length; i++) { + var query = activeQueries[i]; + if( + query.options.address != rinfo.address + && query.options.altaddress != rinfo.address + ) continue; + if(query.options.port_query != rinfo.port) continue; + query._udpResponse(buffer); + break; + } +}); +udpSocket.on('error', function(e) { + if(Gamedig.debug) console.log("UDP ERROR: "+e); +}); + +Gamedig = { + + query: function(options,callback) { + if(callback) options.callback = callback; + + var query; + try { + query = TypeResolver.lookup(options.type); + } catch(e) { + process.nextTick(function() { + callback({error:e.message}); + }); + return; + } + query.debug = Gamedig.debug; + query.udpSocket = udpSocket; + query.type = options.type; + + if(!('port' in query.options) && ('port_query' in query.options)) { + if(Gamedig.isCommandLine) { + process.stderr.write( + "Warning! This game is so old, that we don't know" + +" what the server's connection port is. We've guessed that" + +" the query port for "+query.type+" is "+query.options.port_query+"." + +" If you know the connection port for this type of server, please let" + +" us know on the GameDig issue tracker, thanks!\n" + ); + } + query.options.port = query.options.port_query; + delete query.options.port_query; + } + + // copy over options + for(var i in options) query.options[i] = options[i]; + + activeQueries.push(query); + + query.on('finished',function(state) { + var i = activeQueries.indexOf(query); + if(i >= 0) activeQueries.splice(i, 1); + }); + + process.nextTick(function() { + query.start(); + }); + + return query; + } + +}; + +module.exports = Gamedig; diff --git a/lib/reader.js b/lib/reader.js index 4046c57..f86ee13 100644 --- a/lib/reader.js +++ b/lib/reader.js @@ -1,124 +1,124 @@ -var Iconv = require('iconv-lite'), - Long = require('long'); - -function readUInt64BE(buffer,offset) { - var high = buffer.readUInt32BE(offset); - var low = buffer.readUInt32BE(offset+4); - return new Long(low,high,true); -} -function readUInt64LE(buffer,offset) { - var low = buffer.readUInt32LE(offset); - var high = buffer.readUInt32LE(offset+4); - return new Long(low,high,true); -} - -function Reader(query,buffer) { - this.query = query; - this.buffer = buffer; - this.i = 0; -} - -Reader.prototype = { - offset: function() { return this.i; }, - skip: function(i) { this.i += i; }, - string: function() { - var args = Array.prototype.slice.call(arguments); - var options = {}; - if(args.length == 0) { - options = {}; - } else if(args.length == 1) { - if(typeof args[0] == 'string') options = { delimiter: args[0] }; - else if(typeof args[0] == 'number') options = { length: args[0] }; - else options = args[0]; - } - - options.encoding = options.encoding || this.query.encoding; - if(options.encoding == 'latin1') options.encoding = 'win1252'; - - var start = this.i+0; - var end = start; - if(!('length' in options)) { - // terminated by the delimiter - var delim = options.delimiter || this.query.delimiter; - if(typeof delim == 'string') delim = delim.charCodeAt(0); - while(true) { - if(end >= this.buffer.length) { - end = this.buffer.length; - break; - } - if(this.buffer.readUInt8(end) == delim) break; - end++; - } - this.i = end+1; - } else { - end = start+options.length; - if(end >= this.buffer.length) { - end = this.buffer.length; - } - this.i = end; - } - - var out = this.buffer.slice(start, end); - var enc = options.encoding; - if(enc == 'utf8' || enc == 'ucs2' || enc == 'binary') { - out = out.toString(enc); - } else { - out = Iconv.decode(out,enc); - } - return out; - }, - int: function(bytes) { - var r = 0; - if(this.remaining() >= bytes) { - if(this.query.byteorder == 'be') { - if(bytes == 1) r = this.buffer.readInt8(this.i); - else if(bytes == 2) r = this.buffer.readInt16BE(this.i); - else if(bytes == 4) r = this.buffer.readInt32BE(this.i); - } else { - if(bytes == 1) r = this.buffer.readInt8(this.i); - else if(bytes == 2) r = this.buffer.readInt16LE(this.i); - else if(bytes == 4) r = this.buffer.readInt32LE(this.i); - } - } - this.i += bytes; - return r; - }, - uint: function(bytes) { - var r = 0; - if(this.remaining() >= bytes) { - if(this.query.byteorder == 'be') { - if(bytes == 1) r = this.buffer.readUInt8(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 == 8) r = readUInt64BE(this.buffer,this.i).toString(); - } else { - if(bytes == 1) r = this.buffer.readUInt8(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 == 8) r = readUInt64LE(this.buffer,this.i).toString(); - } - } - this.i += bytes; - return r; - }, - float: function() { - var r = 0; - if(this.remaining() >= 4) { - if(this.query.byteorder == 'be') r = this.buffer.readFloatBE(this.i); - else r = this.buffer.readFloatLE(this.i); - } - this.i += 4; - return r; - }, - remaining: function() { - return this.buffer.length-this.i; - }, - rest: function() { - return this.buffer.slice(this.i); - }, - done: function() { - return this.i >= this.buffer.length; - } -}; - -module.exports = Reader; +var Iconv = require('iconv-lite'), + Long = require('long'); + +function readUInt64BE(buffer,offset) { + var high = buffer.readUInt32BE(offset); + var low = buffer.readUInt32BE(offset+4); + return new Long(low,high,true); +} +function readUInt64LE(buffer,offset) { + var low = buffer.readUInt32LE(offset); + var high = buffer.readUInt32LE(offset+4); + return new Long(low,high,true); +} + +function Reader(query,buffer) { + this.query = query; + this.buffer = buffer; + this.i = 0; +} + +Reader.prototype = { + offset: function() { return this.i; }, + skip: function(i) { this.i += i; }, + string: function() { + var args = Array.prototype.slice.call(arguments); + var options = {}; + if(args.length == 0) { + options = {}; + } else if(args.length == 1) { + if(typeof args[0] == 'string') options = { delimiter: args[0] }; + else if(typeof args[0] == 'number') options = { length: args[0] }; + else options = args[0]; + } + + options.encoding = options.encoding || this.query.encoding; + if(options.encoding == 'latin1') options.encoding = 'win1252'; + + var start = this.i+0; + var end = start; + if(!('length' in options)) { + // terminated by the delimiter + var delim = options.delimiter || this.query.delimiter; + if(typeof delim == 'string') delim = delim.charCodeAt(0); + while(true) { + if(end >= this.buffer.length) { + end = this.buffer.length; + break; + } + if(this.buffer.readUInt8(end) == delim) break; + end++; + } + this.i = end+1; + } else { + end = start+options.length; + if(end >= this.buffer.length) { + end = this.buffer.length; + } + this.i = end; + } + + var out = this.buffer.slice(start, end); + var enc = options.encoding; + if(enc == 'utf8' || enc == 'ucs2' || enc == 'binary') { + out = out.toString(enc); + } else { + out = Iconv.decode(out,enc); + } + return out; + }, + int: function(bytes) { + var r = 0; + if(this.remaining() >= bytes) { + if(this.query.byteorder == 'be') { + if(bytes == 1) r = this.buffer.readInt8(this.i); + else if(bytes == 2) r = this.buffer.readInt16BE(this.i); + else if(bytes == 4) r = this.buffer.readInt32BE(this.i); + } else { + if(bytes == 1) r = this.buffer.readInt8(this.i); + else if(bytes == 2) r = this.buffer.readInt16LE(this.i); + else if(bytes == 4) r = this.buffer.readInt32LE(this.i); + } + } + this.i += bytes; + return r; + }, + uint: function(bytes) { + var r = 0; + if(this.remaining() >= bytes) { + if(this.query.byteorder == 'be') { + if(bytes == 1) r = this.buffer.readUInt8(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 == 8) r = readUInt64BE(this.buffer,this.i).toString(); + } else { + if(bytes == 1) r = this.buffer.readUInt8(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 == 8) r = readUInt64LE(this.buffer,this.i).toString(); + } + } + this.i += bytes; + return r; + }, + float: function() { + var r = 0; + if(this.remaining() >= 4) { + if(this.query.byteorder == 'be') r = this.buffer.readFloatBE(this.i); + else r = this.buffer.readFloatLE(this.i); + } + this.i += 4; + return r; + }, + remaining: function() { + return this.buffer.length-this.i; + }, + rest: function() { + return this.buffer.slice(this.i); + }, + done: function() { + return this.i >= this.buffer.length; + } +}; + +module.exports = Reader; diff --git a/package.json b/package.json index 2cb172d..3f13b62 100644 --- a/package.json +++ b/package.json @@ -1,41 +1,41 @@ -{ - "name": "gamedig", - "description": "Query for the status of any game server in Node.JS", - "tags": [ - "srcds", - "query", - "game", - "utility", - "util", - "server" - ], - "main": "lib/index.js", - "author": "Michael Morrison", - "version": "0.2.9", - "repository" : { - "type" : "git", - "url" : "https://github.com/sonicsnes/node-gamedig.git" - }, - "bugs" : { - "url" : "https://github.com/sonicsnes/node-gamedig/issues" - }, - "licenses" : [ - { - "type" : "MIT", - "url" : "https://raw.github.com/sonicsnes/node-gamedig/master/LICENSE" - } - ], - "dependencies": { - "iconv-lite": "~0.2.11", - "long": "~1.1.2", - "async": "~0.2.10", - "compressjs": "~1.0.1", - "gbxremote": "~0.1.4", - "request": "~2.33.0", - "optimist": "~0.6.0", - "varint": "~1.0.0" - }, - "bin": { - "gamedig": "bin/gamedig.js" - } -} +{ + "name": "gamedig", + "description": "Query for the status of any game server in Node.JS", + "tags": [ + "srcds", + "query", + "game", + "utility", + "util", + "server" + ], + "main": "lib/index.js", + "author": "Michael Morrison", + "version": "0.2.9", + "repository" : { + "type" : "git", + "url" : "https://github.com/sonicsnes/node-gamedig.git" + }, + "bugs" : { + "url" : "https://github.com/sonicsnes/node-gamedig/issues" + }, + "licenses" : [ + { + "type" : "MIT", + "url" : "https://raw.github.com/sonicsnes/node-gamedig/master/LICENSE" + } + ], + "dependencies": { + "iconv-lite": "~0.2.11", + "long": "~1.1.2", + "async": "~0.2.10", + "compressjs": "~1.0.1", + "gbxremote": "~0.1.4", + "request": "~2.33.0", + "optimist": "~0.6.0", + "varint": "~1.0.0" + }, + "bin": { + "gamedig": "bin/gamedig.js" + } +} diff --git a/protocols/armagetron.js b/protocols/armagetron.js index 7f13ef4..906fad9 100644 --- a/protocols/armagetron.js +++ b/protocols/armagetron.js @@ -1,64 +1,64 @@ -module.exports = require('./core').extend({ - init: function() { - this._super(); - this.encoding = 'latin1'; - this.byteorder = 'be'; - }, - run: function(state) { - var self = this; - - var b = new Buffer([0,0x35,0,0,0,0,0,0x11]); - - this.udpSend(b,function(buffer) { - var reader = self.reader(buffer); - - reader.skip(6); - - state.raw.port = self.readUInt(reader); - state.raw.hostname = self.readString(reader,buffer); - state.name = self.stripColorCodes(self.readString(reader,buffer)); - state.raw.numplayers = self.readUInt(reader); - state.raw.versionmin = self.readUInt(reader); - state.raw.versionmax = self.readUInt(reader); - state.raw.version = self.readString(reader,buffer); - state.maxplayers = self.readUInt(reader); - - var players = self.readString(reader,buffer); - var list = players.split('\n'); - for(var i = 0; i < list.length; i++) { - if(!list[i]) continue; - state.players.push({ - name:self.stripColorCodes(list[i]) - }); - } - - state.raw.options = self.stripColorCodes(self.readString(reader,buffer)); - state.raw.uri = self.readString(reader,buffer); - state.raw.globalids = self.readString(reader,buffer); - self.finish(state); - return true; - }); - }, - readUInt: function(reader) { - var a = reader.uint(2); - var b = reader.uint(2); - return (b<<16) + a; - }, - readString: function(reader,b) { - var len = reader.uint(2); - if(!len) return ''; - - var out = ''; - for(var i = 0; i < len; i+=2) { - var hi = reader.uint(1); - var lo = reader.uint(1); - if(i+1[^]*([^]*)<\/td>[^]*([^]*)<\/td>[^]*([^]*)<\/td>[^]*([^]*)<\/td>/g; - while(pm = pre.exec(table)) { - if(pm[2] == 'Ping') continue; - state.players.push({ - name: pm[1], - ping: pm[2], - team: pm[3], - score: pm[4] - }); - } - } - /* - var m = this.options.address.match(/(\d+)\.(\d+)\.(\d+)\.(\d+)/); - if(m) { - var o1 = parseInt(m[1]); - var o2 = parseInt(m[2]); - var o3 = parseInt(m[3]); - var o4 = parseInt(m[4]); - var addr = o1+(o2<<8)+(o3<<16)+(o4<<24); - state.raw.url = 'aos://'+addr; - } - */ - self.finish(state); - }); - } -}); +var request = require('request'); + +module.exports = require('./core').extend({ + run: function(state) { + var self = this; + request({ + uri: 'http://'+this.options.address+':'+this.options.port_query+'/', + timeout: 3000, + }, function(e,r,body) { + if(e) return self.fatal('HTTP error'); + + var m = body.match(/status server for (.*?)\r|\n/); + if(m) state.name = m[1]; + + var m = body.match(/Current uptime: (\d+)/); + if(m) state.raw.uptime = m[1]; + + var m = body.match(/currently running (.*?) by /); + if(m) state.map = m[1]; + + var m = body.match(/Current players: (\d+)\/(\d+)/); + if(m) { + state.raw.numplayers = m[1]; + state.maxplayers = m[2]; + } + + var m = body.match(/class="playerlist"([^]+?)\/table/); + if(m) { + var table = m[1]; + var pre = /[^]*([^]*)<\/td>[^]*([^]*)<\/td>[^]*([^]*)<\/td>[^]*([^]*)<\/td>/g; + while(pm = pre.exec(table)) { + if(pm[2] == 'Ping') continue; + state.players.push({ + name: pm[1], + ping: pm[2], + team: pm[3], + score: pm[4] + }); + } + } + /* + var m = this.options.address.match(/(\d+)\.(\d+)\.(\d+)\.(\d+)/); + if(m) { + var o1 = parseInt(m[1]); + var o2 = parseInt(m[2]); + var o3 = parseInt(m[3]); + var o4 = parseInt(m[4]); + var addr = o1+(o2<<8)+(o3<<16)+(o4<<24); + state.raw.url = 'aos://'+addr; + } + */ + self.finish(state); + }); + } +}); diff --git a/protocols/core.js b/protocols/core.js index 49cbab7..26d28cb 100644 --- a/protocols/core.js +++ b/protocols/core.js @@ -1,312 +1,312 @@ -var EventEmitter = require('events').EventEmitter, - dns = require('dns'), - net = require('net'), - async = require('async'), - Class = require('../lib/Class'), - Reader = require('../lib/reader'); - -module.exports = Class.extend(EventEmitter,{ - init: function() { - this._super(); - this.options = { - tcpTimeout: 1000, - udpTimeout: 1000 - }; - this.maxAttempts = 1; - this.attempt = 1; - this.finished = false; - this.encoding = 'utf8'; - this.byteorder = 'le'; - this.delimiter = '\0'; - - var self = this; - this.globalTimeoutTimer = setTimeout(function() { - self.fatal('timeout'); - },10000); - }, - - fatal: function(err,noretry) { - if(!noretry && this.attempt < this.maxAttempts) { - this.attempt++; - this.start(); - return; - } - - this.done({error: err.toString()}); - }, - initState: function() { - return { - name: '', - map: '', - password: false, - - raw: {}, - - maxplayers: 0, - players: [], - bots: [] - }; - }, - finalizeState: function(state) {}, - - finish: function(state) { - this.finalizeState(state); - this.done(state); - }, - - done: function(state) { - if(this.finished) return; - clearTimeout(this.globalTimeoutTimer); - - if(this.options.notes) - state.notes = this.options.notes; - - state.query = {}; - if('host' in this.options) state.query.host = this.options.host; - if('address' in this.options) state.query.address = this.options.address; - if('port' in this.options) state.query.port = this.options.port; - if('port_query' in this.options) state.query.port_query = this.options.port_query; - state.query.type = this.type; - if('pretty' in this) state.query.pretty = this.pretty; - - this.reset(); - this.finished = true; - this.emit('finished',state); - if(this.options.callback) this.options.callback(state); - }, - - reset: function() { - if(this.timers) { - this.timers.forEach(function(timer) { - clearTimeout(timer); - }); - } - this.timers = []; - - if(this.tcpSocket) { - this.tcpSocket.destroy(); - delete this.tcpSocket; - } - - this.udpTimeoutTimer = false; - this.udpCallback = false; - }, - start: function() { - var self = this; - var options = self.options; - this.reset(); - - async.series([ - function(c) { - // resolve host names - if(!('host' in options)) return c(); - if(options.host.match(/\d+\.\d+\.\d+\.\d+/)) { - options.address = options.host; - c(); - } else { - self.parseDns(options.host,c); - } - }, - function(c) { - // calculate query port if needed - if(!('port_query' in options) && 'port' in options) { - var offset = options.port_query_offset || 0; - options.port_query = options.port + offset; - } - c(); - }, - function(c) { - // run - self.run(self.initState()); - } - - ]); - }, - parseDns: function(host,c) { - var self = this; - - function resolveStandard(host,c) { - dns.lookup(host, function(err,address,family) { - if(err) return self.fatal(err); - self.options.address = address; - c(); - }); - } - function resolveSrv(srv,host,c) { - dns.resolve(srv+'.'+host, 'SRV', function(err,addresses) { - if(err) return resolveStandard(host,c); - if(addresses.length >= 1) { - var line = addresses[0]; - self.options.port = line.port; - var srvhost = line.name; - - if(srvhost.match(/\d+\.\d+\.\d+\.\d+/)) { - self.options.address = srvhost; - c(); - } else { - // resolve yet again - resolveStandard(srvhost,c); - } - return; - } - return resolveStandard(host,c); - }); - } - - if(this.srvRecord) resolveSrv(this.srvRecord,host,c); - else resolveStandard(host,c); - }, - - // utils - reader: function(buffer) { - return new Reader(this,buffer); - }, - translate: function(obj,trans) { - for(var from in trans) { - var to = trans[from]; - if(from in obj) { - if(to) obj[to] = obj[from]; - delete obj[from]; - } - } - }, - setTimeout: function(c,t) { - if(this.finished) return 0; - var id = setTimeout(c,t); - this.timers.push(id); - return id; - }, - - - - trueTest: function(str) { - if(typeof str == 'boolean') return str; - if(typeof str == 'number') return str != 0; - if(typeof str == 'string') { - if(str.toLowerCase() == 'true') return true; - if(str == 'yes') return true; - if(str == '1') return true; - } - return false; - }, - debugBuffer: function(buffer) { - var out = ''; - var out2 = ''; - for(var i = 0; i < buffer.length; i++) { - var sliced = buffer.slice(i,i+1); - out += sliced.toString('hex')+' '; - var chr = sliced.toString(); - if(chr < ' ' || chr > '~') chr = ' '; - out2 += chr+' '; - if(out.length > 60) { - console.log(out); - console.log(out2); - out = out2 = ''; - } - } - console.log(out); - console.log(out2); - }, - - - - - _tcpConnect: function(c) { - var self = this; - if(this.tcpSocket) return c(this.tcpSocket); - - var connected = false; - var received = new Buffer(0); - var address = this.options.address; - var port = this.options.port_query; - - var socket = this.tcpSocket = net.connect(port,address,function() { - if(self.debug) console.log(address+':'+port+" TCPCONNECTED"); - connected = true; - c(socket); - }); - socket.setTimeout(10000); - socket.setNoDelay(true); - if(this.debug) console.log(address+':'+port+" TCPCONNECT"); - - var writeHook = socket.write; - socket.write = function(data) { - if(self.debug) console.log(address+':'+port+" TCP--> "+data.toString('hex')); - writeHook.apply(this,arguments); - } - - socket.on('error', function() {}); - socket.on('close', function() { - if(!self.tcpCallback) return; - if(connected) return self.fatal('Socket closed while waiting on TCP'); - else return self.fatal('TCP Connection Refused'); - }); - socket.on('data', function(data) { - if(!self.tcpCallback) return; - if(self.debug) console.log(address+':'+port+" <--TCP "+data.toString('hex')); - received = Buffer.concat([received,data]); - if(self.tcpCallback(received)) { - clearTimeout(self.tcpTimeoutTimer); - self.tcpCallback = false; - received = new Buffer(0); - } - }); - }, - tcpSend: function(buffer,ondata) { - var self = this; - process.nextTick(function() { - if(self.tcpCallback) return self.fatal('Attempted to send TCP packet while still waiting on a managed response'); - self._tcpConnect(function(socket) { - socket.write(buffer); - }); - if(!ondata) return; - - self.tcpTimeoutTimer = self.setTimeout(function() { - self.tcpCallback = false; - self.fatal('TCP Watchdog Timeout'); - },self.options.tcpTimeout); - self.tcpCallback = ondata; - }); - }, - - - - udpSend: function(buffer,onpacket,ontimeout) { - var self = this; - process.nextTick(function() { - if(self.udpCallback) return self.fatal('Attempted to send UDP packet while still waiting on a managed response'); - self._udpSendNow(buffer); - if(!onpacket) return; - - self.udpTimeoutTimer = self.setTimeout(function() { - self.udpCallback = false; - var timeout = false; - if(!ontimeout || ontimeout() !== true) timeout = true; - if(timeout) self.fatal('UDP Watchdog Timeout'); - },self.options.udpTimeout); - self.udpCallback = onpacket; - }); - }, - _udpSendNow: function(buffer) { - if(!('port_query' in this.options)) return this.fatal('Attempted to send without setting a port'); - if(!('address' in this.options)) return this.fatal('Attempted to send without setting an address'); - - if(typeof buffer == 'string') buffer = new Buffer(buffer,'binary'); - - if(this.debug) console.log(this.options.address+':'+this.options.port_query+" UDP--> "+buffer.toString('hex')); - this.udpSocket.send(buffer,0,buffer.length,this.options.port_query,this.options.address); - }, - _udpResponse: function(buffer) { - if(this.udpCallback) { - var result = this.udpCallback(buffer); - if(result === true) { - // we're done with this udp session - clearTimeout(this.udpTimeoutTimer); - this.udpCallback = false; - } - } else { - this.udpResponse(buffer); - } - }, - udpResponse: function() {} -}); +var EventEmitter = require('events').EventEmitter, + dns = require('dns'), + net = require('net'), + async = require('async'), + Class = require('../lib/Class'), + Reader = require('../lib/reader'); + +module.exports = Class.extend(EventEmitter,{ + init: function() { + this._super(); + this.options = { + tcpTimeout: 1000, + udpTimeout: 1000 + }; + this.maxAttempts = 1; + this.attempt = 1; + this.finished = false; + this.encoding = 'utf8'; + this.byteorder = 'le'; + this.delimiter = '\0'; + + var self = this; + this.globalTimeoutTimer = setTimeout(function() { + self.fatal('timeout'); + },10000); + }, + + fatal: function(err,noretry) { + if(!noretry && this.attempt < this.maxAttempts) { + this.attempt++; + this.start(); + return; + } + + this.done({error: err.toString()}); + }, + initState: function() { + return { + name: '', + map: '', + password: false, + + raw: {}, + + maxplayers: 0, + players: [], + bots: [] + }; + }, + finalizeState: function(state) {}, + + finish: function(state) { + this.finalizeState(state); + this.done(state); + }, + + done: function(state) { + if(this.finished) return; + clearTimeout(this.globalTimeoutTimer); + + if(this.options.notes) + state.notes = this.options.notes; + + state.query = {}; + if('host' in this.options) state.query.host = this.options.host; + if('address' in this.options) state.query.address = this.options.address; + if('port' in this.options) state.query.port = this.options.port; + if('port_query' in this.options) state.query.port_query = this.options.port_query; + state.query.type = this.type; + if('pretty' in this) state.query.pretty = this.pretty; + + this.reset(); + this.finished = true; + this.emit('finished',state); + if(this.options.callback) this.options.callback(state); + }, + + reset: function() { + if(this.timers) { + this.timers.forEach(function(timer) { + clearTimeout(timer); + }); + } + this.timers = []; + + if(this.tcpSocket) { + this.tcpSocket.destroy(); + delete this.tcpSocket; + } + + this.udpTimeoutTimer = false; + this.udpCallback = false; + }, + start: function() { + var self = this; + var options = self.options; + this.reset(); + + async.series([ + function(c) { + // resolve host names + if(!('host' in options)) return c(); + if(options.host.match(/\d+\.\d+\.\d+\.\d+/)) { + options.address = options.host; + c(); + } else { + self.parseDns(options.host,c); + } + }, + function(c) { + // calculate query port if needed + if(!('port_query' in options) && 'port' in options) { + var offset = options.port_query_offset || 0; + options.port_query = options.port + offset; + } + c(); + }, + function(c) { + // run + self.run(self.initState()); + } + + ]); + }, + parseDns: function(host,c) { + var self = this; + + function resolveStandard(host,c) { + dns.lookup(host, function(err,address,family) { + if(err) return self.fatal(err); + self.options.address = address; + c(); + }); + } + function resolveSrv(srv,host,c) { + dns.resolve(srv+'.'+host, 'SRV', function(err,addresses) { + if(err) return resolveStandard(host,c); + if(addresses.length >= 1) { + var line = addresses[0]; + self.options.port = line.port; + var srvhost = line.name; + + if(srvhost.match(/\d+\.\d+\.\d+\.\d+/)) { + self.options.address = srvhost; + c(); + } else { + // resolve yet again + resolveStandard(srvhost,c); + } + return; + } + return resolveStandard(host,c); + }); + } + + if(this.srvRecord) resolveSrv(this.srvRecord,host,c); + else resolveStandard(host,c); + }, + + // utils + reader: function(buffer) { + return new Reader(this,buffer); + }, + translate: function(obj,trans) { + for(var from in trans) { + var to = trans[from]; + if(from in obj) { + if(to) obj[to] = obj[from]; + delete obj[from]; + } + } + }, + setTimeout: function(c,t) { + if(this.finished) return 0; + var id = setTimeout(c,t); + this.timers.push(id); + return id; + }, + + + + trueTest: function(str) { + if(typeof str == 'boolean') return str; + if(typeof str == 'number') return str != 0; + if(typeof str == 'string') { + if(str.toLowerCase() == 'true') return true; + if(str == 'yes') return true; + if(str == '1') return true; + } + return false; + }, + debugBuffer: function(buffer) { + var out = ''; + var out2 = ''; + for(var i = 0; i < buffer.length; i++) { + var sliced = buffer.slice(i,i+1); + out += sliced.toString('hex')+' '; + var chr = sliced.toString(); + if(chr < ' ' || chr > '~') chr = ' '; + out2 += chr+' '; + if(out.length > 60) { + console.log(out); + console.log(out2); + out = out2 = ''; + } + } + console.log(out); + console.log(out2); + }, + + + + + _tcpConnect: function(c) { + var self = this; + if(this.tcpSocket) return c(this.tcpSocket); + + var connected = false; + var received = new Buffer(0); + var address = this.options.address; + var port = this.options.port_query; + + var socket = this.tcpSocket = net.connect(port,address,function() { + if(self.debug) console.log(address+':'+port+" TCPCONNECTED"); + connected = true; + c(socket); + }); + socket.setTimeout(10000); + socket.setNoDelay(true); + if(this.debug) console.log(address+':'+port+" TCPCONNECT"); + + var writeHook = socket.write; + socket.write = function(data) { + if(self.debug) console.log(address+':'+port+" TCP--> "+data.toString('hex')); + writeHook.apply(this,arguments); + } + + socket.on('error', function() {}); + socket.on('close', function() { + if(!self.tcpCallback) return; + if(connected) return self.fatal('Socket closed while waiting on TCP'); + else return self.fatal('TCP Connection Refused'); + }); + socket.on('data', function(data) { + if(!self.tcpCallback) return; + if(self.debug) console.log(address+':'+port+" <--TCP "+data.toString('hex')); + received = Buffer.concat([received,data]); + if(self.tcpCallback(received)) { + clearTimeout(self.tcpTimeoutTimer); + self.tcpCallback = false; + received = new Buffer(0); + } + }); + }, + tcpSend: function(buffer,ondata) { + var self = this; + process.nextTick(function() { + if(self.tcpCallback) return self.fatal('Attempted to send TCP packet while still waiting on a managed response'); + self._tcpConnect(function(socket) { + socket.write(buffer); + }); + if(!ondata) return; + + self.tcpTimeoutTimer = self.setTimeout(function() { + self.tcpCallback = false; + self.fatal('TCP Watchdog Timeout'); + },self.options.tcpTimeout); + self.tcpCallback = ondata; + }); + }, + + + + udpSend: function(buffer,onpacket,ontimeout) { + var self = this; + process.nextTick(function() { + if(self.udpCallback) return self.fatal('Attempted to send UDP packet while still waiting on a managed response'); + self._udpSendNow(buffer); + if(!onpacket) return; + + self.udpTimeoutTimer = self.setTimeout(function() { + self.udpCallback = false; + var timeout = false; + if(!ontimeout || ontimeout() !== true) timeout = true; + if(timeout) self.fatal('UDP Watchdog Timeout'); + },self.options.udpTimeout); + self.udpCallback = onpacket; + }); + }, + _udpSendNow: function(buffer) { + if(!('port_query' in this.options)) return this.fatal('Attempted to send without setting a port'); + if(!('address' in this.options)) return this.fatal('Attempted to send without setting an address'); + + if(typeof buffer == 'string') buffer = new Buffer(buffer,'binary'); + + if(this.debug) console.log(this.options.address+':'+this.options.port_query+" UDP--> "+buffer.toString('hex')); + this.udpSocket.send(buffer,0,buffer.length,this.options.port_query,this.options.address); + }, + _udpResponse: function(buffer) { + if(this.udpCallback) { + var result = this.udpCallback(buffer); + if(result === true) { + // we're done with this udp session + clearTimeout(this.udpTimeoutTimer); + this.udpCallback = false; + } + } else { + this.udpResponse(buffer); + } + }, + udpResponse: function() {} +}); diff --git a/protocols/doom3.js b/protocols/doom3.js index 9379fba..ab8cbdb 100644 --- a/protocols/doom3.js +++ b/protocols/doom3.js @@ -1,95 +1,95 @@ -module.exports = require('./core').extend({ - init: function() { - this._super(); - this.pretty = 'Doom 3'; - this.encoding = 'latin1'; - this.isEtqw = false; - this.hasSpaceBeforeClanTag = false; - this.hasClanTag = false; - this.hasTypeFlag = false; - }, - run: function(state) { - var self = this; - - this.udpSend('\xff\xffgetInfo\x00PiNGPoNG\x00',function(buffer) { - var reader = self.reader(buffer); - - var header = reader.uint(2); - if(header != 0xffff) return; - var header2 = reader.string(); - if(header2 != 'infoResponse') return; - - var tailSize = 5; - if(self.isEtqw) { - var taskId = reader.uint(4); - } - - var challenge = reader.uint(4); - var protoVersion = reader.uint(4); - state.raw.protocolVersion = (protoVersion>>16)+'.'+(protoVersion&0xffff); - - if(self.isEtqw) { - var size = reader.uint(4); - } - - while(!reader.done()) { - var key = reader.string(); - var value = self.stripColors(reader.string()); - if(key == 'si_map') { - value = value.replace('maps/',''); - value = value.replace('.entities',''); - } - if(!key) break; - state.raw[key] = value; - } - - var i = 0; - while(!reader.done()) { - i++; - var player = {}; - player.id = reader.uint(1); - if(player.id == 32) break; - player.ping = reader.uint(2); - if(!self.isEtqw) player.rate = reader.uint(4); - player.name = self.stripColors(reader.string()); - if(self.hasClanTag) { - if(self.hasSpaceBeforeClanTag) reader.uint(1); - player.clantag = self.stripColors(reader.string()); - } - if(self.hasTypeFlag) player.typeflag = reader.uint(1); - - if(!player.ping || player.typeflag) - state.bots.push(player); - else - state.players.push(player); - } - - state.raw.osmask = reader.uint(4); - if(self.isEtqw) { - state.raw.ranked = reader.uint(1); - state.raw.timeleft = reader.uint(4); - state.raw.gamestate = reader.uint(1); - state.raw.servertype = reader.uint(1); - // 0 = regular, 1 = tv - if(state.raw.servertype == 0) { - state.raw.interestedClients = reader.uint(1); - } else if(state.raw.servertype == 1) { - state.raw.connectedClients = reader.uint(4); - state.raw.maxClients = reader.uint(4); - } - } - - if(state.raw.si_name) state.name = state.raw.si_name; - if(state.raw.si_map) state.map = state.raw.si_map; - if(state.raw.si_maxplayers) state.maxplayers = parseInt(state.raw.si_maxplayers); - if(state.raw.si_usepass == '1') state.password = true; - - self.finish(state); - return true; - }); - }, - stripColors: function(str) { - // uses quake 3 color codes - return str.replace(/\^(X.{6}|.)/g,''); - } -}); +module.exports = require('./core').extend({ + init: function() { + this._super(); + this.pretty = 'Doom 3'; + this.encoding = 'latin1'; + this.isEtqw = false; + this.hasSpaceBeforeClanTag = false; + this.hasClanTag = false; + this.hasTypeFlag = false; + }, + run: function(state) { + var self = this; + + this.udpSend('\xff\xffgetInfo\x00PiNGPoNG\x00',function(buffer) { + var reader = self.reader(buffer); + + var header = reader.uint(2); + if(header != 0xffff) return; + var header2 = reader.string(); + if(header2 != 'infoResponse') return; + + var tailSize = 5; + if(self.isEtqw) { + var taskId = reader.uint(4); + } + + var challenge = reader.uint(4); + var protoVersion = reader.uint(4); + state.raw.protocolVersion = (protoVersion>>16)+'.'+(protoVersion&0xffff); + + if(self.isEtqw) { + var size = reader.uint(4); + } + + while(!reader.done()) { + var key = reader.string(); + var value = self.stripColors(reader.string()); + if(key == 'si_map') { + value = value.replace('maps/',''); + value = value.replace('.entities',''); + } + if(!key) break; + state.raw[key] = value; + } + + var i = 0; + while(!reader.done()) { + i++; + var player = {}; + player.id = reader.uint(1); + if(player.id == 32) break; + player.ping = reader.uint(2); + if(!self.isEtqw) player.rate = reader.uint(4); + player.name = self.stripColors(reader.string()); + if(self.hasClanTag) { + if(self.hasSpaceBeforeClanTag) reader.uint(1); + player.clantag = self.stripColors(reader.string()); + } + if(self.hasTypeFlag) player.typeflag = reader.uint(1); + + if(!player.ping || player.typeflag) + state.bots.push(player); + else + state.players.push(player); + } + + state.raw.osmask = reader.uint(4); + if(self.isEtqw) { + state.raw.ranked = reader.uint(1); + state.raw.timeleft = reader.uint(4); + state.raw.gamestate = reader.uint(1); + state.raw.servertype = reader.uint(1); + // 0 = regular, 1 = tv + if(state.raw.servertype == 0) { + state.raw.interestedClients = reader.uint(1); + } else if(state.raw.servertype == 1) { + state.raw.connectedClients = reader.uint(4); + state.raw.maxClients = reader.uint(4); + } + } + + if(state.raw.si_name) state.name = state.raw.si_name; + if(state.raw.si_map) state.map = state.raw.si_map; + if(state.raw.si_maxplayers) state.maxplayers = parseInt(state.raw.si_maxplayers); + if(state.raw.si_usepass == '1') state.password = true; + + self.finish(state); + return true; + }); + }, + stripColors: function(str) { + // uses quake 3 color codes + return str.replace(/\^(X.{6}|.)/g,''); + } +}); diff --git a/protocols/gamespy1.js b/protocols/gamespy1.js index f2379c4..5e61dff 100644 --- a/protocols/gamespy1.js +++ b/protocols/gamespy1.js @@ -1,85 +1,85 @@ -var async = require('async'); - -module.exports = require('./core').extend({ - init: function() { - this._super(); - this.sessionId = 1; - this.encoding = 'latin1'; - this.byteorder = 'be'; - }, - run: function(state) { - var self = this; - - async.series([ - function(c) { - self.sendPacket('info', function(data) { - state.raw = data; - if('hostname' in state.raw) state.name = state.raw.hostname; - if('mapname' in state.raw) state.map = state.raw.mapname; - if(self.trueTest(state.raw.password)) state.password = true; - if('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers); - c(); - }); - }, - function(c) { - self.sendPacket('rules', function(data) { - state.raw.rules = data; - c(); - }); - }, - function(c) { - self.sendPacket('players', function(data) { - var players = {}; - var teams = {}; - for(var ident in data) { - var split = ident.split('_'); - var key = split[0]; - var id = split[1]; - var value = data[ident]; - - if(key == 'teamname') { - teams[id] = value; - } else { - if(!(id in players)) players[id] = {}; - if(key == 'playername') key = 'name'; - else if(key == 'team') value = parseInt(value); - else if(key == 'score' || key == 'ping' || key == 'deaths') value = parseInt(value); - players[id][key] = value; - } - } - - state.raw.teams = teams; - for(var i in players) state.players.push(players[i]); - self.finish(state); - }); - } - ]); - - }, - sendPacket: function(type,callback) { - var self = this; - var queryId = ''; - var output = {}; - this.udpSend('\\'+type+'\\',function(buffer) { - var reader = self.reader(buffer); - var str = reader.string({length:buffer.length}); - var split = str.split('\\'); - split.shift(); - var data = {}; - while(split.length) { - var key = split.shift(); - var value = split.shift() || ''; - data[key] = value; - } - if(!('queryid' in data)) return; - if(queryId && data.queryid != queryId) return; - for(var i in data) output[i] = data[i]; - if('final' in output) { - delete output.final; - delete output.queryid; - callback(output); - return true; - } - }); - } -}); +var async = require('async'); + +module.exports = require('./core').extend({ + init: function() { + this._super(); + this.sessionId = 1; + this.encoding = 'latin1'; + this.byteorder = 'be'; + }, + run: function(state) { + var self = this; + + async.series([ + function(c) { + self.sendPacket('info', function(data) { + state.raw = data; + if('hostname' in state.raw) state.name = state.raw.hostname; + if('mapname' in state.raw) state.map = state.raw.mapname; + if(self.trueTest(state.raw.password)) state.password = true; + if('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers); + c(); + }); + }, + function(c) { + self.sendPacket('rules', function(data) { + state.raw.rules = data; + c(); + }); + }, + function(c) { + self.sendPacket('players', function(data) { + var players = {}; + var teams = {}; + for(var ident in data) { + var split = ident.split('_'); + var key = split[0]; + var id = split[1]; + var value = data[ident]; + + if(key == 'teamname') { + teams[id] = value; + } else { + if(!(id in players)) players[id] = {}; + if(key == 'playername') key = 'name'; + else if(key == 'team') value = parseInt(value); + else if(key == 'score' || key == 'ping' || key == 'deaths') value = parseInt(value); + players[id][key] = value; + } + } + + state.raw.teams = teams; + for(var i in players) state.players.push(players[i]); + self.finish(state); + }); + } + ]); + + }, + sendPacket: function(type,callback) { + var self = this; + var queryId = ''; + var output = {}; + this.udpSend('\\'+type+'\\',function(buffer) { + var reader = self.reader(buffer); + var str = reader.string({length:buffer.length}); + var split = str.split('\\'); + split.shift(); + var data = {}; + while(split.length) { + var key = split.shift(); + var value = split.shift() || ''; + data[key] = value; + } + if(!('queryid' in data)) return; + if(queryId && data.queryid != queryId) return; + for(var i in data) output[i] = data[i]; + if('final' in output) { + delete output.final; + delete output.queryid; + callback(output); + return true; + } + }); + } +}); diff --git a/protocols/gamespy2.js b/protocols/gamespy2.js index 86b4596..0b21d63 100644 --- a/protocols/gamespy2.js +++ b/protocols/gamespy2.js @@ -1,96 +1,96 @@ -module.exports = require('./core').extend({ - init: function() { - this._super(); - this.sessionId = 1; - this.encoding = 'latin1'; - this.byteorder = 'be'; - }, - run: function(state) { - var self = this; - - var request = new Buffer([0xfe,0xfd,0x00,0x00,0x00,0x00,0x01,0xff,0xff,0xff]); - var packets = []; - this.udpSend(request, - function(buffer) { - if(packets.length && buffer.readUInt8(0) == 0) - buffer = buffer.slice(1); - packets.push(buffer); - }, - function() { - var buffer = Buffer.concat(packets); - var reader = self.reader(buffer); - var header = reader.uint(1); - if(header != 0) return; - var pingId = reader.uint(4); - if(pingId != 1) return; - - while(!reader.done()) { - var key = reader.string(); - var value = reader.string(); - if(!key) break; - state.raw[key] = value; - } - - if('hostname' in state.raw) state.name = state.raw.hostname; - if('mapname' in state.raw) state.map = state.raw.mapname; - if(self.trueTest(state.raw.password)) state.password = true; - if('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers); - - state.players = self.readFieldData(reader); - state.raw.teams = self.readFieldData(reader); - - self.finish(state); - return true; - } - ); - }, - readFieldData: function(reader) { - var count = reader.uint(1); - // count is unreliable (often it's wrong), so we don't use it. - // read until we hit an empty first field string - - if(this.debug) console.log("Reading fields, starting at: "+reader.rest()); - - var fields = []; - while(!reader.done()) { - var field = reader.string(); - if(!field) break; - if(field.charCodeAt(0) <= 2) field = field.substring(1); - fields.push(field); - if(this.debug) console.log("field:"+field); - } - - var units = []; - outer: while(!reader.done()) { - var unit = {}; - for(var iField = 0; iField < fields.length; iField++) { - var key = fields[iField]; - var value = reader.string(); - if(!value && iField == 0) break outer; - if(this.debug) console.log("value:"+value); - if(key == 'player_') key = 'name'; - else if(key == 'score_') key = 'score'; - else if(key == 'deaths_') key = 'deaths'; - else if(key == 'ping_') key = 'ping'; - else if(key == 'team_') key = 'team'; - else if(key == 'kills_') key = 'kills'; - else if(key == 'team_t') key = 'name'; - else if(key == 'tickets_t') key = 'tickets'; - - if( - key == 'score' || key == 'deaths' - || key == 'ping' || key == 'team' - || key == 'kills' || key == 'tickets' - ) { - if(value === '') continue; - value = parseInt(value); - } - - unit[key] = value; - } - units.push(unit); - } - - return units; - } -}); +module.exports = require('./core').extend({ + init: function() { + this._super(); + this.sessionId = 1; + this.encoding = 'latin1'; + this.byteorder = 'be'; + }, + run: function(state) { + var self = this; + + var request = new Buffer([0xfe,0xfd,0x00,0x00,0x00,0x00,0x01,0xff,0xff,0xff]); + var packets = []; + this.udpSend(request, + function(buffer) { + if(packets.length && buffer.readUInt8(0) == 0) + buffer = buffer.slice(1); + packets.push(buffer); + }, + function() { + var buffer = Buffer.concat(packets); + var reader = self.reader(buffer); + var header = reader.uint(1); + if(header != 0) return; + var pingId = reader.uint(4); + if(pingId != 1) return; + + while(!reader.done()) { + var key = reader.string(); + var value = reader.string(); + if(!key) break; + state.raw[key] = value; + } + + if('hostname' in state.raw) state.name = state.raw.hostname; + if('mapname' in state.raw) state.map = state.raw.mapname; + if(self.trueTest(state.raw.password)) state.password = true; + if('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers); + + state.players = self.readFieldData(reader); + state.raw.teams = self.readFieldData(reader); + + self.finish(state); + return true; + } + ); + }, + readFieldData: function(reader) { + var count = reader.uint(1); + // count is unreliable (often it's wrong), so we don't use it. + // read until we hit an empty first field string + + if(this.debug) console.log("Reading fields, starting at: "+reader.rest()); + + var fields = []; + while(!reader.done()) { + var field = reader.string(); + if(!field) break; + if(field.charCodeAt(0) <= 2) field = field.substring(1); + fields.push(field); + if(this.debug) console.log("field:"+field); + } + + var units = []; + outer: while(!reader.done()) { + var unit = {}; + for(var iField = 0; iField < fields.length; iField++) { + var key = fields[iField]; + var value = reader.string(); + if(!value && iField == 0) break outer; + if(this.debug) console.log("value:"+value); + if(key == 'player_') key = 'name'; + else if(key == 'score_') key = 'score'; + else if(key == 'deaths_') key = 'deaths'; + else if(key == 'ping_') key = 'ping'; + else if(key == 'team_') key = 'team'; + else if(key == 'kills_') key = 'kills'; + else if(key == 'team_t') key = 'name'; + else if(key == 'tickets_t') key = 'tickets'; + + if( + key == 'score' || key == 'deaths' + || key == 'ping' || key == 'team' + || key == 'kills' || key == 'tickets' + ) { + if(value === '') continue; + value = parseInt(value); + } + + unit[key] = value; + } + units.push(unit); + } + + return units; + } +}); diff --git a/protocols/gamespy3.js b/protocols/gamespy3.js index e2dcaa3..17ea3f1 100644 --- a/protocols/gamespy3.js +++ b/protocols/gamespy3.js @@ -1,168 +1,168 @@ -var async = require('async'); - -module.exports = require('./core').extend({ - init: function() { - this._super(); - this.sessionId = 1; - this.encoding = 'latin1'; - this.byteorder = 'be'; - this.noChallenge = false; - this.useOnlySingleSplit = false; - }, - run: function(state) { - var self = this; - var challenge,packets; - - async.series([ - function(c) { - if(self.noChallenge) return c(); - self.sendPacket(9,false,false,false,function(buffer) { - var reader = self.reader(buffer); - challenge = parseInt(reader.string()); - c(); - }); - }, - function(c) { - self.sendPacket(0,challenge,new Buffer([0xff,0xff,0xff,0x01]),true,function(b) { - packets = b; - c(); - }); - }, - function(c) { - // iterate over the received packets - // the first packet will start off with k/v pairs, followed with data fields - // the following packets will only have data fields - var data = {}; - - for(var iPacket = 0; iPacket < packets.length; iPacket++) { - var packet = packets[iPacket]; - var reader = self.reader(packet); - - if(self.debug) { - console.log("+++"+packet.toString('hex')); - console.log(":::"+packet.toString('ascii')); - } - - if(iPacket == 0) { - while(!reader.done()) { - var key = reader.string(); - if(!key) break; - var value = reader.string(); - - // reread the next line if we hit the weird ut3 bug - if(value == 'p1073741829') value = reader.string(); - - state.raw[key] = value; - } - } - - var firstMode = true; - while(!reader.done()) { - var mode = reader.string(); - if(mode.charCodeAt(0) <= 2) mode = mode.substring(1); - if(!mode) continue; - var offset = 0; - if(iPacket != 0 && firstMode) offset = reader.uint(1); - reader.skip(1); - firstMode = false; - - while(!reader.done()) { - var item = reader.string(); - if(!item) break; - - if( - mode == 'player_' - || mode == 'score_' - || mode == 'ping_' - || mode == 'team_' - || mode == 'deaths_' - || mode == 'pid_' - ) { - if(state.players.length <= offset) - state.players.push({}); - } - if(mode == 'player_') state.players[offset].name = item; - if(mode == 'score_') state.players[offset].score = parseInt(item); - if(mode == 'ping_') state.players[offset].ping = parseInt(item); - if(mode == 'team_') state.players[offset].team = parseInt(item); - if(mode == 'deaths_') state.players[offset].deaths = parseInt(item); - if(mode == 'pid_') state.players[offset].pid = item; - offset++; - } - } - } - - if('hostname' in state.raw) state.name = state.raw.hostname; - else if('servername' in state.raw) state.name = state.raw.servername; - if('mapname' in state.raw) state.map = state.raw.mapname; - if(state.raw.password == '1') state.password = true; - if('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers); - - self.finish(state); - } - ]); - }, - sendPacket: function(type,challenge,payload,assemble,c) { - var self = this; - - var challengeLength = (this.noChallenge || challenge === false) ? 0 : 4; - var payloadLength = payload ? payload.length : 0; - - var b = new Buffer(7 + challengeLength + payloadLength); - b.writeUInt8(0xFE, 0); - b.writeUInt8(0xFD, 1); - b.writeUInt8(type, 2); - b.writeUInt32BE(this.sessionId, 3); - if(challengeLength) b.writeInt32BE(challenge, 7); - if(payloadLength) payload.copy(b, 7+challengeLength); - - var numPackets = 0; - var packets = {}; - this.udpSend(b,function(buffer) { - var reader = self.reader(buffer); - var iType = reader.uint(1); - if(iType != type) return; - var iSessionId = reader.uint(4); - if(iSessionId != self.sessionId) return; - - if(!assemble) { - c(reader.rest()); - return true; - } - if(self.useOnlySingleSplit) { - // has split headers, but they are worthless and only one packet is used - reader.skip(11); - c([reader.rest()]); - return true; - } - - reader.skip(9); // filler data -- usually set to 'splitnum\0' - var id = reader.uint(1); - var last = (id & 0x80); - id = id & 0x7f; - if(last) numPackets = id+1; - - reader.skip(1); // "another 'packet number' byte, but isn't understood." - - packets[id] = reader.rest(); - if(self.debug) { - console.log("Received packet #"+id); - if(last) console.log("(last)"); - } - - if(!numPackets || Object.keys(packets).length != numPackets) return; - - // assemble the parts - var list = []; - for(var i = 0; i < numPackets; i++) { - if(!(i in packets)) { - self.fatal('Missing packet #'+i); - return true; - } - list.push(packets[i]); - } - c(list); - return true; - }); - } -}); +var async = require('async'); + +module.exports = require('./core').extend({ + init: function() { + this._super(); + this.sessionId = 1; + this.encoding = 'latin1'; + this.byteorder = 'be'; + this.noChallenge = false; + this.useOnlySingleSplit = false; + }, + run: function(state) { + var self = this; + var challenge,packets; + + async.series([ + function(c) { + if(self.noChallenge) return c(); + self.sendPacket(9,false,false,false,function(buffer) { + var reader = self.reader(buffer); + challenge = parseInt(reader.string()); + c(); + }); + }, + function(c) { + self.sendPacket(0,challenge,new Buffer([0xff,0xff,0xff,0x01]),true,function(b) { + packets = b; + c(); + }); + }, + function(c) { + // iterate over the received packets + // the first packet will start off with k/v pairs, followed with data fields + // the following packets will only have data fields + var data = {}; + + for(var iPacket = 0; iPacket < packets.length; iPacket++) { + var packet = packets[iPacket]; + var reader = self.reader(packet); + + if(self.debug) { + console.log("+++"+packet.toString('hex')); + console.log(":::"+packet.toString('ascii')); + } + + if(iPacket == 0) { + while(!reader.done()) { + var key = reader.string(); + if(!key) break; + var value = reader.string(); + + // reread the next line if we hit the weird ut3 bug + if(value == 'p1073741829') value = reader.string(); + + state.raw[key] = value; + } + } + + var firstMode = true; + while(!reader.done()) { + var mode = reader.string(); + if(mode.charCodeAt(0) <= 2) mode = mode.substring(1); + if(!mode) continue; + var offset = 0; + if(iPacket != 0 && firstMode) offset = reader.uint(1); + reader.skip(1); + firstMode = false; + + while(!reader.done()) { + var item = reader.string(); + if(!item) break; + + if( + mode == 'player_' + || mode == 'score_' + || mode == 'ping_' + || mode == 'team_' + || mode == 'deaths_' + || mode == 'pid_' + ) { + if(state.players.length <= offset) + state.players.push({}); + } + if(mode == 'player_') state.players[offset].name = item; + if(mode == 'score_') state.players[offset].score = parseInt(item); + if(mode == 'ping_') state.players[offset].ping = parseInt(item); + if(mode == 'team_') state.players[offset].team = parseInt(item); + if(mode == 'deaths_') state.players[offset].deaths = parseInt(item); + if(mode == 'pid_') state.players[offset].pid = item; + offset++; + } + } + } + + if('hostname' in state.raw) state.name = state.raw.hostname; + else if('servername' in state.raw) state.name = state.raw.servername; + if('mapname' in state.raw) state.map = state.raw.mapname; + if(state.raw.password == '1') state.password = true; + if('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers); + + self.finish(state); + } + ]); + }, + sendPacket: function(type,challenge,payload,assemble,c) { + var self = this; + + var challengeLength = (this.noChallenge || challenge === false) ? 0 : 4; + var payloadLength = payload ? payload.length : 0; + + var b = new Buffer(7 + challengeLength + payloadLength); + b.writeUInt8(0xFE, 0); + b.writeUInt8(0xFD, 1); + b.writeUInt8(type, 2); + b.writeUInt32BE(this.sessionId, 3); + if(challengeLength) b.writeInt32BE(challenge, 7); + if(payloadLength) payload.copy(b, 7+challengeLength); + + var numPackets = 0; + var packets = {}; + this.udpSend(b,function(buffer) { + var reader = self.reader(buffer); + var iType = reader.uint(1); + if(iType != type) return; + var iSessionId = reader.uint(4); + if(iSessionId != self.sessionId) return; + + if(!assemble) { + c(reader.rest()); + return true; + } + if(self.useOnlySingleSplit) { + // has split headers, but they are worthless and only one packet is used + reader.skip(11); + c([reader.rest()]); + return true; + } + + reader.skip(9); // filler data -- usually set to 'splitnum\0' + var id = reader.uint(1); + var last = (id & 0x80); + id = id & 0x7f; + if(last) numPackets = id+1; + + reader.skip(1); // "another 'packet number' byte, but isn't understood." + + packets[id] = reader.rest(); + if(self.debug) { + console.log("Received packet #"+id); + if(last) console.log("(last)"); + } + + if(!numPackets || Object.keys(packets).length != numPackets) return; + + // assemble the parts + var list = []; + for(var i = 0; i < numPackets; i++) { + if(!(i in packets)) { + self.fatal('Missing packet #'+i); + return true; + } + list.push(packets[i]); + } + c(list); + return true; + }); + } +}); diff --git a/protocols/m2mp.js b/protocols/m2mp.js index 5522a14..8125301 100644 --- a/protocols/m2mp.js +++ b/protocols/m2mp.js @@ -1,37 +1,37 @@ -module.exports = require('./core').extend({ - init: function() { - this._super(); - this.encoding = 'latin1'; - }, - run: function(state) { - var self = this; - - this.udpSend('M2MP',function(buffer) { - var reader = self.reader(buffer); - - var header = reader.string({length:4}); - if(header != 'M2MP') return; - - state.name = self.readString(reader); - state.raw.numplayers = self.readString(reader); - state.maxplayers = self.readString(reader); - state.raw.gamemode = self.readString(reader); - state.password = !!reader.uint(1); - - while(!reader.done()) { - var name = self.readString(reader); - if(!name) break; - state.players.push({ - name:name - }); - } - - self.finish(state); - return true; - }); - }, - readString: function(reader) { - var length = reader.uint(1); - return reader.string({length:length-1}); - }, -}); +module.exports = require('./core').extend({ + init: function() { + this._super(); + this.encoding = 'latin1'; + }, + run: function(state) { + var self = this; + + this.udpSend('M2MP',function(buffer) { + var reader = self.reader(buffer); + + var header = reader.string({length:4}); + if(header != 'M2MP') return; + + state.name = self.readString(reader); + state.raw.numplayers = self.readString(reader); + state.maxplayers = self.readString(reader); + state.raw.gamemode = self.readString(reader); + state.password = !!reader.uint(1); + + while(!reader.done()) { + var name = self.readString(reader); + if(!name) break; + state.players.push({ + name:name + }); + } + + self.finish(state); + return true; + }); + }, + readString: function(reader) { + var length = reader.uint(1); + return reader.string({length:length-1}); + }, +}); diff --git a/protocols/minecraftping.js b/protocols/minecraftping.js index 150312f..1e1d2b0 100644 --- a/protocols/minecraftping.js +++ b/protocols/minecraftping.js @@ -1,93 +1,93 @@ -var varint = require('varint'), - async = require('async'); - -function varIntBuffer(num) { - return new Buffer(varint.encode(num)); -} -function buildPacket(id,data) { - if(!data) data = new Buffer(0); - var idBuffer = varIntBuffer(id); - return Buffer.concat([ - varIntBuffer(data.length+idBuffer.length), - idBuffer, - data - ]); -} - -module.exports = require('./core').extend({ - run: function(state) { - var self = this; - var receivedData; - - async.series([ - function(c) { - // build and send handshake and status TCP packet - - var portBuf = new Buffer(2); - portBuf.writeUInt16BE(self.options.port_query,0); - - var addressBuf = new Buffer(self.options.address,'utf8'); - - var bufs = [ - varIntBuffer(4), - varIntBuffer(addressBuf.length), - addressBuf, - portBuf, - varIntBuffer(1) - ]; - - var outBuffer = Buffer.concat([ - buildPacket(0,Buffer.concat(bufs)), - buildPacket(0) - ]); - - self.tcpSend(outBuffer, function(data) { - if(data.length < 10) return false; - var expected = varint.decode(data); - data = data.slice(varint.decode.bytesRead); - if(data.length < expected) return false; - receivedData = data; - c(); - return true; - }); - }, - function(c) { - // parse response - - var data = receivedData; - var packetId = varint.decode(data); - data = data.slice(varint.decode.bytesRead); - - var strLen = varint.decode(data); - data = data.slice(varint.decode.bytesRead); - - var str = data.toString('utf8'); - var json; - try { - json = JSON.parse(str); - delete json.favicon; - if(self.debug) console.log(json); - } catch(e) { - return self.fatal('Invalid JSON'); - } - - state.raw.version = json.version.name; - state.maxplayers = json.players.max; - state.raw.description = json.description.text; - if(json.players.sample) { - for(var i = 0; i < json.players.sample.length; i++) { - state.players.push({ - id: json.players.sample[i].id, - name: json.players.sample[i].name - }); - } - } - while(state.players.length < json.players.online) { - state.players.push({}); - } - - self.finish(state); - } - ]); - } -}); +var varint = require('varint'), + async = require('async'); + +function varIntBuffer(num) { + return new Buffer(varint.encode(num)); +} +function buildPacket(id,data) { + if(!data) data = new Buffer(0); + var idBuffer = varIntBuffer(id); + return Buffer.concat([ + varIntBuffer(data.length+idBuffer.length), + idBuffer, + data + ]); +} + +module.exports = require('./core').extend({ + run: function(state) { + var self = this; + var receivedData; + + async.series([ + function(c) { + // build and send handshake and status TCP packet + + var portBuf = new Buffer(2); + portBuf.writeUInt16BE(self.options.port_query,0); + + var addressBuf = new Buffer(self.options.address,'utf8'); + + var bufs = [ + varIntBuffer(4), + varIntBuffer(addressBuf.length), + addressBuf, + portBuf, + varIntBuffer(1) + ]; + + var outBuffer = Buffer.concat([ + buildPacket(0,Buffer.concat(bufs)), + buildPacket(0) + ]); + + self.tcpSend(outBuffer, function(data) { + if(data.length < 10) return false; + var expected = varint.decode(data); + data = data.slice(varint.decode.bytesRead); + if(data.length < expected) return false; + receivedData = data; + c(); + return true; + }); + }, + function(c) { + // parse response + + var data = receivedData; + var packetId = varint.decode(data); + data = data.slice(varint.decode.bytesRead); + + var strLen = varint.decode(data); + data = data.slice(varint.decode.bytesRead); + + var str = data.toString('utf8'); + var json; + try { + json = JSON.parse(str); + delete json.favicon; + if(self.debug) console.log(json); + } catch(e) { + return self.fatal('Invalid JSON'); + } + + state.raw.version = json.version.name; + state.maxplayers = json.players.max; + state.raw.description = json.description.text; + if(json.players.sample) { + for(var i = 0; i < json.players.sample.length; i++) { + state.players.push({ + id: json.players.sample[i].id, + name: json.players.sample[i].name + }); + } + } + while(state.players.length < json.players.online) { + state.players.push({}); + } + + self.finish(state); + } + ]); + } +}); diff --git a/protocols/mumble.js b/protocols/mumble.js index c4313fd..2ddb313 100644 --- a/protocols/mumble.js +++ b/protocols/mumble.js @@ -1,44 +1,44 @@ -var async = require('async'); - -module.exports = require('./core').extend({ - init: function() { - this._super(); - this.options.tcpTimeout = 5000; - }, - run: function(state) { - var self = this; - - this.tcpSend('json', function(buffer) { - if(buffer.length < 10) return; - var str = buffer.toString(); - var json; - try { - json = JSON.parse(str); - } catch(e) { - // probably not all here yet - return; - } - - state.raw = json; - state.name = json.name; - - var channelStack = [state.raw.root]; - while(channelStack.length) { - var channel = channelStack.shift(); - channel.description = self.cleanComment(channel.description); - channelStack = channelStack.concat(channel.channels); - for(var i = 0; i < channel.users.length; i++) { - var user = channel.users[i]; - user.comment = self.cleanComment(user.comment); - state.players.push(user); - } - } - - self.finish(state); - return true; - }); - }, - cleanComment: function(str) { - return str.replace(/<.*>/g,''); - } -}); +var async = require('async'); + +module.exports = require('./core').extend({ + init: function() { + this._super(); + this.options.tcpTimeout = 5000; + }, + run: function(state) { + var self = this; + + this.tcpSend('json', function(buffer) { + if(buffer.length < 10) return; + var str = buffer.toString(); + var json; + try { + json = JSON.parse(str); + } catch(e) { + // probably not all here yet + return; + } + + state.raw = json; + state.name = json.name; + + var channelStack = [state.raw.root]; + while(channelStack.length) { + var channel = channelStack.shift(); + channel.description = self.cleanComment(channel.description); + channelStack = channelStack.concat(channel.channels); + for(var i = 0; i < channel.users.length; i++) { + var user = channel.users[i]; + user.comment = self.cleanComment(user.comment); + state.players.push(user); + } + } + + self.finish(state); + return true; + }); + }, + cleanComment: function(str) { + return str.replace(/<.*>/g,''); + } +}); diff --git a/protocols/mumbleping.js b/protocols/mumbleping.js index acac0b8..c0ef63b 100644 --- a/protocols/mumbleping.js +++ b/protocols/mumbleping.js @@ -1,29 +1,29 @@ -var async = require('async'); - -module.exports = require('./core').extend({ - init: function() { - this._super(); - this.byteorder = 'be'; - }, - run: function(state) { - var self = this; - - this.udpSend('\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08', function(buffer) { - if(buffer.length < 24) return; - var reader = self.reader(buffer); - reader.skip(1); - state.raw.versionMajor = reader.uint(1); - state.raw.versionMinor = reader.uint(1); - state.raw.versionPatch = reader.uint(1); - reader.skip(8); - state.raw.numplayers = reader.uint(4); - state.maxplayers = reader.uint(4); - state.raw.allowedbandwidth = reader.uint(4); - for(var i = 0; i < state.raw.numplayers; i++) { - state.players.push({}); - } - self.finish(state); - return true; - }); - } -}); +var async = require('async'); + +module.exports = require('./core').extend({ + init: function() { + this._super(); + this.byteorder = 'be'; + }, + run: function(state) { + var self = this; + + this.udpSend('\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08', function(buffer) { + if(buffer.length < 24) return; + var reader = self.reader(buffer); + reader.skip(1); + state.raw.versionMajor = reader.uint(1); + state.raw.versionMinor = reader.uint(1); + state.raw.versionPatch = reader.uint(1); + reader.skip(8); + state.raw.numplayers = reader.uint(4); + state.maxplayers = reader.uint(4); + state.raw.allowedbandwidth = reader.uint(4); + for(var i = 0; i < state.raw.numplayers; i++) { + state.players.push({}); + } + self.finish(state); + return true; + }); + } +}); diff --git a/protocols/mutantfactions.js b/protocols/mutantfactions.js index bfff46a..53ccc8e 100644 --- a/protocols/mutantfactions.js +++ b/protocols/mutantfactions.js @@ -1,54 +1,54 @@ -var request = require('request'); - -module.exports = require('./protocols/core').extend({ - run: function(state) { - var self = this; - request({ - uri: 'http://mutantfactions.net/game/receiveLobby.php', - timeout: 3000, - }, function(e,r,body) { - if(e) return self.fatal('Lobby request error'); - - var split = body.split('
'); - - var found = false; - for(var i = 0; i < split.length; i++) { - var line = split[i]; - var fields = line.split('::'); - var ip = fields[2]; - var port = fields[3]; - if(ip == self.options.address && port == self.options.port) { - found = fields; - break; - } - } - - if(!found) return self.fatal('Server not found in list'); - - state.raw.countrycode = fields[0]; - state.raw.country = fields[1]; - state.name = fields[4]; - state.map = fields[5]; - state.raw.numplayers = fields[6]; - state.maxplayers = fields[7]; - // fields[8] is unknown? - state.raw.rules = fields[9]; - state.raw.gamemode = fields[10]; - state.raw.gangsters = fields[11]; - state.raw.cashrate = fields[12]; - state.raw.missions = fields[13]; - state.raw.vehicles = fields[14]; - state.raw.customweapons = fields[15]; - state.raw.friendlyfire = fields[16]; - state.raw.mercs = fields[17]; - // fields[18] is unknown? listen server? - state.raw.version = fields[19]; - - for(var i = 0; i < state.raw.numplayers; i++) { - state.players.push({}); - } - - self.finish(state); - }); - } -}); +var request = require('request'); + +module.exports = require('./protocols/core').extend({ + run: function(state) { + var self = this; + request({ + uri: 'http://mutantfactions.net/game/receiveLobby.php', + timeout: 3000, + }, function(e,r,body) { + if(e) return self.fatal('Lobby request error'); + + var split = body.split('
'); + + var found = false; + for(var i = 0; i < split.length; i++) { + var line = split[i]; + var fields = line.split('::'); + var ip = fields[2]; + var port = fields[3]; + if(ip == self.options.address && port == self.options.port) { + found = fields; + break; + } + } + + if(!found) return self.fatal('Server not found in list'); + + state.raw.countrycode = fields[0]; + state.raw.country = fields[1]; + state.name = fields[4]; + state.map = fields[5]; + state.raw.numplayers = fields[6]; + state.maxplayers = fields[7]; + // fields[8] is unknown? + state.raw.rules = fields[9]; + state.raw.gamemode = fields[10]; + state.raw.gangsters = fields[11]; + state.raw.cashrate = fields[12]; + state.raw.missions = fields[13]; + state.raw.vehicles = fields[14]; + state.raw.customweapons = fields[15]; + state.raw.friendlyfire = fields[16]; + state.raw.mercs = fields[17]; + // fields[18] is unknown? listen server? + state.raw.version = fields[19]; + + for(var i = 0; i < state.raw.numplayers; i++) { + state.players.push({}); + } + + self.finish(state); + }); + } +}); diff --git a/protocols/nadeo.js b/protocols/nadeo.js index d3c6ff0..2cb6791 100644 --- a/protocols/nadeo.js +++ b/protocols/nadeo.js @@ -1,75 +1,75 @@ -var gbxremote = require('gbxremote'), - async = require('async'); - -module.exports = require('./core').extend({ - init: function() { - this._super(); - this.options.port = 2350; - this.options.port_query = 5000; - this.gbxclient = false; - }, - reset: function() { - this._super(); - if(this.gbxclient) { - this.gbxclient.terminate(); - this.gbxclient = false; - } - }, - run: function(state) { - var self = this; - - var cmds = [ - ['Connect'], - ['Authenticate', this.options.login,this.options.password], - ['GetStatus'], - ['GetPlayerList',500,0], - ['GetServerOptions'], - ['GetCurrentChallengeInfo'], - ['GetCurrentGameInfo'] - ]; - var results = []; - - async.eachSeries(cmds, function(cmdset,c) { - var cmd = cmdset[0]; - var params = cmdset.slice(1); - - if(cmd == 'Connect') { - var client = self.gbxclient = gbxremote.createClient(self.options.port_query,self.options.host, function(err) { - if(err) return self.fatal('GBX error '+JSON.stringify(err)); - c(); - }); - client.on('error',function(){}); - } else { - self.gbxclient.methodCall(cmd, params, function(err, value) { - if(err) return self.fatal('XMLRPC error '+JSON.stringify(err)); - results.push(value); - c(); - }); - } - }, function() { - var gamemode = ''; - var igm = results[5].GameMode; - if(igm == 0) gamemode="Rounds"; - if(igm == 1) gamemode="Time Attack"; - if(igm == 2) gamemode="Team"; - if(igm == 3) gamemode="Laps"; - if(igm == 4) gamemode="Stunts"; - if(igm == 5) gamemode="Cup"; - - state.name = self.stripColors(results[3].Name); - state.password = (results[3].Password != 'No password'); - state.maxplayers = results[3].CurrentMaxPlayers; - state.map = self.stripColors(results[4].Name); - state.raw.gametype = gamemode; - - results[2].forEach(function(player) { - state.players.push({name:self.stripColors(player.Name)}); - }); - - self.finish(state); - }); - }, - stripColors: function(str) { - return str.replace(/\$([0-9a-f][^\$]?[^\$]?|[^\$]?)/g,''); - } -}); +var gbxremote = require('gbxremote'), + async = require('async'); + +module.exports = require('./core').extend({ + init: function() { + this._super(); + this.options.port = 2350; + this.options.port_query = 5000; + this.gbxclient = false; + }, + reset: function() { + this._super(); + if(this.gbxclient) { + this.gbxclient.terminate(); + this.gbxclient = false; + } + }, + run: function(state) { + var self = this; + + var cmds = [ + ['Connect'], + ['Authenticate', this.options.login,this.options.password], + ['GetStatus'], + ['GetPlayerList',500,0], + ['GetServerOptions'], + ['GetCurrentChallengeInfo'], + ['GetCurrentGameInfo'] + ]; + var results = []; + + async.eachSeries(cmds, function(cmdset,c) { + var cmd = cmdset[0]; + var params = cmdset.slice(1); + + if(cmd == 'Connect') { + var client = self.gbxclient = gbxremote.createClient(self.options.port_query,self.options.host, function(err) { + if(err) return self.fatal('GBX error '+JSON.stringify(err)); + c(); + }); + client.on('error',function(){}); + } else { + self.gbxclient.methodCall(cmd, params, function(err, value) { + if(err) return self.fatal('XMLRPC error '+JSON.stringify(err)); + results.push(value); + c(); + }); + } + }, function() { + var gamemode = ''; + var igm = results[5].GameMode; + if(igm == 0) gamemode="Rounds"; + if(igm == 1) gamemode="Time Attack"; + if(igm == 2) gamemode="Team"; + if(igm == 3) gamemode="Laps"; + if(igm == 4) gamemode="Stunts"; + if(igm == 5) gamemode="Cup"; + + state.name = self.stripColors(results[3].Name); + state.password = (results[3].Password != 'No password'); + state.maxplayers = results[3].CurrentMaxPlayers; + state.map = self.stripColors(results[4].Name); + state.raw.gametype = gamemode; + + results[2].forEach(function(player) { + state.players.push({name:self.stripColors(player.Name)}); + }); + + self.finish(state); + }); + }, + stripColors: function(str) { + return str.replace(/\$([0-9a-f][^\$]?[^\$]?|[^\$]?)/g,''); + } +}); diff --git a/protocols/quake1.js b/protocols/quake1.js index 4b18c9a..fe338e4 100644 --- a/protocols/quake1.js +++ b/protocols/quake1.js @@ -1,7 +1,7 @@ -module.exports = require('./quake2').extend({ - init: function() { - this._super(); - this.responseHeader = 'n'; - this.isQuake1 = true; - } -}); +module.exports = require('./quake2').extend({ + init: function() { + this._super(); + this.responseHeader = 'n'; + this.isQuake1 = true; + } +}); diff --git a/protocols/quake2.js b/protocols/quake2.js index 802ad87..3fac1bb 100644 --- a/protocols/quake2.js +++ b/protocols/quake2.js @@ -1,85 +1,85 @@ -module.exports = require('./core').extend({ - init: function() { - this._super(); - this.encoding = 'latin1'; - this.delimiter = '\n'; - this.sendHeader = 'status'; - this.responseHeader = 'print'; - this.isQuake1 = false; - }, - run: function(state) { - var self = this; - - this.udpSend('\xff\xff\xff\xff'+this.sendHeader+'\x00',function(buffer) { - var reader = self.reader(buffer); - - var header = reader.string({length:4}); - if(header != '\xff\xff\xff\xff') return; - - var response; - if(this.isQuake1) { - response = reader.string({length:1}); - } else { - response = reader.string(); - } - if(response != this.responseHeader) return; - - var info = reader.string().split('\\'); - if(info[0] == '') info.shift(); - - while(true) { - var key = info.shift(); - var value = info.shift(); - if(typeof value == 'undefined') break; - state.raw[key] = value; - } - - while(!reader.done()) { - var line = reader.string(); - if(!line || line.charAt(0) == '\0') break; - - var args = []; - var split = line.split('"'); - var inQuote = false; - split.forEach(function(part,i) { - var inQuote = (i%2 == 1); - if(inQuote) { - args.push(part); - } else { - var splitSpace = part.split(' '); - splitSpace.forEach(function(subpart) { - if(subpart) args.push(subpart); - }); - } - }); - - var player = {}; - if(self.isQuake1) { - player.id = parseInt(args.shift()); - player.score = parseInt(args.shift()); - player.time = parseInt(args.shift()); - player.ping = parseInt(args.shift()); - player.name = args.shift(); - player.skin = args.shift(); - player.color1 = parseInt(args.shift()); - player.color2 = parseInt(args.shift()); - } else { - player.frags = parseInt(args.shift()); - player.ping = parseInt(args.shift()); - player.name = args.shift() || ''; - player.address = args.shift() || ''; - } - - (player.ping ? state.players : state.bots).push(player); - } - - if('g_needpass' in state.raw) state.password = state.raw.g_needpass; - if('mapname' in state.raw) state.map = state.raw.mapname; - if('sv_maxclients' in state.raw) state.maxplayers = state.raw.sv_maxclients; - if('sv_hostname' in state.raw) state.name = state.raw.sv_hostname; - - self.finish(state); - return true; - }); - } -}); +module.exports = require('./core').extend({ + init: function() { + this._super(); + this.encoding = 'latin1'; + this.delimiter = '\n'; + this.sendHeader = 'status'; + this.responseHeader = 'print'; + this.isQuake1 = false; + }, + run: function(state) { + var self = this; + + this.udpSend('\xff\xff\xff\xff'+this.sendHeader+'\x00',function(buffer) { + var reader = self.reader(buffer); + + var header = reader.string({length:4}); + if(header != '\xff\xff\xff\xff') return; + + var response; + if(this.isQuake1) { + response = reader.string({length:1}); + } else { + response = reader.string(); + } + if(response != this.responseHeader) return; + + var info = reader.string().split('\\'); + if(info[0] == '') info.shift(); + + while(true) { + var key = info.shift(); + var value = info.shift(); + if(typeof value == 'undefined') break; + state.raw[key] = value; + } + + while(!reader.done()) { + var line = reader.string(); + if(!line || line.charAt(0) == '\0') break; + + var args = []; + var split = line.split('"'); + var inQuote = false; + split.forEach(function(part,i) { + var inQuote = (i%2 == 1); + if(inQuote) { + args.push(part); + } else { + var splitSpace = part.split(' '); + splitSpace.forEach(function(subpart) { + if(subpart) args.push(subpart); + }); + } + }); + + var player = {}; + if(self.isQuake1) { + player.id = parseInt(args.shift()); + player.score = parseInt(args.shift()); + player.time = parseInt(args.shift()); + player.ping = parseInt(args.shift()); + player.name = args.shift(); + player.skin = args.shift(); + player.color1 = parseInt(args.shift()); + player.color2 = parseInt(args.shift()); + } else { + player.frags = parseInt(args.shift()); + player.ping = parseInt(args.shift()); + player.name = args.shift() || ''; + player.address = args.shift() || ''; + } + + (player.ping ? state.players : state.bots).push(player); + } + + if('g_needpass' in state.raw) state.password = state.raw.g_needpass; + if('mapname' in state.raw) state.map = state.raw.mapname; + if('sv_maxclients' in state.raw) state.maxplayers = state.raw.sv_maxclients; + if('sv_hostname' in state.raw) state.name = state.raw.sv_hostname; + + self.finish(state); + return true; + }); + } +}); diff --git a/protocols/quake3.js b/protocols/quake3.js index 7fc7990..90c2677 100644 --- a/protocols/quake3.js +++ b/protocols/quake3.js @@ -1,19 +1,19 @@ -module.exports = require('./quake2').extend({ - init: function() { - this._super(); - this.sendHeader = 'getstatus'; - this.responseHeader = 'statusResponse'; - }, - finalizeState: function(state) { - state.name = this.stripColors(state.name); - for(var i in state.raw) { - state.raw[i] = this.stripColors(state.raw[i]); - } - for(var i = 0; i < state.players.length; i++) { - state.players[i].name = this.stripColors(state.players[i].name); - } - }, - stripColors: function(str) { - return str.replace(/\^(X.{6}|.)/g,''); - } -}); +module.exports = require('./quake2').extend({ + init: function() { + this._super(); + this.sendHeader = 'getstatus'; + this.responseHeader = 'statusResponse'; + }, + finalizeState: function(state) { + state.name = this.stripColors(state.name); + for(var i in state.raw) { + state.raw[i] = this.stripColors(state.raw[i]); + } + for(var i = 0; i < state.players.length; i++) { + state.players[i].name = this.stripColors(state.players[i].name); + } + }, + stripColors: function(str) { + return str.replace(/\^(X.{6}|.)/g,''); + } +}); diff --git a/protocols/teamspeak2.js b/protocols/teamspeak2.js index a61e687..8321a2c 100644 --- a/protocols/teamspeak2.js +++ b/protocols/teamspeak2.js @@ -1,77 +1,77 @@ -var async = require('async'); - -module.exports = require('./core').extend({ - run: function(state) { - var self = this; - - async.series([ - function(c) { - self.sendCommand('sel '+self.options.port, function(data) { - if(data != '[TS]') self.fatal('Invalid header'); - c(); - }); - }, - function(c) { - self.sendCommand('si', function(data) { - var split = data.split('\r\n'); - split.forEach(function(line) { - var equals = line.indexOf('='); - var key = equals == -1 ? line : line.substr(0,equals); - var value = equals == -1 ? '' : line.substr(equals+1); - state.raw[key] = value; - }); - c(); - }); - }, - function(c) { - self.sendCommand('pl', function(data) { - var split = data.split('\r\n'); - var fields = split.shift().split('\t'); - split.forEach(function(line) { - var split2 = line.split('\t'); - var player = {}; - split2.forEach(function(value,i) { - var key = fields[i]; - if(!key) return; - if(key == 'nick') key = 'name'; - if(m = value.match(/^"(.*)"$/)) value = m[1]; - player[key] = value; - }); - state.players.push(player); - }); - c(); - }); - }, - function(c) { - self.sendCommand('cl', function(data) { - var split = data.split('\r\n'); - var fields = split.shift().split('\t'); - state.raw.channels = []; - split.forEach(function(line) { - var split2 = line.split('\t'); - var channel = {}; - split2.forEach(function(value,i) { - var key = fields[i]; - if(!key) return; - if(m = value.match(/^"(.*)"$/)) value = m[1]; - channel[key] = value; - }); - state.raw.channels.push(channel); - }); - c(); - }); - }, - function(c) { - self.finish(state); - } - ]); - }, - sendCommand: function(cmd,c) { - this.tcpSend(cmd+'\x0A', function(buffer) { - if(buffer.length < 6) return; - if(buffer.slice(-6).toString() != '\r\nOK\r\n') return; - c(buffer.slice(0,-6).toString()); - return true; - }); - } -}); +var async = require('async'); + +module.exports = require('./core').extend({ + run: function(state) { + var self = this; + + async.series([ + function(c) { + self.sendCommand('sel '+self.options.port, function(data) { + if(data != '[TS]') self.fatal('Invalid header'); + c(); + }); + }, + function(c) { + self.sendCommand('si', function(data) { + var split = data.split('\r\n'); + split.forEach(function(line) { + var equals = line.indexOf('='); + var key = equals == -1 ? line : line.substr(0,equals); + var value = equals == -1 ? '' : line.substr(equals+1); + state.raw[key] = value; + }); + c(); + }); + }, + function(c) { + self.sendCommand('pl', function(data) { + var split = data.split('\r\n'); + var fields = split.shift().split('\t'); + split.forEach(function(line) { + var split2 = line.split('\t'); + var player = {}; + split2.forEach(function(value,i) { + var key = fields[i]; + if(!key) return; + if(key == 'nick') key = 'name'; + if(m = value.match(/^"(.*)"$/)) value = m[1]; + player[key] = value; + }); + state.players.push(player); + }); + c(); + }); + }, + function(c) { + self.sendCommand('cl', function(data) { + var split = data.split('\r\n'); + var fields = split.shift().split('\t'); + state.raw.channels = []; + split.forEach(function(line) { + var split2 = line.split('\t'); + var channel = {}; + split2.forEach(function(value,i) { + var key = fields[i]; + if(!key) return; + if(m = value.match(/^"(.*)"$/)) value = m[1]; + channel[key] = value; + }); + state.raw.channels.push(channel); + }); + c(); + }); + }, + function(c) { + self.finish(state); + } + ]); + }, + sendCommand: function(cmd,c) { + this.tcpSend(cmd+'\x0A', function(buffer) { + if(buffer.length < 6) return; + if(buffer.slice(-6).toString() != '\r\nOK\r\n') return; + c(buffer.slice(0,-6).toString()); + return true; + }); + } +}); diff --git a/protocols/teamspeak3.js b/protocols/teamspeak3.js index 77597a0..fb4df2e 100644 --- a/protocols/teamspeak3.js +++ b/protocols/teamspeak3.js @@ -1,74 +1,74 @@ -var async = require('async'); - -module.exports = require('./core').extend({ - run: function(state) { - var self = this; - - async.series([ - function(c) { - self.sendCommand('use port='+self.options.port, function(data) { - var split = data.split('\n\r'); - if(split[0] != 'TS3') self.fatal('Invalid header'); - c(); - }, true); - }, - function(c) { - self.sendCommand('serverinfo', function(data) { - state.raw = data[0]; - c(); - }); - }, - function(c) { - self.sendCommand('clientlist', function(data) { - for(var i = 0; i < data.length; i++) { - data[i].name = data[i].client_nickname; - delete data[i].client_nickname; - state.players.push(data[i]); - } - c(); - }); - }, - function(c) { - self.sendCommand('channellist -topic', function(data) { - state.raw.channels = data; - c(); - }); - }, - function(c) { - self.finish(state); - } - ]); - }, - sendCommand: function(cmd,c,raw) { - this.tcpSend(cmd+'\x0A', function(buffer) { - if(buffer.length < 21) return; - if(buffer.slice(-21).toString() != '\n\rerror id=0 msg=ok\n\r') return; - var body = buffer.slice(0,-21).toString(); - - var out; - - if(raw) { - out = body; - } else { - var segments = body.split('|'); - out = []; - segments.forEach(function(line) { - var split = line.split(' '); - var unit = {}; - split.forEach(function(field) { - var equals = field.indexOf('='); - var key = equals == -1 ? field : field.substr(0,equals); - var value = equals == -1 ? '' : field.substr(equals+1) - .replace(/\\s/g,' ').replace(/\\\//g,'/'); - unit[key] = value; - }); - out.push(unit); - }); - } - - c(out); - - return true; - }); - } -}); +var async = require('async'); + +module.exports = require('./core').extend({ + run: function(state) { + var self = this; + + async.series([ + function(c) { + self.sendCommand('use port='+self.options.port, function(data) { + var split = data.split('\n\r'); + if(split[0] != 'TS3') self.fatal('Invalid header'); + c(); + }, true); + }, + function(c) { + self.sendCommand('serverinfo', function(data) { + state.raw = data[0]; + c(); + }); + }, + function(c) { + self.sendCommand('clientlist', function(data) { + for(var i = 0; i < data.length; i++) { + data[i].name = data[i].client_nickname; + delete data[i].client_nickname; + state.players.push(data[i]); + } + c(); + }); + }, + function(c) { + self.sendCommand('channellist -topic', function(data) { + state.raw.channels = data; + c(); + }); + }, + function(c) { + self.finish(state); + } + ]); + }, + sendCommand: function(cmd,c,raw) { + this.tcpSend(cmd+'\x0A', function(buffer) { + if(buffer.length < 21) return; + if(buffer.slice(-21).toString() != '\n\rerror id=0 msg=ok\n\r') return; + var body = buffer.slice(0,-21).toString(); + + var out; + + if(raw) { + out = body; + } else { + var segments = body.split('|'); + out = []; + segments.forEach(function(line) { + var split = line.split(' '); + var unit = {}; + split.forEach(function(field) { + var equals = field.indexOf('='); + var key = equals == -1 ? field : field.substr(0,equals); + var value = equals == -1 ? '' : field.substr(equals+1) + .replace(/\\s/g,' ').replace(/\\\//g,'/'); + unit[key] = value; + }); + out.push(unit); + }); + } + + c(out); + + return true; + }); + } +}); diff --git a/protocols/terraria.js b/protocols/terraria.js index e35bd80..afa532b 100644 --- a/protocols/terraria.js +++ b/protocols/terraria.js @@ -1,35 +1,35 @@ -var request = require('request'); - -module.exports = require('./core').extend({ - run: function(state) { - var self = this; - request({ - uri: 'http://'+this.options.address+':'+this.options.port_query+'/v2/server/status', - timeout: 3000, - qs: { - players: 'true', - token: this.options.token - } - }, function(e,r,body) { - if(e) return self.fatal('HTTP error'); - var json; - try { - json = JSON.parse(body); - } catch(e) { - return self.fatal('Invalid JSON'); - } - - if(json.status != 200) return self.fatal('Invalid status'); - - json.players.forEach(function(one) { - state.players.push({name:one.nickname,team:one.team}); - }); - - state.name = json.name; - state.raw.port = json.port; - state.raw.numplayers = json.playercount; - - self.finish(state); - }); - } -}); +var request = require('request'); + +module.exports = require('./core').extend({ + run: function(state) { + var self = this; + request({ + uri: 'http://'+this.options.address+':'+this.options.port_query+'/v2/server/status', + timeout: 3000, + qs: { + players: 'true', + token: this.options.token + } + }, function(e,r,body) { + if(e) return self.fatal('HTTP error'); + var json; + try { + json = JSON.parse(body); + } catch(e) { + return self.fatal('Invalid JSON'); + } + + if(json.status != 200) return self.fatal('Invalid status'); + + json.players.forEach(function(one) { + state.players.push({name:one.nickname,team:one.team}); + }); + + state.name = json.name; + state.raw.port = json.port; + state.raw.numplayers = json.playercount; + + self.finish(state); + }); + } +}); diff --git a/protocols/unreal2.js b/protocols/unreal2.js index b4ea301..c7c24d7 100644 --- a/protocols/unreal2.js +++ b/protocols/unreal2.js @@ -1,142 +1,142 @@ -var async = require('async'); - -module.exports = require('./core').extend({ - init: function() { - this._super(); - this.encoding = 'latin1'; - }, - run: function(state) { - - var self = this; - - async.series([ - function(c) { - self.sendPacket(0,true,function(b) { - var reader = self.reader(b); - state.raw.serverid = reader.uint(4); - state.raw.ip = self.readUnrealString(reader); - state.raw.port = reader.uint(4); - state.raw.queryport = reader.uint(4); - state.name = self.readUnrealString(reader,true); - state.map = self.readUnrealString(reader,true); - state.raw.gametype = self.readUnrealString(reader,true); - state.raw.numplayers = reader.uint(4); - state.maxplayers = reader.uint(4); - self.readExtraInfo(reader,state); - - c(); - }); - }, - function(c) { - self.sendPacket(1,true,function(b) { - var reader = self.reader(b); - state.raw.mutators = []; - state.raw.rules = {}; - while(!reader.done()) { - var key = self.readUnrealString(reader,true); - var value = self.readUnrealString(reader,true); - if(key == 'Mutator') state.raw.mutators.push(value); - else state.raw.rules[key] = value; - } - - if('GamePassword' in state.raw.rules) - state.password = state.raw.rules.GamePassword != 'True'; - - c(); - }); - }, - function(c) { - self.sendPacket(2,false,function(b) { - var reader = self.reader(b); - - while(!reader.done()) { - var player = {}; - player.id = reader.uint(4); - if(!player.id) break; - if(player.id == 0) { - // Unreal2XMP Player (ID is always 0) - reader.skip(4); - } - player.name = self.readUnrealString(reader,true); - player.ping = reader.uint(4); - player.score = reader.int(4); - reader.skip(4); // stats ID - - // Extra data for Unreal2XMP players - if(player.id == 0) { - var count = reader.uint(1); - for(var iField = 0; iField < count; iField++) { - var key = self.readUnrealString(reader,true); - var value = self.readUnrealString(reader,true); - player[key] = value; - } - } - - if(player.id == 0 && player.name == 'Player') { - // these show up in ut2004 queries, but aren't real - // not even really sure why they're there - continue; - } - - (player.ping ? state.players : state.bots).push(player); - } - c(); - }); - }, - function(c) { - self.finish(state); - } - ]); - }, - readExtraInfo: function(reader,state) { - if(this.debug) { - console.log("UNREAL2 EXTRA INFO:"); - console.log(reader.uint(4)); - console.log(reader.uint(4)); - console.log(reader.uint(4)); - console.log(reader.uint(4)); - console.log(reader.buffer.slice(reader.i)); - } - }, - readUnrealString: function(reader, stripColor) { - var length = reader.uint(1); - var out; - if(length < 0x80) { - //out = reader.string({length:length}); - out = ''; - if(length > 0) out = reader.string(); - } else { - length = (length&0x7f)*2; - if(this.debug) { - console.log("UCS2 STRING"); - console.log(length,reader.buffer.slice(reader.i,reader.i+length)); - } - out = reader.string({encoding:'ucs2',length:length}); - } - - if(out.charCodeAt(out.length-1) == 0) - out = out.substring(0,out.length-1); - - if(stripColor) - out = out.replace(/\x1b...|[\x00-\x1a]/g,''); - - return out; - }, - sendPacket: function(type,required,callback) { - var self = this; - var outbuffer = new Buffer([0x79,0,0,0,type]); - - var packets = []; - this.udpSend(outbuffer,function(buffer) { - var reader = self.reader(buffer); - var header = reader.uint(4); - var iType = reader.uint(1); - if(iType != type) return; - packets.push(reader.rest()); - },function() { - if(!packets.length && required) return; - callback(Buffer.concat(packets)); - return true; - }); - } -}); +var async = require('async'); + +module.exports = require('./core').extend({ + init: function() { + this._super(); + this.encoding = 'latin1'; + }, + run: function(state) { + + var self = this; + + async.series([ + function(c) { + self.sendPacket(0,true,function(b) { + var reader = self.reader(b); + state.raw.serverid = reader.uint(4); + state.raw.ip = self.readUnrealString(reader); + state.raw.port = reader.uint(4); + state.raw.queryport = reader.uint(4); + state.name = self.readUnrealString(reader,true); + state.map = self.readUnrealString(reader,true); + state.raw.gametype = self.readUnrealString(reader,true); + state.raw.numplayers = reader.uint(4); + state.maxplayers = reader.uint(4); + self.readExtraInfo(reader,state); + + c(); + }); + }, + function(c) { + self.sendPacket(1,true,function(b) { + var reader = self.reader(b); + state.raw.mutators = []; + state.raw.rules = {}; + while(!reader.done()) { + var key = self.readUnrealString(reader,true); + var value = self.readUnrealString(reader,true); + if(key == 'Mutator') state.raw.mutators.push(value); + else state.raw.rules[key] = value; + } + + if('GamePassword' in state.raw.rules) + state.password = state.raw.rules.GamePassword != 'True'; + + c(); + }); + }, + function(c) { + self.sendPacket(2,false,function(b) { + var reader = self.reader(b); + + while(!reader.done()) { + var player = {}; + player.id = reader.uint(4); + if(!player.id) break; + if(player.id == 0) { + // Unreal2XMP Player (ID is always 0) + reader.skip(4); + } + player.name = self.readUnrealString(reader,true); + player.ping = reader.uint(4); + player.score = reader.int(4); + reader.skip(4); // stats ID + + // Extra data for Unreal2XMP players + if(player.id == 0) { + var count = reader.uint(1); + for(var iField = 0; iField < count; iField++) { + var key = self.readUnrealString(reader,true); + var value = self.readUnrealString(reader,true); + player[key] = value; + } + } + + if(player.id == 0 && player.name == 'Player') { + // these show up in ut2004 queries, but aren't real + // not even really sure why they're there + continue; + } + + (player.ping ? state.players : state.bots).push(player); + } + c(); + }); + }, + function(c) { + self.finish(state); + } + ]); + }, + readExtraInfo: function(reader,state) { + if(this.debug) { + console.log("UNREAL2 EXTRA INFO:"); + console.log(reader.uint(4)); + console.log(reader.uint(4)); + console.log(reader.uint(4)); + console.log(reader.uint(4)); + console.log(reader.buffer.slice(reader.i)); + } + }, + readUnrealString: function(reader, stripColor) { + var length = reader.uint(1); + var out; + if(length < 0x80) { + //out = reader.string({length:length}); + out = ''; + if(length > 0) out = reader.string(); + } else { + length = (length&0x7f)*2; + if(this.debug) { + console.log("UCS2 STRING"); + console.log(length,reader.buffer.slice(reader.i,reader.i+length)); + } + out = reader.string({encoding:'ucs2',length:length}); + } + + if(out.charCodeAt(out.length-1) == 0) + out = out.substring(0,out.length-1); + + if(stripColor) + out = out.replace(/\x1b...|[\x00-\x1a]/g,''); + + return out; + }, + sendPacket: function(type,required,callback) { + var self = this; + var outbuffer = new Buffer([0x79,0,0,0,type]); + + var packets = []; + this.udpSend(outbuffer,function(buffer) { + var reader = self.reader(buffer); + var header = reader.uint(4); + var iType = reader.uint(1); + if(iType != type) return; + packets.push(reader.rest()); + },function() { + if(!packets.length && required) return; + callback(Buffer.concat(packets)); + return true; + }); + } +}); diff --git a/protocols/ut3.js b/protocols/ut3.js index 9a605d0..b9a6631 100644 --- a/protocols/ut3.js +++ b/protocols/ut3.js @@ -1,43 +1,43 @@ -module.exports = require('./gamespy3').extend({ - finalizeState: function(state) { - this._super(state); - - this.translate(state.raw,{ - 'mapname': false, - 'p1073741825': 'map', - 'p1073741826': 'gametype', - 'p1073741827': 'servername', - 'p1073741828': 'custom_mutators', - 'gamemode': 'joininprogress', - 's32779': 'gamemode', - 's0': 'bot_skill', - 's6': 'pure_server', - 's7': 'password', - 's8': 'vs_bots', - 's10': 'force_respawn', - 'p268435704': 'frag_limit', - 'p268435705': 'time_limit', - 'p268435703': 'numbots', - 'p268435717': 'stock_mutators', - 'p1073741829': 'stock_mutators', - 's1': false, - 's9': false, - 's11': false, - 's12': false, - 's13': false, - 's14': false, - 'p268435706': false, - 'p268435968': false, - 'p268435969': false - }); - - function split(a) { - var s = a.split('\x1c'); - s = s.filter(function(e) { return e }); - return s; - } - if('custom_mutators' in state.raw) state.raw['custom_mutators'] = split(state.raw['custom_mutators']); - if('stock_mutators' in state.raw) state.raw['stock_mutators'] = split(state.raw['stock_mutators']); - if('map' in state.raw) state.map = state.raw.map; - } -}); +module.exports = require('./gamespy3').extend({ + finalizeState: function(state) { + this._super(state); + + this.translate(state.raw,{ + 'mapname': false, + 'p1073741825': 'map', + 'p1073741826': 'gametype', + 'p1073741827': 'servername', + 'p1073741828': 'custom_mutators', + 'gamemode': 'joininprogress', + 's32779': 'gamemode', + 's0': 'bot_skill', + 's6': 'pure_server', + 's7': 'password', + 's8': 'vs_bots', + 's10': 'force_respawn', + 'p268435704': 'frag_limit', + 'p268435705': 'time_limit', + 'p268435703': 'numbots', + 'p268435717': 'stock_mutators', + 'p1073741829': 'stock_mutators', + 's1': false, + 's9': false, + 's11': false, + 's12': false, + 's13': false, + 's14': false, + 'p268435706': false, + 'p268435968': false, + 'p268435969': false + }); + + function split(a) { + var s = a.split('\x1c'); + s = s.filter(function(e) { return e }); + return s; + } + if('custom_mutators' in state.raw) state.raw['custom_mutators'] = split(state.raw['custom_mutators']); + if('stock_mutators' in state.raw) state.raw['stock_mutators'] = split(state.raw['stock_mutators']); + if('map' in state.raw) state.map = state.raw.map; + } +}); diff --git a/protocols/valve.js b/protocols/valve.js index 4fe6f65..5ddbd42 100644 --- a/protocols/valve.js +++ b/protocols/valve.js @@ -1,325 +1,325 @@ -var async = require('async'), - Bzip2 = require('compressjs').Bzip2; - -module.exports = require('./core').extend({ - init: function() { - this._super(); - - this.options.port = 27015; - - // legacy goldsrc info response -- basically not used by ANYTHING now, - // as most (all?) goldsrc servers respond with the source info reponse - // delete in a few years if nothing ends up using it anymore - this.goldsrcInfo = false; - - // unfortunately, the split format from goldsrc is still around, but we - // can detect that during the query - this.goldsrcSplits = false; - - // some mods require a challenge, but don't provide them in the new format - // at all, use the old dedicated challenge query if needed - this.legacyChallenge = false; - - // cs:go provides an annoying additional bot that looks exactly like a player, - // but is always named "Max Players" - this.isCsGo = false; - - // 2006 engines don't pass packet switching size in split packet header - // while all others do, this need is detected automatically - this._skipSizeInSplitHeader = false; - - this._challenge = ''; - }, - run: function(state) { - var self = this; - async.series([ - function(c) { self.queryInfo(state,c); }, - function(c) { self.queryChallenge(state,c); }, - function(c) { self.queryPlayers(state,c); }, - function(c) { self.queryRules(state,c); }, - function(c) { self.finish(state); } - ]); - }, - queryInfo: function(state,c) { - var self = this; - self.sendPacket( - 0x54,false,'Source Engine Query\0', - self.goldsrcInfo ? 0x6D : 0x49, - function(b) { - var reader = self.reader(b); - - if(self.goldsrcInfo) state.raw.address = reader.string(); - else state.raw.protocol = reader.uint(1); - - state.name = reader.string(); - state.map = reader.string(); - state.raw.folder = reader.string(); - state.raw.game = reader.string(); - state.raw.steamappid = reader.uint(2); - state.raw.numplayers = reader.uint(1); - state.maxplayers = reader.uint(1); - - if(self.goldsrcInfo) state.raw.protocol = reader.uint(1); - else state.raw.numbots = reader.uint(1); - - state.raw.listentype = reader.uint(1); - state.raw.environment = reader.uint(1); - if(!self.goldsrcInfo) { - state.raw.listentype = String.fromCharCode(state.raw.listentype); - state.raw.environment = String.fromCharCode(state.raw.environment); - } - - state.password = !!reader.uint(1); - if(self.goldsrcInfo) { - state.raw.ismod = reader.uint(1); - if(state.raw.ismod) { - state.raw.modlink = reader.string(); - state.raw.moddownload = reader.string(); - reader.skip(1); - state.raw.modversion = reader.uint(4); - state.raw.modsize = reader.uint(4); - state.raw.modtype = reader.uint(1); - state.raw.moddll = reader.uint(1); - } - } - state.raw.secure = reader.uint(1); - - if(self.goldsrcInfo) { - state.raw.numbots = reader.uint(1); - } else { - if(state.raw.folder == 'ship') { - state.raw.shipmode = reader.uint(1); - state.raw.shipwitnesses = reader.uint(1); - state.raw.shipduration = reader.uint(1); - } - state.raw.version = reader.string(); - var extraFlag = reader.uint(1); - if(extraFlag & 0x80) state.raw.port = reader.uint(2); - if(extraFlag & 0x10) state.raw.steamid = reader.uint(8); - if(extraFlag & 0x40) { - state.raw.sourcetvport = reader.uint(2); - state.raw.sourcetvname = reader.string(); - } - if(extraFlag & 0x20) state.raw.tags = reader.string(); - if(extraFlag & 0x01) state.raw.gameid = reader.uint(8); - } - - // from https://developer.valvesoftware.com/wiki/Server_queries - if( - state.raw.protocol == 7 && ( - state.raw.steamappid == 215 - || state.raw.steamappid == 17550 - || state.raw.steamappid == 17700 - || state.raw.steamappid == 240 - ) - ) { - self._skipSizeInSplitHeader = true; - } - if(self.debug) { - console.log("STEAM APPID: "+state.raw.steamappid); - console.log("PROTOCOL: "+state.raw.protocol); - } - if(state.raw.protocol == 48) { - if(self.debug) console.log("GOLDSRC DETECTED - USING MODIFIED SPLIT FORMAT"); - self.goldsrcSplits = true; - } - - c(); - } - ); - }, - queryChallenge: function(state,c) { - var self = this; - if(this.legacyChallenge) { - self.sendPacket(0x57,false,false,0x41,function(b) { - // sendPacket will catch the response packet and - // save the challenge for us - c(); - }); - } else { - c(); - } - }, - queryPlayers: function(state,c) { - var self = this; - self.sendPacket(0x55,true,false,0x44,function(b) { - var reader = self.reader(b); - var num = reader.uint(1); - var csgoHiddenPlayers = false; - for(var i = 0; i < num; i++) { - reader.skip(1); - var name = reader.string(); - var score = reader.int(4); - var time = reader.float(); - - if(self.debug) console.log("Found player: "+name+" "+score+" "+time); - - // connecting players don't count as players. - if(!name) continue; - - (time == -1 ? state.bots : state.players).push({ - name:name, score:score, time:time - }); - } - - if(self.isCsGo && state.players.length == 1 && state.players[0].name == 'Max Players') { - if(self.debug) console.log("CSGO server using limited player details"); - state.players = []; - for(var i = 0; i < state.raw.numplayers; i++) { state.players.push({}); } - } - - // if we didn't find the bots, iterate - // through and guess which ones they are - if(!state.bots.length && state.raw.numbots) { - var maxTime = 0; - state.players.forEach(function(player) { - maxTime = Math.max(player.time,maxTime); - }); - for(var i = 0; i < state.players.length; i++) { - var player = state.players[i]; - if(state.bots.length >= state.raw.numbots) continue; - if(player.time != maxTime) continue; - state.bots.push(player); - state.players.splice(i, 1); - i--; - } - } - - c(); - }); - }, - queryRules: function(state,c) { - var self = this; - self.sendPacket(0x56,true,false,0x45,function(b) { - var reader = self.reader(b); - var num = reader.uint(2); - state.raw.rules = {}; - for(var i = 0; i < num; i++) { - var key = reader.string(); - var value = reader.string(); - state.raw.rules[key] = value; - } - c(); - }, function() { - // no rules were returned after timeout -- - // the server probably has them disabled - // ignore the timeout - c(); - return true; - }); - }, - sendPacket: function(type,sendChallenge,payload,expect,callback,ontimeout) { - var self = this; - var packetStorage = {}; - - send(); - - function send(c) { - if(typeof payload == 'string') payload = new Buffer(payload,'binary'); - var challengeLength = sendChallenge ? 4 : 0; - var payloadLength = payload ? payload.length : 0; - - var b = new Buffer(5 + challengeLength + payloadLength); - b.writeInt32LE(-1, 0); - b.writeUInt8(type, 4); - - if(sendChallenge) { - var challenge = self._challenge; - if(!challenge) challenge = 0xffffffff; - if(self.byteorder == 'le') b.writeUInt32LE(challenge, 5); - else b.writeUInt32BE(challenge, 5); - } - if(payloadLength) payload.copy(b, 5+challengeLength); - - self.udpSend(b,receivedOne,ontimeout); - } - - function receivedOne(buffer) { - var reader = self.reader(buffer); - - var header = reader.int(4); - if(header == -1) { - // full package - if(self.debug) console.log("Received full packet"); - return receivedFull(reader); - } - if(header == -2) { - // partial package - var uid = reader.uint(4); - if(!(uid in packetStorage)) packetStorage[uid] = {}; - var packets = packetStorage[uid]; - - var bzip = false; - if(!self.goldsrcSplits && uid & 0x80000000) bzip = true; - - var packetNum,payload,numPackets; - if(self.goldsrcSplits) { - packetNum = reader.uint(1); - numPackets = packetNum & 0x0f; - packetNum = (packetNum & 0xf0) >> 4; - payload = reader.rest(); - } else { - numPackets = reader.uint(1); - packetNum = reader.uint(1); - if(!self._skipSizeInSplitHeader) reader.skip(2); - if(packetNum == 0 && bzip) reader.skip(8); - payload = reader.rest(); - } - - packets[packetNum] = payload; - - if(self.debug) { - console.log("Received partial packet uid:"+uid+" num:"+packetNum); - console.log("Received "+Object.keys(packets).length+'/'+numPackets+" packets for this UID"); - } - - if(Object.keys(packets).length != numPackets) return; - - // assemble the parts - var list = []; - for(var i = 0; i < numPackets; i++) { - if(!(i in packets)) { - self.fatal('Missing packet #'+i); - return true; - } - list.push(packets[i]); - } - - var assembled = Buffer.concat(list); - if(bzip) { - if(self.debug) console.log("BZIP DETECTED - Extracing packet..."); - try { - assembled = new Buffer(Bzip2.decompressFile(assembled)); - } catch(e) { - self.fatal('Invalid bzip packet'); - return true; - } - } - var assembledReader = self.reader(assembled); - assembledReader.skip(4); // header - return receivedFull(assembledReader); - } - } - - function receivedFull(reader) { - var type = reader.uint(1); - - if(type == 0x41) { - if(self.debug) console.log('Received challenge key'); - if(self._challenge) return self.fatal('Received more than one challenge key'); - self._challenge = reader.uint(4); - - if(sendChallenge) { - if(self.debug) console.log('Restarting query'); - send(); - return true; - } - } - - if(self.debug) console.log("Received "+type.toString(16)+" expected "+expect.toString(16)); - if(type != expect) return; - callback(reader.rest()); - return true; - } - } -}); +var async = require('async'), + Bzip2 = require('compressjs').Bzip2; + +module.exports = require('./core').extend({ + init: function() { + this._super(); + + this.options.port = 27015; + + // legacy goldsrc info response -- basically not used by ANYTHING now, + // as most (all?) goldsrc servers respond with the source info reponse + // delete in a few years if nothing ends up using it anymore + this.goldsrcInfo = false; + + // unfortunately, the split format from goldsrc is still around, but we + // can detect that during the query + this.goldsrcSplits = false; + + // some mods require a challenge, but don't provide them in the new format + // at all, use the old dedicated challenge query if needed + this.legacyChallenge = false; + + // cs:go provides an annoying additional bot that looks exactly like a player, + // but is always named "Max Players" + this.isCsGo = false; + + // 2006 engines don't pass packet switching size in split packet header + // while all others do, this need is detected automatically + this._skipSizeInSplitHeader = false; + + this._challenge = ''; + }, + run: function(state) { + var self = this; + async.series([ + function(c) { self.queryInfo(state,c); }, + function(c) { self.queryChallenge(state,c); }, + function(c) { self.queryPlayers(state,c); }, + function(c) { self.queryRules(state,c); }, + function(c) { self.finish(state); } + ]); + }, + queryInfo: function(state,c) { + var self = this; + self.sendPacket( + 0x54,false,'Source Engine Query\0', + self.goldsrcInfo ? 0x6D : 0x49, + function(b) { + var reader = self.reader(b); + + if(self.goldsrcInfo) state.raw.address = reader.string(); + else state.raw.protocol = reader.uint(1); + + state.name = reader.string(); + state.map = reader.string(); + state.raw.folder = reader.string(); + state.raw.game = reader.string(); + state.raw.steamappid = reader.uint(2); + state.raw.numplayers = reader.uint(1); + state.maxplayers = reader.uint(1); + + if(self.goldsrcInfo) state.raw.protocol = reader.uint(1); + else state.raw.numbots = reader.uint(1); + + state.raw.listentype = reader.uint(1); + state.raw.environment = reader.uint(1); + if(!self.goldsrcInfo) { + state.raw.listentype = String.fromCharCode(state.raw.listentype); + state.raw.environment = String.fromCharCode(state.raw.environment); + } + + state.password = !!reader.uint(1); + if(self.goldsrcInfo) { + state.raw.ismod = reader.uint(1); + if(state.raw.ismod) { + state.raw.modlink = reader.string(); + state.raw.moddownload = reader.string(); + reader.skip(1); + state.raw.modversion = reader.uint(4); + state.raw.modsize = reader.uint(4); + state.raw.modtype = reader.uint(1); + state.raw.moddll = reader.uint(1); + } + } + state.raw.secure = reader.uint(1); + + if(self.goldsrcInfo) { + state.raw.numbots = reader.uint(1); + } else { + if(state.raw.folder == 'ship') { + state.raw.shipmode = reader.uint(1); + state.raw.shipwitnesses = reader.uint(1); + state.raw.shipduration = reader.uint(1); + } + state.raw.version = reader.string(); + var extraFlag = reader.uint(1); + if(extraFlag & 0x80) state.raw.port = reader.uint(2); + if(extraFlag & 0x10) state.raw.steamid = reader.uint(8); + if(extraFlag & 0x40) { + state.raw.sourcetvport = reader.uint(2); + state.raw.sourcetvname = reader.string(); + } + if(extraFlag & 0x20) state.raw.tags = reader.string(); + if(extraFlag & 0x01) state.raw.gameid = reader.uint(8); + } + + // from https://developer.valvesoftware.com/wiki/Server_queries + if( + state.raw.protocol == 7 && ( + state.raw.steamappid == 215 + || state.raw.steamappid == 17550 + || state.raw.steamappid == 17700 + || state.raw.steamappid == 240 + ) + ) { + self._skipSizeInSplitHeader = true; + } + if(self.debug) { + console.log("STEAM APPID: "+state.raw.steamappid); + console.log("PROTOCOL: "+state.raw.protocol); + } + if(state.raw.protocol == 48) { + if(self.debug) console.log("GOLDSRC DETECTED - USING MODIFIED SPLIT FORMAT"); + self.goldsrcSplits = true; + } + + c(); + } + ); + }, + queryChallenge: function(state,c) { + var self = this; + if(this.legacyChallenge) { + self.sendPacket(0x57,false,false,0x41,function(b) { + // sendPacket will catch the response packet and + // save the challenge for us + c(); + }); + } else { + c(); + } + }, + queryPlayers: function(state,c) { + var self = this; + self.sendPacket(0x55,true,false,0x44,function(b) { + var reader = self.reader(b); + var num = reader.uint(1); + var csgoHiddenPlayers = false; + for(var i = 0; i < num; i++) { + reader.skip(1); + var name = reader.string(); + var score = reader.int(4); + var time = reader.float(); + + if(self.debug) console.log("Found player: "+name+" "+score+" "+time); + + // connecting players don't count as players. + if(!name) continue; + + (time == -1 ? state.bots : state.players).push({ + name:name, score:score, time:time + }); + } + + if(self.isCsGo && state.players.length == 1 && state.players[0].name == 'Max Players') { + if(self.debug) console.log("CSGO server using limited player details"); + state.players = []; + for(var i = 0; i < state.raw.numplayers; i++) { state.players.push({}); } + } + + // if we didn't find the bots, iterate + // through and guess which ones they are + if(!state.bots.length && state.raw.numbots) { + var maxTime = 0; + state.players.forEach(function(player) { + maxTime = Math.max(player.time,maxTime); + }); + for(var i = 0; i < state.players.length; i++) { + var player = state.players[i]; + if(state.bots.length >= state.raw.numbots) continue; + if(player.time != maxTime) continue; + state.bots.push(player); + state.players.splice(i, 1); + i--; + } + } + + c(); + }); + }, + queryRules: function(state,c) { + var self = this; + self.sendPacket(0x56,true,false,0x45,function(b) { + var reader = self.reader(b); + var num = reader.uint(2); + state.raw.rules = {}; + for(var i = 0; i < num; i++) { + var key = reader.string(); + var value = reader.string(); + state.raw.rules[key] = value; + } + c(); + }, function() { + // no rules were returned after timeout -- + // the server probably has them disabled + // ignore the timeout + c(); + return true; + }); + }, + sendPacket: function(type,sendChallenge,payload,expect,callback,ontimeout) { + var self = this; + var packetStorage = {}; + + send(); + + function send(c) { + if(typeof payload == 'string') payload = new Buffer(payload,'binary'); + var challengeLength = sendChallenge ? 4 : 0; + var payloadLength = payload ? payload.length : 0; + + var b = new Buffer(5 + challengeLength + payloadLength); + b.writeInt32LE(-1, 0); + b.writeUInt8(type, 4); + + if(sendChallenge) { + var challenge = self._challenge; + if(!challenge) challenge = 0xffffffff; + if(self.byteorder == 'le') b.writeUInt32LE(challenge, 5); + else b.writeUInt32BE(challenge, 5); + } + if(payloadLength) payload.copy(b, 5+challengeLength); + + self.udpSend(b,receivedOne,ontimeout); + } + + function receivedOne(buffer) { + var reader = self.reader(buffer); + + var header = reader.int(4); + if(header == -1) { + // full package + if(self.debug) console.log("Received full packet"); + return receivedFull(reader); + } + if(header == -2) { + // partial package + var uid = reader.uint(4); + if(!(uid in packetStorage)) packetStorage[uid] = {}; + var packets = packetStorage[uid]; + + var bzip = false; + if(!self.goldsrcSplits && uid & 0x80000000) bzip = true; + + var packetNum,payload,numPackets; + if(self.goldsrcSplits) { + packetNum = reader.uint(1); + numPackets = packetNum & 0x0f; + packetNum = (packetNum & 0xf0) >> 4; + payload = reader.rest(); + } else { + numPackets = reader.uint(1); + packetNum = reader.uint(1); + if(!self._skipSizeInSplitHeader) reader.skip(2); + if(packetNum == 0 && bzip) reader.skip(8); + payload = reader.rest(); + } + + packets[packetNum] = payload; + + if(self.debug) { + console.log("Received partial packet uid:"+uid+" num:"+packetNum); + console.log("Received "+Object.keys(packets).length+'/'+numPackets+" packets for this UID"); + } + + if(Object.keys(packets).length != numPackets) return; + + // assemble the parts + var list = []; + for(var i = 0; i < numPackets; i++) { + if(!(i in packets)) { + self.fatal('Missing packet #'+i); + return true; + } + list.push(packets[i]); + } + + var assembled = Buffer.concat(list); + if(bzip) { + if(self.debug) console.log("BZIP DETECTED - Extracing packet..."); + try { + assembled = new Buffer(Bzip2.decompressFile(assembled)); + } catch(e) { + self.fatal('Invalid bzip packet'); + return true; + } + } + var assembledReader = self.reader(assembled); + assembledReader.skip(4); // header + return receivedFull(assembledReader); + } + } + + function receivedFull(reader) { + var type = reader.uint(1); + + if(type == 0x41) { + if(self.debug) console.log('Received challenge key'); + if(self._challenge) return self.fatal('Received more than one challenge key'); + self._challenge = reader.uint(4); + + if(sendChallenge) { + if(self.debug) console.log('Restarting query'); + send(); + return true; + } + } + + if(self.debug) console.log("Received "+type.toString(16)+" expected "+expect.toString(16)); + if(type != expect) return; + callback(reader.rest()); + return true; + } + } +}); diff --git a/protocols/ventrilo.js b/protocols/ventrilo.js index 101924b..1ad53f0 100644 --- a/protocols/ventrilo.js +++ b/protocols/ventrilo.js @@ -1,240 +1,240 @@ -var async = require('async'); - -module.exports = require('./core').extend({ - init: function() { - this._super(); - this.byteorder = 'be'; - }, - run: function(state) { - var self = this; - - this.sendCommand(2,'',function(data) { - state.raw = splitFields(data.toString()); - state.raw.CLIENTS.forEach(function(client) { - client.name = client.NAME; - delete client.NAME; - client.ping = parseInt(client.PING); - delete client.PING; - state.players.push(client); - }); - delete state.raw.CLIENTS; - - if('NAME' in state.raw) state.name = state.raw.NAME; - if('MAXCLIENTS' in state.raw) state.maxplayers = state.raw.MAXCLIENTS; - if(self.trueTest(state.raw.AUTH)) state.password = true; - self.finish(state); - }); - }, - sendCommand: function(cmd,password,c) { - var self = this; - var body = new Buffer(16); - body.write(password,0,15,'utf8'); - var encrypted = encrypt(cmd,body); - - var packets = {}; - this.udpSend(encrypted, function(buffer) { - if(buffer.length < 20) return; - var data = decrypt(buffer); - - if(data.zero != 0) return; - packets[data.packetNum] = data.body; - if(Object.keys(packets).length != data.packetTotal) return; - - var out = []; - for(var i = 0; i < data.packetTotal; i++) { - if(!(i in packets)) return self.fatal('Missing packet #'+i); - out.push(packets[i]); - } - c(Buffer.concat(out)); - return true; - }); - } -}); - -function splitFields(str,subMode) { - var splitter,delim; - if(subMode) { - splitter = '='; - delim = ','; - } else { - splitter = ': '; - delim = '\n'; - } - - var split = str.split(delim); - var out = {}; - if(!subMode) { - out.CHANNELS = []; - out.CLIENTS = []; - } - split.forEach(function(one) { - var equal = one.indexOf(splitter); - var key = equal == -1 ? one : one.substr(0,equal); - if(!key || key == '\0') return; - var value = equal == -1 ? '' : one.substr(equal+splitter.length); - if(!subMode && key == 'CHANNEL') out.CHANNELS.push(splitFields(value,true)); - else if(!subMode && key == 'CLIENT') out.CLIENTS.push(splitFields(value,true)); - else out[key] = value; - }); - return out; -} - -function randInt(min,max) { - return Math.floor(Math.random()*(max-min+1)+min) -} - -function crc(body) { - var crc = 0; - for(var i = 0; i < body.length; i++) { - crc = crc_table[crc>>8] ^ body.readUInt8(i) ^ (crc<<8); - crc &= 0xffff; - } - return crc; -} - -function encrypt(cmd,body) { - var headerKeyStart = randInt(0,0xff); - var headerKeyAdd = randInt(1,0xff); - var bodyKeyStart = randInt(0,0xff); - var bodyKeyAdd = randInt(1,0xff); - - var header = new Buffer(20); - header.writeUInt8(headerKeyStart,0); - header.writeUInt8(headerKeyAdd,1); - header.writeUInt16BE(cmd,4); - header.writeUInt16BE(body.length,8); - header.writeUInt16BE(body.length,10); - header.writeUInt16BE(1,12); - header.writeUInt16BE(0,14); - header.writeUInt8(bodyKeyStart,16); - header.writeUInt8(bodyKeyAdd,17); - header.writeUInt16BE(crc(body),18); - - var offset = headerKeyStart; - for(var i = 2; i < header.length; i++) { - var val = header.readUInt8(i); - val += code_head.charCodeAt(offset) + ((i-2) % 5); - val = val & 0xff; - header.writeUInt8(val,i); - offset = (offset+headerKeyAdd) & 0xff; - } - - offset = bodyKeyStart; - for(var i = 0; i < body.length; i++) { - var val = body.readUInt8(i); - val += code_body.charCodeAt(offset) + (i % 72); - val = val & 0xff; - body.writeUInt8(val,i); - offset = (offset+bodyKeyAdd) & 0xff; - } - - return Buffer.concat([header,body]); -} -function decrypt(data) { - var header = data.slice(0,20); - var body = data.slice(20); - var headerKeyStart = header.readUInt8(0); - var headerKeyAdd = header.readUInt8(1); - - var offset = headerKeyStart; - for(var i = 2; i < header.length; i++) { - var val = header.readUInt8(i); - val -= code_head.charCodeAt(offset) + ((i-2) % 5); - val = val & 0xff; - header.writeUInt8(val,i); - offset = (offset+headerKeyAdd) & 0xff; - } - - var bodyKeyStart = header.readUInt8(16); - var bodyKeyAdd = header.readUInt8(17); - offset = bodyKeyStart; - for(var i = 0; i < body.length; i++) { - var val = body.readUInt8(i); - val -= code_body.charCodeAt(offset) + (i % 72); - val = val & 0xff; - body.writeUInt8(val,i); - offset = (offset+bodyKeyAdd) & 0xff; - } - - // header format: - // key, zero, cmd, echo, totallength, thislength - // totalpacket, packetnum, body key, crc - return { - zero: header.readUInt16BE(2), - cmd: header.readUInt16BE(4), - packetTotal: header.readUInt16BE(12), - packetNum: header.readUInt16BE(14), - body: body - }; -} - -var code_head = - '\x80\xe5\x0e\x38\xba\x63\x4c\x99\x88\x63\x4c\xd6\x54\xb8\x65\x7e'+ - '\xbf\x8a\xf0\x17\x8a\xaa\x4d\x0f\xb7\x23\x27\xf6\xeb\x12\xf8\xea'+ - '\x17\xb7\xcf\x52\x57\xcb\x51\xcf\x1b\x14\xfd\x6f\x84\x38\xb5\x24'+ - '\x11\xcf\x7a\x75\x7a\xbb\x78\x74\xdc\xbc\x42\xf0\x17\x3f\x5e\xeb'+ - '\x74\x77\x04\x4e\x8c\xaf\x23\xdc\x65\xdf\xa5\x65\xdd\x7d\xf4\x3c'+ - '\x4c\x95\xbd\xeb\x65\x1c\xf4\x24\x5d\x82\x18\xfb\x50\x86\xb8\x53'+ - '\xe0\x4e\x36\x96\x1f\xb7\xcb\xaa\xaf\xea\xcb\x20\x27\x30\x2a\xae'+ - '\xb9\x07\x40\xdf\x12\x75\xc9\x09\x82\x9c\x30\x80\x5d\x8f\x0d\x09'+ - '\xa1\x64\xec\x91\xd8\x8a\x50\x1f\x40\x5d\xf7\x08\x2a\xf8\x60\x62'+ - '\xa0\x4a\x8b\xba\x4a\x6d\x00\x0a\x93\x32\x12\xe5\x07\x01\x65\xf5'+ - '\xff\xe0\xae\xa7\x81\xd1\xba\x25\x62\x61\xb2\x85\xad\x7e\x9d\x3f'+ - '\x49\x89\x26\xe5\xd5\xac\x9f\x0e\xd7\x6e\x47\x94\x16\x84\xc8\xff'+ - '\x44\xea\x04\x40\xe0\x33\x11\xa3\x5b\x1e\x82\xff\x7a\x69\xe9\x2f'+ - '\xfb\xea\x9a\xc6\x7b\xdb\xb1\xff\x97\x76\x56\xf3\x52\xc2\x3f\x0f'+ - '\xb6\xac\x77\xc4\xbf\x59\x5e\x80\x74\xbb\xf2\xde\x57\x62\x4c\x1a'+ - '\xff\x95\x6d\xc7\x04\xa2\x3b\xc4\x1b\x72\xc7\x6c\x82\x60\xd1\x0d'; - -var code_body = - '\x82\x8b\x7f\x68\x90\xe0\x44\x09\x19\x3b\x8e\x5f\xc2\x82\x38\x23'+ - '\x6d\xdb\x62\x49\x52\x6e\x21\xdf\x51\x6c\x76\x37\x86\x50\x7d\x48'+ - '\x1f\x65\xe7\x52\x6a\x88\xaa\xc1\x32\x2f\xf7\x54\x4c\xaa\x6d\x7e'+ - '\x6d\xa9\x8c\x0d\x3f\xff\x6c\x09\xb3\xa5\xaf\xdf\x98\x02\xb4\xbe'+ - '\x6d\x69\x0d\x42\x73\xe4\x34\x50\x07\x30\x79\x41\x2f\x08\x3f\x42'+ - '\x73\xa7\x68\xfa\xee\x88\x0e\x6e\xa4\x70\x74\x22\x16\xae\x3c\x81'+ - '\x14\xa1\xda\x7f\xd3\x7c\x48\x7d\x3f\x46\xfb\x6d\x92\x25\x17\x36'+ - '\x26\xdb\xdf\x5a\x87\x91\x6f\xd6\xcd\xd4\xad\x4a\x29\xdd\x7d\x59'+ - '\xbd\x15\x34\x53\xb1\xd8\x50\x11\x83\x79\x66\x21\x9e\x87\x5b\x24'+ - '\x2f\x4f\xd7\x73\x34\xa2\xf7\x09\xd5\xd9\x42\x9d\xf8\x15\xdf\x0e'+ - '\x10\xcc\x05\x04\x35\x81\xb2\xd5\x7a\xd2\xa0\xa5\x7b\xb8\x75\xd2'+ - '\x35\x0b\x39\x8f\x1b\x44\x0e\xce\x66\x87\x1b\x64\xac\xe1\xca\x67'+ - '\xb4\xce\x33\xdb\x89\xfe\xd8\x8e\xcd\x58\x92\x41\x50\x40\xcb\x08'+ - '\xe1\x15\xee\xf4\x64\xfe\x1c\xee\x25\xe7\x21\xe6\x6c\xc6\xa6\x2e'+ - '\x52\x23\xa7\x20\xd2\xd7\x28\x07\x23\x14\x24\x3d\x45\xa5\xc7\x90'+ - '\xdb\x77\xdd\xea\x38\x59\x89\x32\xbc\x00\x3a\x6d\x61\x4e\xdb\x29'; - -var crc_table = [ - 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, - 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, - 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, - 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, - 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, - 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, - 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, - 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, - 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, - 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, - 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, - 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, - 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, - 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, - 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, - 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, - 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, - 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, - 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, - 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, - 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, - 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, - 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, - 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, - 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, - 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, - 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, - 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, - 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, - 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, - 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, - 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 -]; +var async = require('async'); + +module.exports = require('./core').extend({ + init: function() { + this._super(); + this.byteorder = 'be'; + }, + run: function(state) { + var self = this; + + this.sendCommand(2,'',function(data) { + state.raw = splitFields(data.toString()); + state.raw.CLIENTS.forEach(function(client) { + client.name = client.NAME; + delete client.NAME; + client.ping = parseInt(client.PING); + delete client.PING; + state.players.push(client); + }); + delete state.raw.CLIENTS; + + if('NAME' in state.raw) state.name = state.raw.NAME; + if('MAXCLIENTS' in state.raw) state.maxplayers = state.raw.MAXCLIENTS; + if(self.trueTest(state.raw.AUTH)) state.password = true; + self.finish(state); + }); + }, + sendCommand: function(cmd,password,c) { + var self = this; + var body = new Buffer(16); + body.write(password,0,15,'utf8'); + var encrypted = encrypt(cmd,body); + + var packets = {}; + this.udpSend(encrypted, function(buffer) { + if(buffer.length < 20) return; + var data = decrypt(buffer); + + if(data.zero != 0) return; + packets[data.packetNum] = data.body; + if(Object.keys(packets).length != data.packetTotal) return; + + var out = []; + for(var i = 0; i < data.packetTotal; i++) { + if(!(i in packets)) return self.fatal('Missing packet #'+i); + out.push(packets[i]); + } + c(Buffer.concat(out)); + return true; + }); + } +}); + +function splitFields(str,subMode) { + var splitter,delim; + if(subMode) { + splitter = '='; + delim = ','; + } else { + splitter = ': '; + delim = '\n'; + } + + var split = str.split(delim); + var out = {}; + if(!subMode) { + out.CHANNELS = []; + out.CLIENTS = []; + } + split.forEach(function(one) { + var equal = one.indexOf(splitter); + var key = equal == -1 ? one : one.substr(0,equal); + if(!key || key == '\0') return; + var value = equal == -1 ? '' : one.substr(equal+splitter.length); + if(!subMode && key == 'CHANNEL') out.CHANNELS.push(splitFields(value,true)); + else if(!subMode && key == 'CLIENT') out.CLIENTS.push(splitFields(value,true)); + else out[key] = value; + }); + return out; +} + +function randInt(min,max) { + return Math.floor(Math.random()*(max-min+1)+min) +} + +function crc(body) { + var crc = 0; + for(var i = 0; i < body.length; i++) { + crc = crc_table[crc>>8] ^ body.readUInt8(i) ^ (crc<<8); + crc &= 0xffff; + } + return crc; +} + +function encrypt(cmd,body) { + var headerKeyStart = randInt(0,0xff); + var headerKeyAdd = randInt(1,0xff); + var bodyKeyStart = randInt(0,0xff); + var bodyKeyAdd = randInt(1,0xff); + + var header = new Buffer(20); + header.writeUInt8(headerKeyStart,0); + header.writeUInt8(headerKeyAdd,1); + header.writeUInt16BE(cmd,4); + header.writeUInt16BE(body.length,8); + header.writeUInt16BE(body.length,10); + header.writeUInt16BE(1,12); + header.writeUInt16BE(0,14); + header.writeUInt8(bodyKeyStart,16); + header.writeUInt8(bodyKeyAdd,17); + header.writeUInt16BE(crc(body),18); + + var offset = headerKeyStart; + for(var i = 2; i < header.length; i++) { + var val = header.readUInt8(i); + val += code_head.charCodeAt(offset) + ((i-2) % 5); + val = val & 0xff; + header.writeUInt8(val,i); + offset = (offset+headerKeyAdd) & 0xff; + } + + offset = bodyKeyStart; + for(var i = 0; i < body.length; i++) { + var val = body.readUInt8(i); + val += code_body.charCodeAt(offset) + (i % 72); + val = val & 0xff; + body.writeUInt8(val,i); + offset = (offset+bodyKeyAdd) & 0xff; + } + + return Buffer.concat([header,body]); +} +function decrypt(data) { + var header = data.slice(0,20); + var body = data.slice(20); + var headerKeyStart = header.readUInt8(0); + var headerKeyAdd = header.readUInt8(1); + + var offset = headerKeyStart; + for(var i = 2; i < header.length; i++) { + var val = header.readUInt8(i); + val -= code_head.charCodeAt(offset) + ((i-2) % 5); + val = val & 0xff; + header.writeUInt8(val,i); + offset = (offset+headerKeyAdd) & 0xff; + } + + var bodyKeyStart = header.readUInt8(16); + var bodyKeyAdd = header.readUInt8(17); + offset = bodyKeyStart; + for(var i = 0; i < body.length; i++) { + var val = body.readUInt8(i); + val -= code_body.charCodeAt(offset) + (i % 72); + val = val & 0xff; + body.writeUInt8(val,i); + offset = (offset+bodyKeyAdd) & 0xff; + } + + // header format: + // key, zero, cmd, echo, totallength, thislength + // totalpacket, packetnum, body key, crc + return { + zero: header.readUInt16BE(2), + cmd: header.readUInt16BE(4), + packetTotal: header.readUInt16BE(12), + packetNum: header.readUInt16BE(14), + body: body + }; +} + +var code_head = + '\x80\xe5\x0e\x38\xba\x63\x4c\x99\x88\x63\x4c\xd6\x54\xb8\x65\x7e'+ + '\xbf\x8a\xf0\x17\x8a\xaa\x4d\x0f\xb7\x23\x27\xf6\xeb\x12\xf8\xea'+ + '\x17\xb7\xcf\x52\x57\xcb\x51\xcf\x1b\x14\xfd\x6f\x84\x38\xb5\x24'+ + '\x11\xcf\x7a\x75\x7a\xbb\x78\x74\xdc\xbc\x42\xf0\x17\x3f\x5e\xeb'+ + '\x74\x77\x04\x4e\x8c\xaf\x23\xdc\x65\xdf\xa5\x65\xdd\x7d\xf4\x3c'+ + '\x4c\x95\xbd\xeb\x65\x1c\xf4\x24\x5d\x82\x18\xfb\x50\x86\xb8\x53'+ + '\xe0\x4e\x36\x96\x1f\xb7\xcb\xaa\xaf\xea\xcb\x20\x27\x30\x2a\xae'+ + '\xb9\x07\x40\xdf\x12\x75\xc9\x09\x82\x9c\x30\x80\x5d\x8f\x0d\x09'+ + '\xa1\x64\xec\x91\xd8\x8a\x50\x1f\x40\x5d\xf7\x08\x2a\xf8\x60\x62'+ + '\xa0\x4a\x8b\xba\x4a\x6d\x00\x0a\x93\x32\x12\xe5\x07\x01\x65\xf5'+ + '\xff\xe0\xae\xa7\x81\xd1\xba\x25\x62\x61\xb2\x85\xad\x7e\x9d\x3f'+ + '\x49\x89\x26\xe5\xd5\xac\x9f\x0e\xd7\x6e\x47\x94\x16\x84\xc8\xff'+ + '\x44\xea\x04\x40\xe0\x33\x11\xa3\x5b\x1e\x82\xff\x7a\x69\xe9\x2f'+ + '\xfb\xea\x9a\xc6\x7b\xdb\xb1\xff\x97\x76\x56\xf3\x52\xc2\x3f\x0f'+ + '\xb6\xac\x77\xc4\xbf\x59\x5e\x80\x74\xbb\xf2\xde\x57\x62\x4c\x1a'+ + '\xff\x95\x6d\xc7\x04\xa2\x3b\xc4\x1b\x72\xc7\x6c\x82\x60\xd1\x0d'; + +var code_body = + '\x82\x8b\x7f\x68\x90\xe0\x44\x09\x19\x3b\x8e\x5f\xc2\x82\x38\x23'+ + '\x6d\xdb\x62\x49\x52\x6e\x21\xdf\x51\x6c\x76\x37\x86\x50\x7d\x48'+ + '\x1f\x65\xe7\x52\x6a\x88\xaa\xc1\x32\x2f\xf7\x54\x4c\xaa\x6d\x7e'+ + '\x6d\xa9\x8c\x0d\x3f\xff\x6c\x09\xb3\xa5\xaf\xdf\x98\x02\xb4\xbe'+ + '\x6d\x69\x0d\x42\x73\xe4\x34\x50\x07\x30\x79\x41\x2f\x08\x3f\x42'+ + '\x73\xa7\x68\xfa\xee\x88\x0e\x6e\xa4\x70\x74\x22\x16\xae\x3c\x81'+ + '\x14\xa1\xda\x7f\xd3\x7c\x48\x7d\x3f\x46\xfb\x6d\x92\x25\x17\x36'+ + '\x26\xdb\xdf\x5a\x87\x91\x6f\xd6\xcd\xd4\xad\x4a\x29\xdd\x7d\x59'+ + '\xbd\x15\x34\x53\xb1\xd8\x50\x11\x83\x79\x66\x21\x9e\x87\x5b\x24'+ + '\x2f\x4f\xd7\x73\x34\xa2\xf7\x09\xd5\xd9\x42\x9d\xf8\x15\xdf\x0e'+ + '\x10\xcc\x05\x04\x35\x81\xb2\xd5\x7a\xd2\xa0\xa5\x7b\xb8\x75\xd2'+ + '\x35\x0b\x39\x8f\x1b\x44\x0e\xce\x66\x87\x1b\x64\xac\xe1\xca\x67'+ + '\xb4\xce\x33\xdb\x89\xfe\xd8\x8e\xcd\x58\x92\x41\x50\x40\xcb\x08'+ + '\xe1\x15\xee\xf4\x64\xfe\x1c\xee\x25\xe7\x21\xe6\x6c\xc6\xa6\x2e'+ + '\x52\x23\xa7\x20\xd2\xd7\x28\x07\x23\x14\x24\x3d\x45\xa5\xc7\x90'+ + '\xdb\x77\xdd\xea\x38\x59\x89\x32\xbc\x00\x3a\x6d\x61\x4e\xdb\x29'; + +var crc_table = [ + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, + 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, + 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, + 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, + 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, + 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, + 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, + 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, + 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, + 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, + 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, + 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, + 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, + 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, + 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, + 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, + 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, + 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, + 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, + 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, + 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, + 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, + 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 +]; diff --git a/protocols/warsow.js b/protocols/warsow.js index 9f43ca2..1b9856f 100644 --- a/protocols/warsow.js +++ b/protocols/warsow.js @@ -1,12 +1,12 @@ -module.exports = require('./quake3').extend({ - finalizeState: function(state) { - this._super(state); - if(state.players) { - for(var i = 0; i < state.players.length; i++) { - var player = state.players[i]; - player.team = player.address; - delete player.address; - } - } - } -}); +module.exports = require('./quake3').extend({ + finalizeState: function(state) { + this._super(state); + if(state.players) { + for(var i = 0; i < state.players.length; i++) { + var player = state.players[i]; + player.team = player.address; + delete player.address; + } + } + } +});