crlf -> lf conversion

This commit is contained in:
Michael Morrison 2014-10-29 01:42:33 -05:00
parent c209686798
commit a3c3184eb8
32 changed files with 3307 additions and 3307 deletions

792
README.md
View File

@ -1,396 +1,396 @@
node-GameDig - Game Server Query Library node-GameDig - Game Server Query Library
--- ---
node-GameDig is a game server query library, capable of querying for the status of 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, nearly any game or voice server. If a server makes its status publically available,
GameDig can fetch it for you. GameDig can fetch it for you.
GameDig is available as a node.js module, as well as a GameDig is available as a node.js module, as well as a
[command line executable](#usage-from-command-line). [command line executable](#usage-from-command-line).
Usage from Node.js Usage from Node.js
--- ---
```shell ```shell
npm install gamedig npm install gamedig
``` ```
```javascript ```javascript
var Gamedig = require('gamedig'); var Gamedig = require('gamedig');
Gamedig.query( Gamedig.query(
{ {
type: 'minecraft', type: 'minecraft',
host: 'mc.example.com' host: 'mc.example.com'
}, },
function(state) { function(state) {
if(state.error) console.log("Server is offline"); if(state.error) console.log("Server is offline");
else console.log(state); else console.log(state);
} }
); );
``` ```
> Is NPM out of date? If you're feeling lucky, you can install the latest code with > Is NPM out of date? If you're feeling lucky, you can install the latest code with
> ```shell > ```shell
> npm install "git+https://github.com/sonicsnes/node-gamedig.git" > npm install "git+https://github.com/sonicsnes/node-gamedig.git"
> ``` > ```
### Input Parameters ### Input Parameters
* **type**: One of the game IDs listed in the game list below * **type**: One of the game IDs listed in the game list below
* **host** * **host**
* **port**: (optional) Uses the protocol default if not set * **port**: (optional) Uses the protocol default if not set
* **notes**: (optional) Passed through to output * **notes**: (optional) Passed through to output
###Callback Function ###Callback Function
The callback function is "guaranteed" to be called exactly once. 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 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. 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: Otherwise, the returned object is guaranteed to contain the following keys:
**Stable, always present:** **Stable, always present:**
* **name** * **name**
* **map** * **map**
* **password**: Boolean * **password**: Boolean
* **maxplayers** * **maxplayers**
* **players**: (array of objects) Each object **may** contain name, ping, score, team, address * **players**: (array of objects) Each object **may** contain name, ping, score, team, address
* **bots**: Same schema as players * **bots**: Same schema as players
* **notes**: Passed through from the input * **notes**: Passed through from the input
**Unstable, not guaranteed:** **Unstable, not guaranteed:**
* **raw**: Contains all information received from the server * **raw**: Contains all information received from the server
* **query**: Details about the query performed * **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. 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. Some servers may return an additional player count number, which may be present in the unstable raw object.
Games List Games List
--- ---
###Supported ###Supported
<!--- BEGIN GENERATED GAMES --> <!--- BEGIN GENERATED GAMES -->
* Age of Chivalry (ageofchivalry) * Age of Chivalry (ageofchivalry)
* Age of Empires 2 (aoe2) [[Separate Query Port](#separate-query-port)] * Age of Empires 2 (aoe2) [[Separate Query Port](#separate-query-port)]
* Alien Arena (alienarena) [[Separate Query Port](#separate-query-port)] * Alien Arena (alienarena) [[Separate Query Port](#separate-query-port)]
* Alien Swarm (alienswarm) * Alien Swarm (alienswarm)
* Aliens vs Predator 2 (avp2) * Aliens vs Predator 2 (avp2)
* Aliens vs Predator 2010 (avp2010) * Aliens vs Predator 2010 (avp2010)
* America's Army (americasarmy) [[Separate Query Port](#separate-query-port)] * America's Army (americasarmy) [[Separate Query Port](#separate-query-port)]
* America's Army 2 (americasarmy2) [[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 3 (americasarmy3) [[Separate Query Port](#separate-query-port)]
* America's Army: Proving Grounds (americasarmypg) [[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 1 (arma)
* ArmA Armed Assault 2 (arma2) [[Separate Query Port](#separate-query-port)] * ArmA Armed Assault 2 (arma2) [[Separate Query Port](#separate-query-port)]
* ArmA Armed Assault 3 (arma3) [[Separate Query Port](#separate-query-port)] * ArmA Armed Assault 3 (arma3) [[Separate Query Port](#separate-query-port)]
* Armagetron (armagetron) * Armagetron (armagetron)
* Baldur's Gate (baldursgate) [[Separate Query Port](#separate-query-port)] * Baldur's Gate (baldursgate) [[Separate Query Port](#separate-query-port)]
* Battlefield 1942 (bf1942) [[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 Vietnam (bfv) [[Separate Query Port](#separate-query-port)]
* Battlefield 2 (bf2) [[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 2142 (bf2142) [[Separate Query Port](#separate-query-port)]
* Battlefield: Bad Company 2 (bfbc2) [[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 3 (bf3) [[Separate Query Port](#separate-query-port)]
* Battlefield 4 (bf4) [[Separate Query Port](#separate-query-port)] * Battlefield 4 (bf4) [[Separate Query Port](#separate-query-port)]
* Breach (breach) * Breach (breach)
* Breed (breed) * Breed (breed)
* Brink (brink) [[Separate Query Port](#separate-query-port)] * Brink (brink) [[Separate Query Port](#separate-query-port)]
* Build and Shoot (buildandshoot) [[Separate Query Port](#separate-query-port)] * Build and Shoot (buildandshoot) [[Separate Query Port](#separate-query-port)]
* Call of Duty (cod) * Call of Duty (cod)
* Call of Duty: United Offensive (coduo) * Call of Duty: United Offensive (coduo)
* Call of Duty 2 (cod2) * Call of Duty 2 (cod2)
* Call of Duty 3 (cod3) * Call of Duty 3 (cod3)
* Call of Duty 4: Modern Warfare (cod4) * Call of Duty 4: Modern Warfare (cod4)
* Call of Duty: World at War (codwaw) * Call of Duty: World at War (codwaw)
* Call of Duty: Modern Warfare 2 (codmw2) * Call of Duty: Modern Warfare 2 (codmw2)
* Call of Duty: Modern Warfare 3 (codmw3) [[Separate Query Port](#separate-query-port)] * Call of Duty: Modern Warfare 3 (codmw3) [[Separate Query Port](#separate-query-port)]
* Call of Juarez (callofjuarez) [[Separate Query Port](#separate-query-port)] * Call of Juarez (callofjuarez) [[Separate Query Port](#separate-query-port)]
* Chaser (chaser) [[Separate Query Port](#separate-query-port)] * Chaser (chaser) [[Separate Query Port](#separate-query-port)]
* Chrome (chrome) [[Separate Query Port](#separate-query-port)] * Chrome (chrome) [[Separate Query Port](#separate-query-port)]
* Codename Eagle (codenameeagle) [[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)] * Commandos 3: Destination Berlin (commandos3) [[Separate Query Port](#separate-query-port)]
* Command and Conquer: Renegade (cacrenegade) [[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)] * Contact J.A.C.K. (contactjack) [[Separate Query Port](#separate-query-port)]
* Counter-Strike 1.6 (cs16) * Counter-Strike 1.6 (cs16)
* Counter-Strike: Condition Zero (cscz) * Counter-Strike: Condition Zero (cscz)
* Counter-Strike: Source (css) * Counter-Strike: Source (css)
* Counter-Strike: Global Offensive (csgo) * Counter-Strike: Global Offensive (csgo)
* Cross Racing Championship (crossracing) [[Separate Query Port](#separate-query-port)] * Cross Racing Championship (crossracing) [[Separate Query Port](#separate-query-port)]
* Crysis (crysis) * Crysis (crysis)
* Crysis Wars (crysiswars) * Crysis Wars (crysiswars)
* Crysis 2 (crysis2) * Crysis 2 (crysis2)
* Daikatana (daikatana) [[Separate Query Port](#separate-query-port)] * Daikatana (daikatana) [[Separate Query Port](#separate-query-port)]
* Dark Messiah of Might and Magic (dmomam) * Dark Messiah of Might and Magic (dmomam)
* Darkest Hour (darkesthour) [[Separate Query Port](#separate-query-port)] * Darkest Hour (darkesthour) [[Separate Query Port](#separate-query-port)]
* DayZ (dayz) [[Separate Query Port](#separate-query-port)] [[Additional Notes](#dayz)] * DayZ (dayz) [[Separate Query Port](#separate-query-port)] [[Additional Notes](#dayz)]
* Deadly Dozen: Pacific Theater (deadlydozenpt) [[Separate Query Port](#separate-query-port)] * Deadly Dozen: Pacific Theater (deadlydozenpt) [[Separate Query Port](#separate-query-port)]
* Deer Hunter 2005 (dh2005) [[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)] * Descent 3 (descent3) [[Separate Query Port](#separate-query-port)]
* Deus Ex (deusex) [[Separate Query Port](#separate-query-port)] * Deus Ex (deusex) [[Separate Query Port](#separate-query-port)]
* Devastation (devastation) [[Separate Query Port](#separate-query-port)] * Devastation (devastation) [[Separate Query Port](#separate-query-port)]
* Dino D-Day (dinodday) * Dino D-Day (dinodday)
* Dirt Track Racing 2 (dirttrackracing2) [[Separate Query Port](#separate-query-port)] * Dirt Track Racing 2 (dirttrackracing2) [[Separate Query Port](#separate-query-port)]
* Day of Defeat (dod) * Day of Defeat (dod)
* Day of Defeat: Source (dods) * Day of Defeat: Source (dods)
* Doom 3 (doom3) * Doom 3 (doom3)
* DOTA 2 (dota2) * DOTA 2 (dota2)
* Drakan (drakan) [[Separate Query Port](#separate-query-port)] * Drakan (drakan) [[Separate Query Port](#separate-query-port)]
* Enemy Territory Quake Wars (etqw) [[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)] * F.E.A.R. (fear) [[Separate Query Port](#separate-query-port)]
* F1 2002 (f12002) [[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)] * F1 Challenge 99-02 (f1c9902) [[Separate Query Port](#separate-query-port)]
* Far Cry (farcry) [[Separate Query Port](#separate-query-port)] * Far Cry (farcry) [[Separate Query Port](#separate-query-port)]
* Far Cry (farcry2) [[Separate Query Port](#separate-query-port)] * Far Cry (farcry2) [[Separate Query Port](#separate-query-port)]
* Fortress Forever (fortressforever) * Fortress Forever (fortressforever)
* Flashpoint (flashpoint) [[Separate Query Port](#separate-query-port)] * Flashpoint (flashpoint) [[Separate Query Port](#separate-query-port)]
* Frontlines: Fuel of War (ffow) [[Separate Query Port](#separate-query-port)] * Frontlines: Fuel of War (ffow) [[Separate Query Port](#separate-query-port)]
* Garry's Mod (garrysmod) * Garry's Mod (garrysmod)
* Ghost Recon: Advanced Warfighter (graw) [[Separate Query Port](#separate-query-port)] * Ghost Recon: Advanced Warfighter (graw) [[Separate Query Port](#separate-query-port)]
* Ghost Recon: Advanced Warfighter 2 (graw2) [[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)] * Giants: Citizen Kabuto (giantscitizenkabuto) [[Separate Query Port](#separate-query-port)]
* Global Operations (globaloperations) [[Separate Query Port](#separate-query-port)] * Global Operations (globaloperations) [[Separate Query Port](#separate-query-port)]
* Gore (gore) [[Separate Query Port](#separate-query-port)] * Gore (gore) [[Separate Query Port](#separate-query-port)]
* Gunman Chronicles (gunmanchronicles) * Gunman Chronicles (gunmanchronicles)
* Half-Life 1 Deathmatch (hldm) * Half-Life 1 Deathmatch (hldm)
* Half-Life 2 Deathmatch (hl2dm) * Half-Life 2 Deathmatch (hl2dm)
* Halo (halo) * Halo (halo)
* Halo 2 (halo2) * Halo 2 (halo2)
* Heretic 2 (heretic2) [[Separate Query Port](#separate-query-port)] * Heretic 2 (heretic2) [[Separate Query Port](#separate-query-port)]
* Hexen World (hexenworld) [[Separate Query Port](#separate-query-port)] * Hexen World (hexenworld) [[Separate Query Port](#separate-query-port)]
* The Hidden: Source (hidden) * The Hidden: Source (hidden)
* Hidden and Dangerous 2 (had2) [[Separate Query Port](#separate-query-port)] * Hidden and Dangerous 2 (had2) [[Separate Query Port](#separate-query-port)]
* Homefront (homefront) * Homefront (homefront)
* Homeworld 2 (homeworld2) [[Separate Query Port](#separate-query-port)] * Homeworld 2 (homeworld2) [[Separate Query Port](#separate-query-port)]
* IGI-2: Covert Strike (igi2) [[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)] * IL-2 Sturmovik (il2) [[Separate Query Port](#separate-query-port)]
* Insurgency (insurgency) * Insurgency (insurgency)
* Iron Storm (ironstorm) [[Separate Query Port](#separate-query-port)] * Iron Storm (ironstorm) [[Separate Query Port](#separate-query-port)]
* James Bond: Nightfire (jamesbondnightfire) [[Separate Query Port](#separate-query-port)] * James Bond: Nightfire (jamesbondnightfire) [[Separate Query Port](#separate-query-port)]
* Just Cause 2 Multiplayer (jc2mp) * Just Cause 2 Multiplayer (jc2mp)
* Killing Floor (killingfloor) [[Separate Query Port](#separate-query-port)] * Killing Floor (killingfloor) [[Separate Query Port](#separate-query-port)]
* Kingpin: Life of Crime (kingpin) [[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)] * KISS Psycho Circus (kisspc) [[Separate Query Port](#separate-query-port)]
* KzMod (kzmod) * KzMod (kzmod)
* Left 4 Dead (left4dead) * Left 4 Dead (left4dead)
* Left 4 Dead 2 (left4dead2) * Left 4 Dead 2 (left4dead2)
* Mafia 2 Multiplayer (m2mp) [[Separate Query Port](#separate-query-port)] * 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: Allied Assault (mohaa) [[Separate Query Port](#separate-query-port)]
* Medal of Honor: Pacific Assault (mohpa) [[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: Airborne (mohab) [[Separate Query Port](#separate-query-port)]
* Medal of Honor: Spearhead (mohsh) [[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: Breakthrough (mohbt) [[Separate Query Port](#separate-query-port)]
* Medal of Honor 2010 (moh2010) [[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)] * Medal of Honor: Warfighter (mohwf) [[Separate Query Port](#separate-query-port)]
* Minecraft (minecraft) [[Additional Notes](#minecraft)] * Minecraft (minecraft) [[Additional Notes](#minecraft)]
* Minecraft (minecraftping) [[Additional Notes](#minecraft)] * Minecraft (minecraftping) [[Additional Notes](#minecraft)]
* Monday Night Combat (mnc) [[Separate Query Port](#separate-query-port)] * 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: Vice City (mtavc) [[Separate Query Port](#separate-query-port)]
* Multi Theft Auto: San Andreas (mtasa) [[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 (mumble) [[Separate Query Port](#separate-query-port)] [[Additional Notes](#mumble)]
* Mumble (mumbleping) [[Additional Notes](#mumble)] * Mumble (mumbleping) [[Additional Notes](#mumble)]
* Mutant Factions (mutantfactions) * Mutant Factions (mutantfactions)
* Nascar Thunder 2004 (nascarthunder2004) [[Separate Query Port](#separate-query-port)] * Nascar Thunder 2004 (nascarthunder2004) [[Separate Query Port](#separate-query-port)]
* netPanzer (netpanzer) * netPanzer (netpanzer)
* No More Room in Hell (nmrih) * No More Room in Hell (nmrih)
* Natural Selection (ns) * Natural Selection (ns)
* Natural Selection 2 (ns2) [[Separate Query Port](#separate-query-port)] * Natural Selection 2 (ns2) [[Separate Query Port](#separate-query-port)]
* Need for Speed: Hot Pursuit 2 (nfshp2) [[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)] * Nerf Arena Blast (nab) [[Separate Query Port](#separate-query-port)]
* Neverwinter Nights (nwn) [[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)] * Neverwinter Nights 2 (nwn2) [[Separate Query Port](#separate-query-port)]
* Nexuiz (nexuiz) [[Separate Query Port](#separate-query-port)] * Nexuiz (nexuiz) [[Separate Query Port](#separate-query-port)]
* Nitro Family (nitrofamily) [[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 (nolf) [[Separate Query Port](#separate-query-port)]
* No One Lives Forever 2 (nolf2) [[Separate Query Port](#separate-query-port)] * No One Lives Forever 2 (nolf2) [[Separate Query Port](#separate-query-port)]
* Nuclear Dawn (nucleardawn) * Nuclear Dawn (nucleardawn)
* OpenArena (openarena) [[Separate Query Port](#separate-query-port)] * OpenArena (openarena) [[Separate Query Port](#separate-query-port)]
* Operation Flashpoint (operationflashpoint) [[Separate Query Port](#separate-query-port)] * Operation Flashpoint (operationflashpoint) [[Separate Query Port](#separate-query-port)]
* Painkiller (painkiller) [[Separate Query Port](#separate-query-port)] * Painkiller (painkiller) [[Separate Query Port](#separate-query-port)]
* Postal 2 (postal2) [[Separate Query Port](#separate-query-port)] * Postal 2 (postal2) [[Separate Query Port](#separate-query-port)]
* Prey (prey) [[Separate Query Port](#separate-query-port)] * Prey (prey) [[Separate Query Port](#separate-query-port)]
* Quake 1: QuakeWorld (quake1) * Quake 1: QuakeWorld (quake1)
* Quake 2 (quake2) * Quake 2 (quake2)
* Quake 3: Arena (quake3) * Quake 3: Arena (quake3)
* Quake 4 (quake4) * Quake 4 (quake4)
* Rag Doll Kung Fu (ragdollkungfu) * Rag Doll Kung Fu (ragdollkungfu)
* Rainbow Six (r6) [[Separate Query Port](#separate-query-port)] * Rainbow Six (r6) [[Separate Query Port](#separate-query-port)]
* Rainbow Six 2: Rogue Spear (r6roguespear) [[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)] * Rainbow Six 3: Raven Shield (r6ravenshield) [[Separate Query Port](#separate-query-port)]
* RalliSport Challenge (rallisportchallenge) [[Separate Query Port](#separate-query-port)] * RalliSport Challenge (rallisportchallenge) [[Separate Query Port](#separate-query-port)]
* Rally Masters (rallymasters) [[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 (redorchestra) [[Separate Query Port](#separate-query-port)]
* Red Orchestra: Ostfront 41-45 (redorchestraost) [[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)] * Red Orchestra 2 (redorchestra2) [[Separate Query Port](#separate-query-port)]
* Redline (redline) [[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)] * Return to Castle Wolfenstein (rtcw) [[Separate Query Port](#separate-query-port)]
* Ricochet (ricochet) * Ricochet (ricochet)
* Rise of Nations (riseofnations) [[Separate Query Port](#separate-query-port)] * Rise of Nations (riseofnations) [[Separate Query Port](#separate-query-port)]
* Rune (rune) [[Separate Query Port](#separate-query-port)] * Rune (rune) [[Separate Query Port](#separate-query-port)]
* Rust (rust) [[Separate Query Port](#separate-query-port)] * Rust (rust) [[Separate Query Port](#separate-query-port)]
* San Andreas Multiplayer (samp) * San Andreas Multiplayer (samp)
* Serious Sam (ss) [[Separate Query Port](#separate-query-port)] * Serious Sam (ss) [[Separate Query Port](#separate-query-port)]
* Serious Sam 2 (ss2) * Serious Sam 2 (ss2)
* Shattered Horizon (shatteredhorizon) * Shattered Horizon (shatteredhorizon)
* The Ship (ship) * The Ship (ship)
* Shogo (shogo) [[Separate Query Port](#separate-query-port)] * Shogo (shogo) [[Separate Query Port](#separate-query-port)]
* Shootmania (shootmania) [[Additional Notes](#nadeo-shootmania--trackmania--etc)] * Shootmania (shootmania) [[Additional Notes](#nadeo-shootmania--trackmania--etc)]
* SiN (sin) [[Separate Query Port](#separate-query-port)] * SiN (sin) [[Separate Query Port](#separate-query-port)]
* SiN Episodes (sinep) * SiN Episodes (sinep)
* Soldat (soldat) [[Separate Query Port](#separate-query-port)] * Soldat (soldat) [[Separate Query Port](#separate-query-port)]
* Soldier of Fortune (sof) [[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)] * 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)] * 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: 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 (stvef) [[Separate Query Port](#separate-query-port)]
* Star Trek: Voyager - Elite Force 2 (stvef2) [[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 (swbf) [[Separate Query Port](#separate-query-port)]
* Star Wars: Battlefront 2 (swbf2) [[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 (swjk) [[Separate Query Port](#separate-query-port)]
* Star Wars: Jedi Knight 2 (swjk2) [[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)] * Star Wars: Republic Commando (swrc) [[Separate Query Port](#separate-query-port)]
* Starbound (starbound) * Starbound (starbound)
* Suicide Survival (suicidesurvival) * Suicide Survival (suicidesurvival)
* SWAT 4 (swat4) [[Separate Query Port](#separate-query-port)] * SWAT 4 (swat4) [[Separate Query Port](#separate-query-port)]
* Sven Coop (svencoop) * Sven Coop (svencoop)
* Synergy (synergy) * Synergy (synergy)
* Tactical Ops (tacticalops) [[Separate Query Port](#separate-query-port)] * Tactical Ops (tacticalops) [[Separate Query Port](#separate-query-port)]
* Team Factor (teamfactor) [[Separate Query Port](#separate-query-port)] * Team Factor (teamfactor) [[Separate Query Port](#separate-query-port)]
* Team Fortress Classic (tfc) * Team Fortress Classic (tfc)
* Team Fortress 2 (tf2) * Team Fortress 2 (tf2)
* Teamspeak 2 (teamspeak2) [[Separate Query Port](#separate-query-port)] * Teamspeak 2 (teamspeak2) [[Separate Query Port](#separate-query-port)]
* Teamspeak 3 (teamspeak3) [[Separate Query Port](#separate-query-port)] * Teamspeak 3 (teamspeak3) [[Separate Query Port](#separate-query-port)]
* Terminus (terminus) [[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)] * 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 3 (thps3) [[Separate Query Port](#separate-query-port)]
* Tony Hawk's Pro Skater 4 (thps4) [[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)] * Tony Hawk's Underground 2 (thu2) [[Separate Query Port](#separate-query-port)]
* Trackmania 2 (trackmania2) [[Additional Notes](#nadeo-shootmania--trackmania--etc)] * Trackmania 2 (trackmania2) [[Additional Notes](#nadeo-shootmania--trackmania--etc)]
* Trackmania Forever (trackmaniaforever) [[Additional Notes](#nadeo-shootmania--trackmania--etc)] * Trackmania Forever (trackmaniaforever) [[Additional Notes](#nadeo-shootmania--trackmania--etc)]
* Tremulous (tremulous) [[Separate Query Port](#separate-query-port)] * Tremulous (tremulous) [[Separate Query Port](#separate-query-port)]
* Tribes: Vengeance (tribesvengeance) [[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)] * Tron 2.0 (tron20) [[Separate Query Port](#separate-query-port)]
* Turok 2 (turok2) [[Separate Query Port](#separate-query-port)] * Turok 2 (turok2) [[Separate Query Port](#separate-query-port)]
* Universal Combat (universalcombat) [[Separate Query Port](#separate-query-port)] * Universal Combat (universalcombat) [[Separate Query Port](#separate-query-port)]
* Unreal (unreal) [[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 (ut) [[Separate Query Port](#separate-query-port)]
* Unreal Tournament 2003 (ut2003) [[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 2004 (ut2004) [[Separate Query Port](#separate-query-port)]
* Unreal Tournament 3 (ut3) [[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)] * Urban Terror (urbanterror) [[Separate Query Port](#separate-query-port)]
* V8 Supercar Challenge (v8supercar) [[Separate Query Port](#separate-query-port)] * V8 Supercar Challenge (v8supercar) [[Separate Query Port](#separate-query-port)]
* Ventrilo (ventrilo) * Ventrilo (ventrilo)
* Vietcong (vietcong) [[Separate Query Port](#separate-query-port)] * Vietcong (vietcong) [[Separate Query Port](#separate-query-port)]
* Vietcong 2 (vietcong2) [[Separate Query Port](#separate-query-port)] * Vietcong 2 (vietcong2) [[Separate Query Port](#separate-query-port)]
* Warsow (warsow) * Warsow (warsow)
* Wheel of Time (wheeloftime) [[Separate Query Port](#separate-query-port)] * Wheel of Time (wheeloftime) [[Separate Query Port](#separate-query-port)]
* Wolfenstein 2009 (wolfenstein2009) [[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)] * Wolfenstein: Enemy Territory (wolfensteinet) [[Separate Query Port](#separate-query-port)]
* Xpand Rally (xpandrally) [[Separate Query Port](#separate-query-port)] * Xpand Rally (xpandrally) [[Separate Query Port](#separate-query-port)]
* Zombie Master (zombiemaster) * Zombie Master (zombiemaster)
* Zombie Panic: Source (zps) * Zombie Panic: Source (zps)
<!--- END GENERATED GAMES --> <!--- END GENERATED GAMES -->
###Not supported (yet) ###Not supported (yet)
* rFactor Engine (rfactor): * rFactor Engine (rfactor):
* rFactor * rFactor
* Arca Sim Racing * Arca Sim Racing
* Cube Engine (cube): * Cube Engine (cube):
* Cube 1 * Cube 1
* Assault Cube * Assault Cube
* Cube 2: Sauerbraten * Cube 2: Sauerbraten
* Blood Frontier * Blood Frontier
* BFRIS * BFRIS
* Call of Duty: Black Ops 1 and 2 (no documentation, may require rcon) * Call of Duty: Black Ops 1 and 2 (no documentation, may require rcon)
* Counter-Strike 2D * Counter-Strike 2D
* Freelancer * Freelancer
* Ghost Recon * Ghost Recon
* GTR2 * GTR2
* Haze * Haze
* Hexen 2 * Hexen 2
* OpenTTD * OpenTTD
* Plain Sight * Plain Sight
* Red Faction * Red Faction
* Savage: Battle for Newerth * Savage: Battle for Newerth
* Savage 2: A Tortured Soul * Savage 2: A Tortured Soul
* Sum of All Fears * Sum of All Fears
* Teeworlds * Teeworlds
* Tribes 1: Starsiege * Tribes 1: Starsiege
* Tribes 2 * Tribes 2
* Vice City Multiplayer * Vice City Multiplayer
* World in Conflict * World in Conflict
> Want support for one of these games? Please open an issue to show your interest! > Want support for one of these games? Please open an issue to show your interest!
> __Know how to code?__ Protocols for most of the games above are documented > __Know how to code?__ Protocols for most of the games above are documented
> in the /reference folder, ready for you to develop into GameDig! > in the /reference folder, ready for you to develop into GameDig!
<!-- --> <!-- -->
> Don't see your game listed here? > Don't see your game listed here?
> >
> First, let us know so we can fix it. Then, you can try using some common query > 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: > protocols directly by using one of these server types:
> * protocol-ase > * protocol-ase
> * protocol-battlefield > * protocol-battlefield
> * protocol-doom3 > * protocol-doom3
> * protocol-gamespy1 > * protocol-gamespy1
> * protocol-gamespy2 > * protocol-gamespy2
> * protocol-gamespy3 > * protocol-gamespy3
> * protocol-nadeo > * protocol-nadeo
> * protocol-quake2 > * protocol-quake2
> * protocol-quake3 > * protocol-quake3
> * protocol-unreal2 > * protocol-unreal2
> * protocol-valve > * protocol-valve
Games with Additional Notes Games with Additional Notes
--- ---
### DayZ ### DayZ
DayZ uses a query port that is separate from its main game port. The query port is usually 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. the game port PLUS 24714 or 24715. You may need to pass this port in as the 'port_query' request option.
### Minecraft ### Minecraft
Some minecraft servers may not respond to a typical status query. If this is the case, try using the 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. 'minecraftping' server type instead, which uses a less accurate but more reliable solution.
### Mumble ### Mumble
For full query results from Mumble, you must be running the For full query results from Mumble, you must be running the
[GTmurmur plugin](http://www.gametracker.com/downloads/gtmurmurplugin.php). [GTmurmur plugin](http://www.gametracker.com/downloads/gtmurmurplugin.php).
If you do not wish to run the plugin, or do not require details such as channel and user lists, 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 you can use the 'mumbleping' server type instead, which uses a less accurate but more reliable solution
### Nadeo (ShootMania / TrackMania / etc) ### Nadeo (ShootMania / TrackMania / etc)
The server must have xmlrpc enabled, and you must pass the xmlrpc port to GameDig, not the connection port. 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. 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 Pass the login into to GameDig with the additional options: login, password
### Terraria ### Terraria
Requires tshock server mod, and a REST user token, which can be passed to GameDig with the Requires tshock server mod, and a REST user token, which can be passed to GameDig with the
additional option: token additional option: token
### Separate Query Port ### Separate Query Port
Games with this note use a query port which is usually not the same as the game's connection 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 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, 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. you may need to pass the 'port_query' option to GameDig as well, indicating the separate query port.
Usage from Command Line Usage from Command Line
--- ---
Want to integrate server queries from a batch script or other programming language? Want to integrate server queries from a batch script or other programming language?
You'll still need npm to install gamedig: You'll still need npm to install gamedig:
```shell ```shell
npm install gamedig -g npm install gamedig -g
``` ```
After installing gamedig globally, you can call gamedig via the command line After installing gamedig globally, you can call gamedig via the command line
using the same parameters mentioned in the API above: using the same parameters mentioned in the API above:
```shell ```shell
gamedig --type minecraft --host mc.example.com --port 11234 gamedig --type minecraft --host mc.example.com --port 11234
``` ```
The output of the command will be in JSON format. The output of the command will be in JSON format.

View File

@ -1,34 +1,34 @@
#!/usr/bin/env node #!/usr/bin/env node
var argv = require('optimist').argv; var argv = require('optimist').argv;
var debug = argv.debug; var debug = argv.debug;
delete argv.debug; delete argv.debug;
var outputFormat = argv.output; var outputFormat = argv.output;
delete argv.output; delete argv.output;
var options = {}; var options = {};
for(var key in argv) { for(var key in argv) {
var value = argv[key]; var value = argv[key];
if( if(
key == '_' key == '_'
|| key.charAt(0) == '$' || key.charAt(0) == '$'
|| (typeof value != 'string' && typeof value != 'number') || (typeof value != 'string' && typeof value != 'number')
) )
continue; continue;
options[key] = value; options[key] = value;
} }
var Gamedig = require('../lib/index'); var Gamedig = require('../lib/index');
if(debug) Gamedig.debug = true; if(debug) Gamedig.debug = true;
Gamedig.isCommandLine = true; Gamedig.isCommandLine = true;
Gamedig.query( Gamedig.query(
options, options,
function(state) { function(state) {
if(outputFormat == 'pretty') { if(outputFormat == 'pretty') {
console.log(JSON.stringify(state,null,' ')); console.log(JSON.stringify(state,null,' '));
} else { } else {
console.log(JSON.stringify(state)); console.log(JSON.stringify(state));
} }
} }
); );

View File

@ -1,19 +1,19 @@
#!/usr/bin/env node #!/usr/bin/env node
var fs = require('fs'); var fs = require('fs');
var TypeResolver = require('../lib/typeresolver'); var TypeResolver = require('../lib/typeresolver');
var generated = TypeResolver.printReadme(); var generated = TypeResolver.printReadme();
var readmeFilename = __dirname+'/../README.md'; var readmeFilename = __dirname+'/../README.md';
var readme = fs.readFileSync(readmeFilename, {encoding:'utf8'}); var readme = fs.readFileSync(readmeFilename, {encoding:'utf8'});
var marker_top = '<!--- BEGIN GENERATED GAMES -->'; var marker_top = '<!--- BEGIN GENERATED GAMES -->';
var marker_bottom = '<!--- END GENERATED GAMES -->'; var marker_bottom = '<!--- END GENERATED GAMES -->';
var start = readme.indexOf(marker_top); var start = readme.indexOf(marker_top);
start += marker_top.length; start += marker_top.length;
var end = readme.indexOf(marker_bottom); var end = readme.indexOf(marker_bottom);
var updated = readme.substr(0,start)+"\n\n"+generated+"\n"+readme.substr(end); var updated = readme.substr(0,start)+"\n\n"+generated+"\n"+readme.substr(end);
fs.writeFileSync(readmeFilename, updated); fs.writeFileSync(readmeFilename, updated);

546
games.txt
View File

@ -1,273 +1,273 @@
# id | pretty | protocol | options | parameters # id | pretty | protocol | options | parameters
#### TODO: #### TODO:
# cube1|Cube 1|cube|port=28786,port_query_offset=1 # cube1|Cube 1|cube|port=28786,port_query_offset=1
# assaultcube|Assault Cube|cube|port_query=28764 # assaultcube|Assault Cube|cube|port_query=28764
# cube2|Cube 2: Sauerbraten|cube|port=28785,port_query_offset=1 # cube2|Cube 2: Sauerbraten|cube|port=28785,port_query_offset=1
# bloodfrontier|Blood Frontier|cube # bloodfrontier|Blood Frontier|cube
# arcasimracing|Arca Sim Racing|rfactor|port=34397,port_query_offset=-100 # arcasimracing|Arca Sim Racing|rfactor|port=34397,port_query_offset=-100
# rfactor|rFactor|rfactor|port=34397,port_query_offset=-100 # rfactor|rFactor|rfactor|port=34397,port_query_offset=-100
# bfris|BFRIS|bfris|port=44001 # bfris|BFRIS|bfris|port=44001
# cs2d|Counter-Strike: 2D|cs2d|port_query=36963 # cs2d|Counter-Strike: 2D|cs2d|port_query=36963
# freelancer|Freelancer|freelancer|port_query=2302 # freelancer|Freelancer|freelancer|port_query=2302
# gr|Ghost Recon|ghostrecon|port=2346,port_query_offset=2 # gr|Ghost Recon|ghostrecon|port=2346,port_query_offset=2
# gtr2|GTR2|gtr2|port=34297,port_query_offset=1 # gtr2|GTR2|gtr2|port=34297,port_query_offset=1
# haze|Haze|haze # haze|Haze|haze
# hexen2|Hexen 2|hexen2|port_query=26900 # hexen2|Hexen 2|hexen2|port_query=26900
# openttd|OpenTTD|openttd|port=3979 # openttd|OpenTTD|openttd|port=3979
# plainsight|Plain Sight|plainsight # plainsight|Plain Sight|plainsight
# redfaction|Red Faction|redfaction|port_query=7755 # redfaction|Red Faction|redfaction|port_query=7755
# savage|Savage|savage|port_query=11235 # savage|Savage|savage|port_query=11235
# savage2|Savage 2|savage2|port_query=11235 # savage2|Savage 2|savage2|port_query=11235
# teeworlds|Teeworlds|teeworlds|port=8303 # teeworlds|Teeworlds|teeworlds|port=8303
# tribes|Tribes 1: Starsiege|tribes|port_query=28001 # tribes|Tribes 1: Starsiege|tribes|port_query=28001
# tribes2|Tribes 2|tribes2|port_query=28000 # tribes2|Tribes 2|tribes2|port_query=28000
# vcmp|Vice City Multiplayer|vcmp # vcmp|Vice City Multiplayer|vcmp
# worldinconflict|World in Conflict|worldinconflict # worldinconflict|World in Conflict|worldinconflict
ageofchivalry|Age of Chivalry|valve ageofchivalry|Age of Chivalry|valve
aoe2|Age of Empires 2|ase|port_query=27224 aoe2|Age of Empires 2|ase|port_query=27224
alienarena|Alien Arena|quake2|port_query=27910 alienarena|Alien Arena|quake2|port_query=27910
alienswarm|Alien Swarm|valve alienswarm|Alien Swarm|valve
avp2|Aliens vs Predator 2|gamespy1|port=27888 avp2|Aliens vs Predator 2|gamespy1|port=27888
# avp2010 doesn't really... have a default port or query port # avp2010 doesn't really... have a default port or query port
# both port and port_query should be specified when used # both port and port_query should be specified when used
avp2010|Aliens vs Predator 2010|valve avp2010|Aliens vs Predator 2010|valve
americasarmy|America's Army|americasarmy|port=1716,port_query_offset=1 americasarmy|America's Army|americasarmy|port=1716,port_query_offset=1
americasarmy2|America's Army 2|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 americasarmy3|America's Army 3|valve|port=8777,port_query=27020
americasarmypg|America's Army: Proving Grounds|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 arma|ArmA Armed Assault 1|gamespy2|port=2302
arma2|ArmA Armed Assault 2|valve|port=2302,port_query_offset=1 arma2|ArmA Armed Assault 2|valve|port=2302,port_query_offset=1
arma3|ArmA Armed Assault 3|valve|port=2302,port_query_offset=1 arma3|ArmA Armed Assault 3|valve|port=2302,port_query_offset=1
armagetron|Armagetron|armagetron|port=4534 armagetron|Armagetron|armagetron|port=4534
baldursgate|Baldur's Gate|gamespy1|port=6073,port_query=1470 baldursgate|Baldur's Gate|gamespy1|port=6073,port_query=1470
bf1942|Battlefield 1942|gamespy1|port=14567,port_query=23000 bf1942|Battlefield 1942|gamespy1|port=14567,port_query=23000
bfv|Battlefield Vietnam|gamespy2|port=15567,port_query=23000 bfv|Battlefield Vietnam|gamespy2|port=15567,port_query=23000
bf2|Battlefield 2|gamespy3|port=16567,port_query=29900|noChallenge bf2|Battlefield 2|gamespy3|port=16567,port_query=29900|noChallenge
bf2142|Battlefield 2142|gamespy3|port=16567,port_query=29900 bf2142|Battlefield 2142|gamespy3|port=16567,port_query=29900
bfbc2|Battlefield: Bad Company 2|battlefield|port=19567,port_query=48888|isBadCompany2 bfbc2|Battlefield: Bad Company 2|battlefield|port=19567,port_query=48888|isBadCompany2
bf3|Battlefield 3|battlefield|port=25200,port_query_offset=22000 bf3|Battlefield 3|battlefield|port=25200,port_query_offset=22000
bf4|Battlefield 4|battlefield|port=25200,port_query_offset=22000 bf4|Battlefield 4|battlefield|port=25200,port_query_offset=22000
breach|Breach|valve|port=27016 breach|Breach|valve|port=27016
breed|Breed|gamespy2|port=7649 breed|Breed|gamespy2|port=7649
brink|Brink|valve|port_query_offset=1 brink|Brink|valve|port_query_offset=1
buildandshoot|Build and Shoot|buildandshoot|port=32887,port_query=32886 buildandshoot|Build and Shoot|buildandshoot|port=32887,port_query=32886
cod|Call of Duty|quake3|port=28960 cod|Call of Duty|quake3|port=28960
coduo|Call of Duty: United Offensive|quake3|port=28960 coduo|Call of Duty: United Offensive|quake3|port=28960
cod2|Call of Duty 2|quake3|port=28960 cod2|Call of Duty 2|quake3|port=28960
cod3|Call of Duty 3|quake3|port=28960 cod3|Call of Duty 3|quake3|port=28960
cod4|Call of Duty 4: Modern Warfare|quake3|port=28960 cod4|Call of Duty 4: Modern Warfare|quake3|port=28960
codwaw|Call of Duty: World at War|quake3|port=28960 codwaw|Call of Duty: World at War|quake3|port=28960
codmw2|Call of Duty: Modern Warfare 2|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 codmw3|Call of Duty: Modern Warfare 3|valve|port_query_offset=2
callofjuarez|Call of Juarez|ase|port_query=26000 callofjuarez|Call of Juarez|ase|port_query=26000
chaser|Chaser|ase|port=3000,port_query_offset=123 chaser|Chaser|ase|port=3000,port_query_offset=123
chrome|Chrome|ase|port=27015,port_query_offset=123 chrome|Chrome|ase|port=27015,port_query_offset=123
codenameeagle|Codename Eagle|gamespy1|port_query=4711 codenameeagle|Codename Eagle|gamespy1|port_query=4711
commandos3|Commandos 3: Destination Berlin|gamespy1|port_query=6500 commandos3|Commandos 3: Destination Berlin|gamespy1|port_query=6500
cacrenegade|Command and Conquer: Renegade|gamespy1|port=4848,port_query=25300 cacrenegade|Command and Conquer: Renegade|gamespy1|port=4848,port_query=25300
contactjack|Contact J.A.C.K.|gamespy1|port_query=27888 contactjack|Contact J.A.C.K.|gamespy1|port_query=27888
cs16|Counter-Strike 1.6|valve cs16|Counter-Strike 1.6|valve
cscz|Counter-Strike: Condition Zero|valve cscz|Counter-Strike: Condition Zero|valve
css|Counter-Strike: Source|valve css|Counter-Strike: Source|valve
csgo|Counter-Strike: Global Offensive|valve||isCsGo csgo|Counter-Strike: Global Offensive|valve||isCsGo
crossracing|Cross Racing Championship|ase|port=12321,port_query_offset=123 crossracing|Cross Racing Championship|ase|port=12321,port_query_offset=123
crysis|Crysis|gamespy3|port=64087 crysis|Crysis|gamespy3|port=64087
crysiswars|Crysis Wars|gamespy3|port=64100 crysiswars|Crysis Wars|gamespy3|port=64100
crysis2|Crysis 2|gamespy3|port=64000 crysis2|Crysis 2|gamespy3|port=64000
daikatana|Daikatana|quake2|port=27982,port_query_offset=10 daikatana|Daikatana|quake2|port=27982,port_query_offset=10
dmomam|Dark Messiah of Might and Magic|valve dmomam|Dark Messiah of Might and Magic|valve
darkesthour|Darkest Hour|unreal2|port=7757,port_query_offset=1 darkesthour|Darkest Hour|unreal2|port=7757,port_query_offset=1
dayz|DayZ|valve|port=2302,port_query_offset=24714|doc_notes=dayz dayz|DayZ|valve|port=2302,port_query_offset=24714|doc_notes=dayz
deadlydozenpt|Deadly Dozen: Pacific Theater|gamespy1|port_query=25300 deadlydozenpt|Deadly Dozen: Pacific Theater|gamespy1|port_query=25300
dh2005|Deer Hunter 2005|gamespy2|port=23459,port_query=34567 dh2005|Deer Hunter 2005|gamespy2|port=23459,port_query=34567
descent3|Descent 3|gamespy1|port=2092,port_query=20142 descent3|Descent 3|gamespy1|port=2092,port_query=20142
deusex|Deus Ex|gamespy2|port=7791,port_query_offset=1 deusex|Deus Ex|gamespy2|port=7791,port_query_offset=1
devastation|Devastation|unreal2|port=7777,port_query_offset=1 devastation|Devastation|unreal2|port=7777,port_query_offset=1
dinodday|Dino D-Day|valve dinodday|Dino D-Day|valve
dirttrackracing2|Dirt Track Racing 2|gamespy1|port=32240,port_query_offset=-100 dirttrackracing2|Dirt Track Racing 2|gamespy1|port=32240,port_query_offset=-100
dod|Day of Defeat|valve dod|Day of Defeat|valve
dods|Day of Defeat: Source|valve dods|Day of Defeat: Source|valve
doom3|Doom 3|doom3|port=27666 doom3|Doom 3|doom3|port=27666
dota2|DOTA 2|valve dota2|DOTA 2|valve
drakan|Drakan|gamespy1|port=27045,port_query_offset=1 drakan|Drakan|gamespy1|port=27045,port_query_offset=1
etqw|Enemy Territory Quake Wars|doom3|port=3074,port_query=27733|isEtqw,hasSpaceBeforeClanTag,hasClanTag,hasTypeFlag etqw|Enemy Territory Quake Wars|doom3|port=3074,port_query=27733|isEtqw,hasSpaceBeforeClanTag,hasClanTag,hasTypeFlag
fear|F.E.A.R.|gamespy2|port_query=27888 fear|F.E.A.R.|gamespy2|port_query=27888
f12002|F1 2002|gamespy1|port_query=3297 f12002|F1 2002|gamespy1|port_query=3297
f1c9902|F1 Challenge 99-02|gamespy1|port_query=34397 f1c9902|F1 Challenge 99-02|gamespy1|port_query=34397
farcry|Far Cry|ase|port=49001,port_query_offset=123 farcry|Far Cry|ase|port=49001,port_query_offset=123
farcry2|Far Cry|ase|port_query=14001 farcry2|Far Cry|ase|port_query=14001
fortressforever|Fortress Forever|valve fortressforever|Fortress Forever|valve
flashpoint|Flashpoint|gamespy1|port=2302,port_query_offset=1 flashpoint|Flashpoint|gamespy1|port=2302,port_query_offset=1
ffow|Frontlines: Fuel of War|ffow|port=5476,port_query_offset=2 ffow|Frontlines: Fuel of War|ffow|port=5476,port_query_offset=2
garrysmod|Garry's Mod|valve garrysmod|Garry's Mod|valve
graw|Ghost Recon: Advanced Warfighter|gamespy2|port_query=15250 graw|Ghost Recon: Advanced Warfighter|gamespy2|port_query=15250
graw2|Ghost Recon: Advanced Warfighter 2|gamespy2|port_query=16250 graw2|Ghost Recon: Advanced Warfighter 2|gamespy2|port_query=16250
giantscitizenkabuto|Giants: Citizen Kabuto|gamespy1|port_query=8911 giantscitizenkabuto|Giants: Citizen Kabuto|gamespy1|port_query=8911
globaloperations|Global Operations|gamespy1|port_query=28672 globaloperations|Global Operations|gamespy1|port_query=28672
gore|Gore|gamespy1|port=27777,port_query_offset=1 gore|Gore|gamespy1|port=27777,port_query_offset=1
gunmanchronicles|Gunman Chronicles|valve gunmanchronicles|Gunman Chronicles|valve
hldm|Half-Life 1 Deathmatch|valve hldm|Half-Life 1 Deathmatch|valve
hl2dm|Half-Life 2 Deathmatch|valve hl2dm|Half-Life 2 Deathmatch|valve
halo|Halo|gamespy2|port=2302 halo|Halo|gamespy2|port=2302
halo2|Halo 2|gamespy2|port=2302 halo2|Halo 2|gamespy2|port=2302
heretic2|Heretic 2|gamespy1|port_query=28910 heretic2|Heretic 2|gamespy1|port_query=28910
hexenworld|Hexen World|hexenworld|port_query=26950 hexenworld|Hexen World|hexenworld|port_query=26950
hidden|The Hidden: Source|valve hidden|The Hidden: Source|valve
had2|Hidden and Dangerous 2|gamespy1|port=11001,port_query_offset=3 had2|Hidden and Dangerous 2|gamespy1|port=11001,port_query_offset=3
homefront|Homefront|valve homefront|Homefront|valve
homeworld2|Homeworld 2|gamespy1|port_query=6500 homeworld2|Homeworld 2|gamespy1|port_query=6500
igi2|IGI-2: Covert Strike|gamespy1|port_query=26001 igi2|IGI-2: Covert Strike|gamespy1|port_query=26001
il2|IL-2 Sturmovik|gamespy1|port_query=21000 il2|IL-2 Sturmovik|gamespy1|port_query=21000
insurgency|Insurgency|valve insurgency|Insurgency|valve
ironstorm|Iron Storm|gamespy1|port_query=3505 ironstorm|Iron Storm|gamespy1|port_query=3505
jamesbondnightfire|James Bond: Nightfire|gamespy1|port_query=6550 jamesbondnightfire|James Bond: Nightfire|gamespy1|port_query=6550
jc2mp|Just Cause 2 Multiplayer|jc2mp|port=7777 jc2mp|Just Cause 2 Multiplayer|jc2mp|port=7777
killingfloor|Killing Floor|killingfloor|port=7707,port_query_offset=1 killingfloor|Killing Floor|killingfloor|port=7707,port_query_offset=1
kingpin|Kingpin: Life of Crime|gamespy1|port=31510,port_query_offset=-10 kingpin|Kingpin: Life of Crime|gamespy1|port=31510,port_query_offset=-10
kisspc|KISS Psycho Circus|gamespy1|port=7777,port_query_offset=1 kisspc|KISS Psycho Circus|gamespy1|port=7777,port_query_offset=1
kzmod|KzMod|valve kzmod|KzMod|valve
left4dead|Left 4 Dead|valve left4dead|Left 4 Dead|valve
left4dead2|Left 4 Dead 2|valve left4dead2|Left 4 Dead 2|valve
m2mp|Mafia 2 Multiplayer|m2mp|port=27016,port_query_offset=1 m2mp|Mafia 2 Multiplayer|m2mp|port=27016,port_query_offset=1
mohaa|Medal of Honor: Allied Assault|gamespy1|port=12203,port_query_offset=97 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 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 mohab|Medal of Honor: Airborne|gamespy1|port=12203,port_query_offset=97
mohsh|Medal of Honor: Spearhead|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 mohbt|Medal of Honor: Breakthrough|gamespy1|port=12203,port_query_offset=97
moh2010|Medal of Honor 2010|battlefield|port=7673,port_query=48888 moh2010|Medal of Honor 2010|battlefield|port=7673,port_query=48888
mohwf|Medal of Honor: Warfighter|battlefield|port=25200,port_query_offset=22000 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 minecraft|Minecraft|gamespy3|port=25565|maxAttempts=2,srvRecord=_minecraft._tcp,doc_notes=minecraft
minecraftping|Minecraft|minecraftping|port=25565|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 mnc|Monday Night Combat|valve|port=7777,port_query=27016
mtavc|Multi Theft Auto: Vice City|ase|port=22003,port_query_offset=123 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 mtasa|Multi Theft Auto: San Andreas|ase|port=22003,port_query_offset=123
mumble|Mumble|mumble|port=64738,port_query=27800|doc_notes=mumble mumble|Mumble|mumble|port=64738,port_query=27800|doc_notes=mumble
mumbleping|Mumble|mumbleping|port=64738|doc_notes=mumble mumbleping|Mumble|mumbleping|port=64738|doc_notes=mumble
mutantfactions|Mutant Factions|mutantfactions|port=11235 mutantfactions|Mutant Factions|mutantfactions|port=11235
nascarthunder2004|Nascar Thunder 2004|gamespy2|port_query=13333 nascarthunder2004|Nascar Thunder 2004|gamespy2|port_query=13333
netpanzer|netPanzer|gamespy1|3030 netpanzer|netPanzer|gamespy1|3030
nmrih|No More Room in Hell|valve nmrih|No More Room in Hell|valve
ns|Natural Selection|valve ns|Natural Selection|valve
ns2|Natural Selection 2|valve|port_query_offset=1 ns2|Natural Selection 2|valve|port_query_offset=1
nfshp2|Need for Speed: Hot Pursuit 2|gamespy1|port_query=61220 nfshp2|Need for Speed: Hot Pursuit 2|gamespy1|port_query=61220
nab|Nerf Arena Blast|gamespy1|port=4444,port_query_offset=1 nab|Nerf Arena Blast|gamespy1|port=4444,port_query_offset=1
nwn|Neverwinter Nights|gamespy2|port_query=5121 nwn|Neverwinter Nights|gamespy2|port_query=5121
nwn2|Neverwinter Nights 2|gamespy2|port=5121,port_query=6500 nwn2|Neverwinter Nights 2|gamespy2|port=5121,port_query=6500
nexuiz|Nexuiz|quake3|port_query=26000 nexuiz|Nexuiz|quake3|port_query=26000
nitrofamily|Nitro Family|gamespy1|port_query=25601 nitrofamily|Nitro Family|gamespy1|port_query=25601
nolf|No One Lives Forever|gamespy1|port_query=27888 nolf|No One Lives Forever|gamespy1|port_query=27888
nolf2|No One Lives Forever 2|gamespy1|port_query=27890 nolf2|No One Lives Forever 2|gamespy1|port_query=27890
nucleardawn|Nuclear Dawn|valve nucleardawn|Nuclear Dawn|valve
openarena|OpenArena|quake3|port_query=27960 openarena|OpenArena|quake3|port_query=27960
operationflashpoint|Operation Flashpoint|gamespy1|port=2234,port_query_offset=1 operationflashpoint|Operation Flashpoint|gamespy1|port=2234,port_query_offset=1
painkiller|Painkiller|ase|port=3455,port_query_offset=123 painkiller|Painkiller|ase|port=3455,port_query_offset=123
postal2|Postal 2|gamespy1|port=7777,port_query_offset=1 postal2|Postal 2|gamespy1|port=7777,port_query_offset=1
prey|Prey|doom3|port_query=27719 prey|Prey|doom3|port_query=27719
quake1|Quake 1: QuakeWorld|quake1|port=27500 quake1|Quake 1: QuakeWorld|quake1|port=27500
quake2|Quake 2|quake2|port=27910 quake2|Quake 2|quake2|port=27910
quake3|Quake 3: Arena|quake3|port=27960 quake3|Quake 3: Arena|quake3|port=27960
quake4|Quake 4|doom3|port=28004|hasClanTag quake4|Quake 4|doom3|port=28004|hasClanTag
ragdollkungfu|Rag Doll Kung Fu|valve ragdollkungfu|Rag Doll Kung Fu|valve
r6|Rainbow Six|gamespy1|port_query=2348 r6|Rainbow Six|gamespy1|port_query=2348
r6roguespear|Rainbow Six 2: Rogue Spear|gamespy1|port_query=2346 r6roguespear|Rainbow Six 2: Rogue Spear|gamespy1|port_query=2346
r6ravenshield|Rainbow Six 3: Raven Shield|gamespy1|port=7777,port_query_offset=1000 r6ravenshield|Rainbow Six 3: Raven Shield|gamespy1|port=7777,port_query_offset=1000
rallisportchallenge|RalliSport Challenge|gamespy1|port_query=17500 rallisportchallenge|RalliSport Challenge|gamespy1|port_query=17500
rallymasters|Rally Masters|gamespy1|port_query=16666 rallymasters|Rally Masters|gamespy1|port_query=16666
redorchestra|Red Orchestra|unreal2|port=7758,port_query_offset=1 redorchestra|Red Orchestra|unreal2|port=7758,port_query_offset=1
redorchestraost|Red Orchestra: Ostfront 41-45|gamespy1|port=7757,port_query_offset=10 redorchestraost|Red Orchestra: Ostfront 41-45|gamespy1|port=7757,port_query_offset=10
redorchestra2|Red Orchestra 2|valve|port=7777,port_query=27015 redorchestra2|Red Orchestra 2|valve|port=7777,port_query=27015
redline|Redline|gamespy1|port_query=25252 redline|Redline|gamespy1|port_query=25252
rtcw|Return to Castle Wolfenstein|quake3|port_query=27960 rtcw|Return to Castle Wolfenstein|quake3|port_query=27960
ricochet|Ricochet|valve ricochet|Ricochet|valve
riseofnations|Rise of Nations|gamespy1|port_query=6501 riseofnations|Rise of Nations|gamespy1|port_query=6501
rune|Rune|gamespy1|port=7777,port_query_offset=1 rune|Rune|gamespy1|port=7777,port_query_offset=1
rust|Rust|valve|port=28015,port_query_offset=1 rust|Rust|valve|port=28015,port_query_offset=1
samp|San Andreas Multiplayer|samp|port=7777 samp|San Andreas Multiplayer|samp|port=7777
ss|Serious Sam|gamespy1|port=25600,port_query_offset=1 ss|Serious Sam|gamespy1|port=25600,port_query_offset=1
ss2|Serious Sam 2|gamespy2|port=25600 ss2|Serious Sam 2|gamespy2|port=25600
shatteredhorizon|Shattered Horizon|valve shatteredhorizon|Shattered Horizon|valve
ship|The Ship|valve ship|The Ship|valve
shogo|Shogo|gamespy1|port_query=27888 shogo|Shogo|gamespy1|port_query=27888
shootmania|Shootmania|nadeo||doc_notes=nadeo-shootmania--trackmania--etc shootmania|Shootmania|nadeo||doc_notes=nadeo-shootmania--trackmania--etc
sin|SiN|gamespy1|port_query=22450 sin|SiN|gamespy1|port_query=22450
sinep|SiN Episodes|valve sinep|SiN Episodes|valve
soldat|Soldat|ase|port=13073,port_query_offset=123 soldat|Soldat|ase|port=13073,port_query_offset=123
sof|Soldier of Fortune|quake1|port_query=28910 sof|Soldier of Fortune|quake1|port_query=28910
sof2|Soldier of Fortune 2|quake3|port_query=20100 sof2|Soldier of Fortune 2|quake3|port_query=20100
stalker|S.T.A.L.K.E.R.|gamespy3|port=5445,port_query_offset=2 stalker|S.T.A.L.K.E.R.|gamespy3|port=5445,port_query_offset=2
stbc|Star Trek: Bridge Commander|gamespy1|port_query=22101 stbc|Star Trek: Bridge Commander|gamespy1|port_query=22101
stvef|Star Trek: Voyager - Elite Force|quake3|port_query=27960 stvef|Star Trek: Voyager - Elite Force|quake3|port_query=27960
stvef2|Star Trek: Voyager - Elite Force 2|quake3|port_query=29253 stvef2|Star Trek: Voyager - Elite Force 2|quake3|port_query=29253
swbf|Star Wars: Battlefront|gamespy2|port_query=3658 swbf|Star Wars: Battlefront|gamespy2|port_query=3658
swbf2|Star Wars: Battlefront 2|gamespy2|port_query=3658 swbf2|Star Wars: Battlefront 2|gamespy2|port_query=3658
swjk|Star Wars: Jedi Knight|quake3|port_query=29070 swjk|Star Wars: Jedi Knight|quake3|port_query=29070
swjk2|Star Wars: Jedi Knight 2|quake3|port_query=28070 swjk2|Star Wars: Jedi Knight 2|quake3|port_query=28070
swrc|Star Wars: Republic Commando|gamespy2|port=7777,port_query=11138 swrc|Star Wars: Republic Commando|gamespy2|port=7777,port_query=11138
starbound|Starbound|valve|port=21025 starbound|Starbound|valve|port=21025
suicidesurvival|Suicide Survival|valve suicidesurvival|Suicide Survival|valve
swat4|SWAT 4|gamespy2|port=10480,port_query_offset=2 swat4|SWAT 4|gamespy2|port=10480,port_query_offset=2
svencoop|Sven Coop|valve svencoop|Sven Coop|valve
synergy|Synergy|valve synergy|Synergy|valve
tacticalops|Tactical Ops|gamespy1|port=7777,port_query_offset=1 tacticalops|Tactical Ops|gamespy1|port=7777,port_query_offset=1
teamfactor|Team Factor|gamespy1|port_query=57778 teamfactor|Team Factor|gamespy1|port_query=57778
tfc|Team Fortress Classic|valve tfc|Team Fortress Classic|valve
tf2|Team Fortress 2|valve tf2|Team Fortress 2|valve
teamspeak2|Teamspeak 2|teamspeak2|port=8767,port_query=51234 teamspeak2|Teamspeak 2|teamspeak2|port=8767,port_query=51234
teamspeak3|Teamspeak 3|teamspeak3|port=9987,port_query=10011 teamspeak3|Teamspeak 3|teamspeak3|port=9987,port_query=10011
terminus|Terminus|gamespy1|port_query=12286 terminus|Terminus|gamespy1|port_query=12286
terraria|Terraria|terraria|port=7777,port_query_offset=101|doc_notes=terraria terraria|Terraria|terraria|port=7777,port_query_offset=101|doc_notes=terraria
thps3|Tony Hawk's Pro Skater 3|gamespy1|port_query=6500 thps3|Tony Hawk's Pro Skater 3|gamespy1|port_query=6500
thps4|Tony Hawk's Pro Skater 4|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 thu2|Tony Hawk's Underground 2|gamespy1|port_query=5153
trackmania2|Trackmania 2|nadeo||doc_notes=nadeo-shootmania--trackmania--etc trackmania2|Trackmania 2|nadeo||doc_notes=nadeo-shootmania--trackmania--etc
trackmaniaforever|Trackmania Forever|nadeo||doc_notes=nadeo-shootmania--trackmania--etc trackmaniaforever|Trackmania Forever|nadeo||doc_notes=nadeo-shootmania--trackmania--etc
tremulous|Tremulous|quake3|port_query=30720 tremulous|Tremulous|quake3|port_query=30720
tribesvengeance|Tribes: Vengeance|gamespy2|port=7777,port_query_offset=1 tribesvengeance|Tribes: Vengeance|gamespy2|port=7777,port_query_offset=1
tron20|Tron 2.0|gamespy2|port_query=27888 tron20|Tron 2.0|gamespy2|port_query=27888
turok2|Turok 2|gamespy1|port_query=12880 turok2|Turok 2|gamespy1|port_query=12880
universalcombat|Universal Combat|ase|port=1135,port_query_offset=123 universalcombat|Universal Combat|ase|port=1135,port_query_offset=123
unreal|Unreal|gamespy1|port=7777,port_query_offset=1 unreal|Unreal|gamespy1|port=7777,port_query_offset=1
ut|Unreal Tournament|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 ut2003|Unreal Tournament 2003|unreal2|port=7757,port_query_offset=1
ut2004|Unreal Tournament 2004|ut2004|port=7777,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 ut3|Unreal Tournament 3|ut3|port=7777,port_query_offset=-1277
urbanterror|Urban Terror|quake3|port_query=27960 urbanterror|Urban Terror|quake3|port_query=27960
v8supercar|V8 Supercar Challenge|gamespy1|port_query=16700 v8supercar|V8 Supercar Challenge|gamespy1|port_query=16700
ventrilo|Ventrilo|ventrilo|port=3784 ventrilo|Ventrilo|ventrilo|port=3784
vietcong|Vietcong|gamespy1|port=5425,port_query=15425 vietcong|Vietcong|gamespy1|port=5425,port_query=15425
vietcong2|Vietcong 2|gamespy2|port=5001,port_query=19967 vietcong2|Vietcong 2|gamespy2|port=5001,port_query=19967
warsow|Warsow|warsow|port=44400 warsow|Warsow|warsow|port=44400
wheeloftime|Wheel of Time|gamespy1|port=7777,port_query_offset=1 wheeloftime|Wheel of Time|gamespy1|port=7777,port_query_offset=1
wolfenstein2009|Wolfenstein 2009|doom3|port_query=27666|hasSpaceBeforeClanTag,hasClanTag,hasTypeFlag wolfenstein2009|Wolfenstein 2009|doom3|port_query=27666|hasSpaceBeforeClanTag,hasClanTag,hasTypeFlag
wolfensteinet|Wolfenstein: Enemy Territory|quake3|port_query=27960 wolfensteinet|Wolfenstein: Enemy Territory|quake3|port_query=27960
xpandrally|Xpand Rally|ase|port=28015,port_query_offset=123 xpandrally|Xpand Rally|ase|port=28015,port_query_offset=123
zombiemaster|Zombie Master|valve zombiemaster|Zombie Master|valve
zps|Zombie Panic: Source|valve zps|Zombie Panic: Source|valve

View File

@ -1,74 +1,74 @@
/* based on Simple JavaScript Inheritance /* based on Simple JavaScript Inheritance
* By John Resig http://ejohn.org/ * By John Resig http://ejohn.org/
* MIT Licensed. * MIT Licensed.
*/ */
var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/; var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
// The base Class implementation (does nothing) // The base Class implementation (does nothing)
var Class = function(){}; var Class = function(){};
// Create a new Class that inherits from this class // Create a new Class that inherits from this class
Class.extend = function() { Class.extend = function() {
var args = Array.prototype.slice.call(arguments); var args = Array.prototype.slice.call(arguments);
var name = 'Class'; var name = 'Class';
var parent = this; var parent = this;
var prop = {}; var prop = {};
if(typeof args[0] == 'string') name = args.shift(); if(typeof args[0] == 'string') name = args.shift();
if(args.length >= 2) parent = args.shift(); if(args.length >= 2) parent = args.shift();
prop = args.shift(); prop = args.shift();
// Copy prototype from the parent object // Copy prototype from the parent object
var prototype = {}; var prototype = {};
for(var name in parent.prototype) { for(var name in parent.prototype) {
prototype[name] = parent.prototype[name]; prototype[name] = parent.prototype[name];
} }
// Copy the properties over onto the new prototype // Copy the properties over onto the new prototype
for(var name in prop) { for(var name in prop) {
if(typeof prop[name] == "function" && fnTest.test(prop[name])) { if(typeof prop[name] == "function" && fnTest.test(prop[name])) {
// this is a function that references _super, so we have to wrap it // this is a function that references _super, so we have to wrap it
// and provide it with its super function // and provide it with its super function
prototype[name] = (function(name, fn){ prototype[name] = (function(name, fn){
return function() { return function() {
var tmp = this._super; var tmp = this._super;
// Add a new ._super() method that is the same method // Add a new ._super() method that is the same method
// but on the super-class // but on the super-class
if(typeof parent.prototype[name] == 'undefined') { if(typeof parent.prototype[name] == 'undefined') {
if(name == 'init') this._super = parent.prototype.constructor; 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 = function() { throw new Error('Called _super in method without a parent'); }
} else this._super = parent.prototype[name]; } else this._super = parent.prototype[name];
// The method only need to be bound temporarily, so we // The method only need to be bound temporarily, so we
// remove it when we're done executing // remove it when we're done executing
var ret = fn.apply(this, arguments); var ret = fn.apply(this, arguments);
this._super = tmp; this._super = tmp;
return ret; return ret;
}; };
})(name, prop[name]); })(name, prop[name]);
} else { } else {
prototype[name] = prop[name]; prototype[name] = prop[name];
} }
} }
// The dummy class constructor // The dummy class constructor
function Class() { function Class() {
// All construction is actually done in the init method // All construction is actually done in the init method
if(this.init) this.init.apply(this, arguments); if(this.init) this.init.apply(this, arguments);
} }
// Populate our constructed prototype object // Populate our constructed prototype object
Class.prototype = prototype; Class.prototype = prototype;
// Enforce the constructor to be what we expect // Enforce the constructor to be what we expect
Class.prototype.constructor = Class; Class.prototype.constructor = Class;
// And make this class extendable // And make this class extendable
Class.extend = arguments.callee; Class.extend = arguments.callee;
return Class; return Class;
}; };
module.exports = Class; module.exports = Class;

View File

@ -1,80 +1,80 @@
var dgram = require('dgram'), var dgram = require('dgram'),
EventEmitter = require('events').EventEmitter, EventEmitter = require('events').EventEmitter,
util = require('util'), util = require('util'),
dns = require('dns'), dns = require('dns'),
TypeResolver = require('./typeresolver'); TypeResolver = require('./typeresolver');
var activeQueries = []; var activeQueries = [];
var udpSocket = dgram.createSocket('udp4'); var udpSocket = dgram.createSocket('udp4');
udpSocket.unref(); udpSocket.unref();
udpSocket.bind(21943); udpSocket.bind(21943);
udpSocket.on('message', function(buffer, rinfo) { udpSocket.on('message', function(buffer, rinfo) {
if(Gamedig.debug) console.log(rinfo.address+':'+rinfo.port+" <--UDP "+buffer.toString('hex')); if(Gamedig.debug) console.log(rinfo.address+':'+rinfo.port+" <--UDP "+buffer.toString('hex'));
for(var i = 0; i < activeQueries.length; i++) { for(var i = 0; i < activeQueries.length; i++) {
var query = activeQueries[i]; var query = activeQueries[i];
if( if(
query.options.address != rinfo.address query.options.address != rinfo.address
&& query.options.altaddress != rinfo.address && query.options.altaddress != rinfo.address
) continue; ) continue;
if(query.options.port_query != rinfo.port) continue; if(query.options.port_query != rinfo.port) continue;
query._udpResponse(buffer); query._udpResponse(buffer);
break; break;
} }
}); });
udpSocket.on('error', function(e) { udpSocket.on('error', function(e) {
if(Gamedig.debug) console.log("UDP ERROR: "+e); if(Gamedig.debug) console.log("UDP ERROR: "+e);
}); });
Gamedig = { Gamedig = {
query: function(options,callback) { query: function(options,callback) {
if(callback) options.callback = callback; if(callback) options.callback = callback;
var query; var query;
try { try {
query = TypeResolver.lookup(options.type); query = TypeResolver.lookup(options.type);
} catch(e) { } catch(e) {
process.nextTick(function() { process.nextTick(function() {
callback({error:e.message}); callback({error:e.message});
}); });
return; return;
} }
query.debug = Gamedig.debug; query.debug = Gamedig.debug;
query.udpSocket = udpSocket; query.udpSocket = udpSocket;
query.type = options.type; query.type = options.type;
if(!('port' in query.options) && ('port_query' in query.options)) { if(!('port' in query.options) && ('port_query' in query.options)) {
if(Gamedig.isCommandLine) { if(Gamedig.isCommandLine) {
process.stderr.write( process.stderr.write(
"Warning! This game is so old, that we don't know" "Warning! This game is so old, that we don't know"
+" what the server's connection port is. We've guessed that" +" what the server's connection port is. We've guessed that"
+" the query port for "+query.type+" is "+query.options.port_query+"." +" the query port for "+query.type+" is "+query.options.port_query+"."
+" If you know the connection port for this type of server, please let" +" If you know the connection port for this type of server, please let"
+" us know on the GameDig issue tracker, thanks!\n" +" us know on the GameDig issue tracker, thanks!\n"
); );
} }
query.options.port = query.options.port_query; query.options.port = query.options.port_query;
delete query.options.port_query; delete query.options.port_query;
} }
// copy over options // copy over options
for(var i in options) query.options[i] = options[i]; for(var i in options) query.options[i] = options[i];
activeQueries.push(query); activeQueries.push(query);
query.on('finished',function(state) { query.on('finished',function(state) {
var i = activeQueries.indexOf(query); var i = activeQueries.indexOf(query);
if(i >= 0) activeQueries.splice(i, 1); if(i >= 0) activeQueries.splice(i, 1);
}); });
process.nextTick(function() { process.nextTick(function() {
query.start(); query.start();
}); });
return query; return query;
} }
}; };
module.exports = Gamedig; module.exports = Gamedig;

View File

@ -1,124 +1,124 @@
var Iconv = require('iconv-lite'), var Iconv = require('iconv-lite'),
Long = require('long'); Long = require('long');
function readUInt64BE(buffer,offset) { function readUInt64BE(buffer,offset) {
var high = buffer.readUInt32BE(offset); var high = buffer.readUInt32BE(offset);
var low = buffer.readUInt32BE(offset+4); var low = buffer.readUInt32BE(offset+4);
return new Long(low,high,true); return new Long(low,high,true);
} }
function readUInt64LE(buffer,offset) { function readUInt64LE(buffer,offset) {
var low = buffer.readUInt32LE(offset); var low = buffer.readUInt32LE(offset);
var high = buffer.readUInt32LE(offset+4); var high = buffer.readUInt32LE(offset+4);
return new Long(low,high,true); return new Long(low,high,true);
} }
function Reader(query,buffer) { function Reader(query,buffer) {
this.query = query; this.query = query;
this.buffer = buffer; this.buffer = buffer;
this.i = 0; this.i = 0;
} }
Reader.prototype = { Reader.prototype = {
offset: function() { return this.i; }, offset: function() { return this.i; },
skip: function(i) { this.i += i; }, skip: function(i) { this.i += i; },
string: function() { string: function() {
var args = Array.prototype.slice.call(arguments); var args = Array.prototype.slice.call(arguments);
var options = {}; var options = {};
if(args.length == 0) { if(args.length == 0) {
options = {}; options = {};
} else if(args.length == 1) { } else if(args.length == 1) {
if(typeof args[0] == 'string') options = { delimiter: args[0] }; if(typeof args[0] == 'string') options = { delimiter: args[0] };
else if(typeof args[0] == 'number') options = { length: args[0] }; else if(typeof args[0] == 'number') options = { length: args[0] };
else options = args[0]; else options = args[0];
} }
options.encoding = options.encoding || this.query.encoding; options.encoding = options.encoding || this.query.encoding;
if(options.encoding == 'latin1') options.encoding = 'win1252'; if(options.encoding == 'latin1') options.encoding = 'win1252';
var start = this.i+0; var start = this.i+0;
var end = start; var end = start;
if(!('length' in options)) { if(!('length' in options)) {
// terminated by the delimiter // terminated by the delimiter
var delim = options.delimiter || this.query.delimiter; var delim = options.delimiter || this.query.delimiter;
if(typeof delim == 'string') delim = delim.charCodeAt(0); if(typeof delim == 'string') delim = delim.charCodeAt(0);
while(true) { while(true) {
if(end >= this.buffer.length) { if(end >= this.buffer.length) {
end = this.buffer.length; end = this.buffer.length;
break; break;
} }
if(this.buffer.readUInt8(end) == delim) break; if(this.buffer.readUInt8(end) == delim) break;
end++; end++;
} }
this.i = end+1; this.i = end+1;
} else { } else {
end = start+options.length; end = start+options.length;
if(end >= this.buffer.length) { if(end >= this.buffer.length) {
end = this.buffer.length; end = this.buffer.length;
} }
this.i = end; this.i = end;
} }
var out = this.buffer.slice(start, end); var out = this.buffer.slice(start, end);
var enc = options.encoding; var enc = options.encoding;
if(enc == 'utf8' || enc == 'ucs2' || enc == 'binary') { if(enc == 'utf8' || enc == 'ucs2' || enc == 'binary') {
out = out.toString(enc); out = out.toString(enc);
} else { } else {
out = Iconv.decode(out,enc); out = Iconv.decode(out,enc);
} }
return out; return out;
}, },
int: function(bytes) { int: function(bytes) {
var r = 0; var r = 0;
if(this.remaining() >= bytes) { if(this.remaining() >= bytes) {
if(this.query.byteorder == 'be') { if(this.query.byteorder == 'be') {
if(bytes == 1) r = this.buffer.readInt8(this.i); if(bytes == 1) r = this.buffer.readInt8(this.i);
else if(bytes == 2) r = this.buffer.readInt16BE(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 == 4) r = this.buffer.readInt32BE(this.i);
} else { } else {
if(bytes == 1) r = this.buffer.readInt8(this.i); if(bytes == 1) r = this.buffer.readInt8(this.i);
else if(bytes == 2) r = this.buffer.readInt16LE(this.i); else if(bytes == 2) r = this.buffer.readInt16LE(this.i);
else if(bytes == 4) r = this.buffer.readInt32LE(this.i); else if(bytes == 4) r = this.buffer.readInt32LE(this.i);
} }
} }
this.i += bytes; this.i += bytes;
return r; return r;
}, },
uint: function(bytes) { uint: function(bytes) {
var r = 0; var r = 0;
if(this.remaining() >= bytes) { if(this.remaining() >= bytes) {
if(this.query.byteorder == 'be') { if(this.query.byteorder == 'be') {
if(bytes == 1) r = this.buffer.readUInt8(this.i); if(bytes == 1) r = this.buffer.readUInt8(this.i);
else if(bytes == 2) r = this.buffer.readUInt16BE(this.i); else if(bytes == 2) r = this.buffer.readUInt16BE(this.i);
else if(bytes == 4) r = this.buffer.readUInt32BE(this.i); else if(bytes == 4) r = this.buffer.readUInt32BE(this.i);
else if(bytes == 8) r = readUInt64BE(this.buffer,this.i).toString(); else if(bytes == 8) r = readUInt64BE(this.buffer,this.i).toString();
} else { } else {
if(bytes == 1) r = this.buffer.readUInt8(this.i); if(bytes == 1) r = this.buffer.readUInt8(this.i);
else if(bytes == 2) r = this.buffer.readUInt16LE(this.i); else if(bytes == 2) r = this.buffer.readUInt16LE(this.i);
else if(bytes == 4) r = this.buffer.readUInt32LE(this.i); else if(bytes == 4) r = this.buffer.readUInt32LE(this.i);
else if(bytes == 8) r = readUInt64LE(this.buffer,this.i).toString(); else if(bytes == 8) r = readUInt64LE(this.buffer,this.i).toString();
} }
} }
this.i += bytes; this.i += bytes;
return r; return r;
}, },
float: function() { float: function() {
var r = 0; var r = 0;
if(this.remaining() >= 4) { if(this.remaining() >= 4) {
if(this.query.byteorder == 'be') r = this.buffer.readFloatBE(this.i); if(this.query.byteorder == 'be') r = this.buffer.readFloatBE(this.i);
else r = this.buffer.readFloatLE(this.i); else r = this.buffer.readFloatLE(this.i);
} }
this.i += 4; this.i += 4;
return r; return r;
}, },
remaining: function() { remaining: function() {
return this.buffer.length-this.i; return this.buffer.length-this.i;
}, },
rest: function() { rest: function() {
return this.buffer.slice(this.i); return this.buffer.slice(this.i);
}, },
done: function() { done: function() {
return this.i >= this.buffer.length; return this.i >= this.buffer.length;
} }
}; };
module.exports = Reader; module.exports = Reader;

View File

@ -1,41 +1,41 @@
{ {
"name": "gamedig", "name": "gamedig",
"description": "Query for the status of any game server in Node.JS", "description": "Query for the status of any game server in Node.JS",
"tags": [ "tags": [
"srcds", "srcds",
"query", "query",
"game", "game",
"utility", "utility",
"util", "util",
"server" "server"
], ],
"main": "lib/index.js", "main": "lib/index.js",
"author": "Michael Morrison", "author": "Michael Morrison",
"version": "0.2.9", "version": "0.2.9",
"repository" : { "repository" : {
"type" : "git", "type" : "git",
"url" : "https://github.com/sonicsnes/node-gamedig.git" "url" : "https://github.com/sonicsnes/node-gamedig.git"
}, },
"bugs" : { "bugs" : {
"url" : "https://github.com/sonicsnes/node-gamedig/issues" "url" : "https://github.com/sonicsnes/node-gamedig/issues"
}, },
"licenses" : [ "licenses" : [
{ {
"type" : "MIT", "type" : "MIT",
"url" : "https://raw.github.com/sonicsnes/node-gamedig/master/LICENSE" "url" : "https://raw.github.com/sonicsnes/node-gamedig/master/LICENSE"
} }
], ],
"dependencies": { "dependencies": {
"iconv-lite": "~0.2.11", "iconv-lite": "~0.2.11",
"long": "~1.1.2", "long": "~1.1.2",
"async": "~0.2.10", "async": "~0.2.10",
"compressjs": "~1.0.1", "compressjs": "~1.0.1",
"gbxremote": "~0.1.4", "gbxremote": "~0.1.4",
"request": "~2.33.0", "request": "~2.33.0",
"optimist": "~0.6.0", "optimist": "~0.6.0",
"varint": "~1.0.0" "varint": "~1.0.0"
}, },
"bin": { "bin": {
"gamedig": "bin/gamedig.js" "gamedig": "bin/gamedig.js"
} }
} }

View File

@ -1,64 +1,64 @@
module.exports = require('./core').extend({ module.exports = require('./core').extend({
init: function() { init: function() {
this._super(); this._super();
this.encoding = 'latin1'; this.encoding = 'latin1';
this.byteorder = 'be'; this.byteorder = 'be';
}, },
run: function(state) { run: function(state) {
var self = this; var self = this;
var b = new Buffer([0,0x35,0,0,0,0,0,0x11]); var b = new Buffer([0,0x35,0,0,0,0,0,0x11]);
this.udpSend(b,function(buffer) { this.udpSend(b,function(buffer) {
var reader = self.reader(buffer); var reader = self.reader(buffer);
reader.skip(6); reader.skip(6);
state.raw.port = self.readUInt(reader); state.raw.port = self.readUInt(reader);
state.raw.hostname = self.readString(reader,buffer); state.raw.hostname = self.readString(reader,buffer);
state.name = self.stripColorCodes(self.readString(reader,buffer)); state.name = self.stripColorCodes(self.readString(reader,buffer));
state.raw.numplayers = self.readUInt(reader); state.raw.numplayers = self.readUInt(reader);
state.raw.versionmin = self.readUInt(reader); state.raw.versionmin = self.readUInt(reader);
state.raw.versionmax = self.readUInt(reader); state.raw.versionmax = self.readUInt(reader);
state.raw.version = self.readString(reader,buffer); state.raw.version = self.readString(reader,buffer);
state.maxplayers = self.readUInt(reader); state.maxplayers = self.readUInt(reader);
var players = self.readString(reader,buffer); var players = self.readString(reader,buffer);
var list = players.split('\n'); var list = players.split('\n');
for(var i = 0; i < list.length; i++) { for(var i = 0; i < list.length; i++) {
if(!list[i]) continue; if(!list[i]) continue;
state.players.push({ state.players.push({
name:self.stripColorCodes(list[i]) name:self.stripColorCodes(list[i])
}); });
} }
state.raw.options = self.stripColorCodes(self.readString(reader,buffer)); state.raw.options = self.stripColorCodes(self.readString(reader,buffer));
state.raw.uri = self.readString(reader,buffer); state.raw.uri = self.readString(reader,buffer);
state.raw.globalids = self.readString(reader,buffer); state.raw.globalids = self.readString(reader,buffer);
self.finish(state); self.finish(state);
return true; return true;
}); });
}, },
readUInt: function(reader) { readUInt: function(reader) {
var a = reader.uint(2); var a = reader.uint(2);
var b = reader.uint(2); var b = reader.uint(2);
return (b<<16) + a; return (b<<16) + a;
}, },
readString: function(reader,b) { readString: function(reader,b) {
var len = reader.uint(2); var len = reader.uint(2);
if(!len) return ''; if(!len) return '';
var out = ''; var out = '';
for(var i = 0; i < len; i+=2) { for(var i = 0; i < len; i+=2) {
var hi = reader.uint(1); var hi = reader.uint(1);
var lo = reader.uint(1); var lo = reader.uint(1);
if(i+1<len) out += String.fromCharCode(lo); if(i+1<len) out += String.fromCharCode(lo);
if(i+2<len) out += String.fromCharCode(hi); if(i+2<len) out += String.fromCharCode(hi);
} }
return out; return out;
}, },
stripColorCodes: function(str) { stripColorCodes: function(str) {
return str.replace(/0x[0-9a-f]{6}/g,''); return str.replace(/0x[0-9a-f]{6}/g,'');
} }
}); });

View File

@ -1,55 +1,55 @@
var request = require('request'); var request = require('request');
module.exports = require('./core').extend({ module.exports = require('./core').extend({
run: function(state) { run: function(state) {
var self = this; var self = this;
request({ request({
uri: 'http://'+this.options.address+':'+this.options.port_query+'/', uri: 'http://'+this.options.address+':'+this.options.port_query+'/',
timeout: 3000, timeout: 3000,
}, function(e,r,body) { }, function(e,r,body) {
if(e) return self.fatal('HTTP error'); if(e) return self.fatal('HTTP error');
var m = body.match(/status server for (.*?)\r|\n/); var m = body.match(/status server for (.*?)\r|\n/);
if(m) state.name = m[1]; if(m) state.name = m[1];
var m = body.match(/Current uptime: (\d+)/); var m = body.match(/Current uptime: (\d+)/);
if(m) state.raw.uptime = m[1]; if(m) state.raw.uptime = m[1];
var m = body.match(/currently running (.*?) by /); var m = body.match(/currently running (.*?) by /);
if(m) state.map = m[1]; if(m) state.map = m[1];
var m = body.match(/Current players: (\d+)\/(\d+)/); var m = body.match(/Current players: (\d+)\/(\d+)/);
if(m) { if(m) {
state.raw.numplayers = m[1]; state.raw.numplayers = m[1];
state.maxplayers = m[2]; state.maxplayers = m[2];
} }
var m = body.match(/class="playerlist"([^]+?)\/table/); var m = body.match(/class="playerlist"([^]+?)\/table/);
if(m) { if(m) {
var table = m[1]; var table = m[1];
var pre = /<tr>[^]*<td>([^]*)<\/td>[^]*<td>([^]*)<\/td>[^]*<td>([^]*)<\/td>[^]*<td>([^]*)<\/td>/g; var pre = /<tr>[^]*<td>([^]*)<\/td>[^]*<td>([^]*)<\/td>[^]*<td>([^]*)<\/td>[^]*<td>([^]*)<\/td>/g;
while(pm = pre.exec(table)) { while(pm = pre.exec(table)) {
if(pm[2] == 'Ping') continue; if(pm[2] == 'Ping') continue;
state.players.push({ state.players.push({
name: pm[1], name: pm[1],
ping: pm[2], ping: pm[2],
team: pm[3], team: pm[3],
score: pm[4] score: pm[4]
}); });
} }
} }
/* /*
var m = this.options.address.match(/(\d+)\.(\d+)\.(\d+)\.(\d+)/); var m = this.options.address.match(/(\d+)\.(\d+)\.(\d+)\.(\d+)/);
if(m) { if(m) {
var o1 = parseInt(m[1]); var o1 = parseInt(m[1]);
var o2 = parseInt(m[2]); var o2 = parseInt(m[2]);
var o3 = parseInt(m[3]); var o3 = parseInt(m[3]);
var o4 = parseInt(m[4]); var o4 = parseInt(m[4]);
var addr = o1+(o2<<8)+(o3<<16)+(o4<<24); var addr = o1+(o2<<8)+(o3<<16)+(o4<<24);
state.raw.url = 'aos://'+addr; state.raw.url = 'aos://'+addr;
} }
*/ */
self.finish(state); self.finish(state);
}); });
} }
}); });

View File

@ -1,312 +1,312 @@
var EventEmitter = require('events').EventEmitter, var EventEmitter = require('events').EventEmitter,
dns = require('dns'), dns = require('dns'),
net = require('net'), net = require('net'),
async = require('async'), async = require('async'),
Class = require('../lib/Class'), Class = require('../lib/Class'),
Reader = require('../lib/reader'); Reader = require('../lib/reader');
module.exports = Class.extend(EventEmitter,{ module.exports = Class.extend(EventEmitter,{
init: function() { init: function() {
this._super(); this._super();
this.options = { this.options = {
tcpTimeout: 1000, tcpTimeout: 1000,
udpTimeout: 1000 udpTimeout: 1000
}; };
this.maxAttempts = 1; this.maxAttempts = 1;
this.attempt = 1; this.attempt = 1;
this.finished = false; this.finished = false;
this.encoding = 'utf8'; this.encoding = 'utf8';
this.byteorder = 'le'; this.byteorder = 'le';
this.delimiter = '\0'; this.delimiter = '\0';
var self = this; var self = this;
this.globalTimeoutTimer = setTimeout(function() { this.globalTimeoutTimer = setTimeout(function() {
self.fatal('timeout'); self.fatal('timeout');
},10000); },10000);
}, },
fatal: function(err,noretry) { fatal: function(err,noretry) {
if(!noretry && this.attempt < this.maxAttempts) { if(!noretry && this.attempt < this.maxAttempts) {
this.attempt++; this.attempt++;
this.start(); this.start();
return; return;
} }
this.done({error: err.toString()}); this.done({error: err.toString()});
}, },
initState: function() { initState: function() {
return { return {
name: '', name: '',
map: '', map: '',
password: false, password: false,
raw: {}, raw: {},
maxplayers: 0, maxplayers: 0,
players: [], players: [],
bots: [] bots: []
}; };
}, },
finalizeState: function(state) {}, finalizeState: function(state) {},
finish: function(state) { finish: function(state) {
this.finalizeState(state); this.finalizeState(state);
this.done(state); this.done(state);
}, },
done: function(state) { done: function(state) {
if(this.finished) return; if(this.finished) return;
clearTimeout(this.globalTimeoutTimer); clearTimeout(this.globalTimeoutTimer);
if(this.options.notes) if(this.options.notes)
state.notes = this.options.notes; state.notes = this.options.notes;
state.query = {}; state.query = {};
if('host' in this.options) state.query.host = this.options.host; if('host' in this.options) state.query.host = this.options.host;
if('address' in this.options) state.query.address = this.options.address; if('address' in this.options) state.query.address = this.options.address;
if('port' in this.options) state.query.port = this.options.port; 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; if('port_query' in this.options) state.query.port_query = this.options.port_query;
state.query.type = this.type; state.query.type = this.type;
if('pretty' in this) state.query.pretty = this.pretty; if('pretty' in this) state.query.pretty = this.pretty;
this.reset(); this.reset();
this.finished = true; this.finished = true;
this.emit('finished',state); this.emit('finished',state);
if(this.options.callback) this.options.callback(state); if(this.options.callback) this.options.callback(state);
}, },
reset: function() { reset: function() {
if(this.timers) { if(this.timers) {
this.timers.forEach(function(timer) { this.timers.forEach(function(timer) {
clearTimeout(timer); clearTimeout(timer);
}); });
} }
this.timers = []; this.timers = [];
if(this.tcpSocket) { if(this.tcpSocket) {
this.tcpSocket.destroy(); this.tcpSocket.destroy();
delete this.tcpSocket; delete this.tcpSocket;
} }
this.udpTimeoutTimer = false; this.udpTimeoutTimer = false;
this.udpCallback = false; this.udpCallback = false;
}, },
start: function() { start: function() {
var self = this; var self = this;
var options = self.options; var options = self.options;
this.reset(); this.reset();
async.series([ async.series([
function(c) { function(c) {
// resolve host names // resolve host names
if(!('host' in options)) return c(); if(!('host' in options)) return c();
if(options.host.match(/\d+\.\d+\.\d+\.\d+/)) { if(options.host.match(/\d+\.\d+\.\d+\.\d+/)) {
options.address = options.host; options.address = options.host;
c(); c();
} else { } else {
self.parseDns(options.host,c); self.parseDns(options.host,c);
} }
}, },
function(c) { function(c) {
// calculate query port if needed // calculate query port if needed
if(!('port_query' in options) && 'port' in options) { if(!('port_query' in options) && 'port' in options) {
var offset = options.port_query_offset || 0; var offset = options.port_query_offset || 0;
options.port_query = options.port + offset; options.port_query = options.port + offset;
} }
c(); c();
}, },
function(c) { function(c) {
// run // run
self.run(self.initState()); self.run(self.initState());
} }
]); ]);
}, },
parseDns: function(host,c) { parseDns: function(host,c) {
var self = this; var self = this;
function resolveStandard(host,c) { function resolveStandard(host,c) {
dns.lookup(host, function(err,address,family) { dns.lookup(host, function(err,address,family) {
if(err) return self.fatal(err); if(err) return self.fatal(err);
self.options.address = address; self.options.address = address;
c(); c();
}); });
} }
function resolveSrv(srv,host,c) { function resolveSrv(srv,host,c) {
dns.resolve(srv+'.'+host, 'SRV', function(err,addresses) { dns.resolve(srv+'.'+host, 'SRV', function(err,addresses) {
if(err) return resolveStandard(host,c); if(err) return resolveStandard(host,c);
if(addresses.length >= 1) { if(addresses.length >= 1) {
var line = addresses[0]; var line = addresses[0];
self.options.port = line.port; self.options.port = line.port;
var srvhost = line.name; var srvhost = line.name;
if(srvhost.match(/\d+\.\d+\.\d+\.\d+/)) { if(srvhost.match(/\d+\.\d+\.\d+\.\d+/)) {
self.options.address = srvhost; self.options.address = srvhost;
c(); c();
} else { } else {
// resolve yet again // resolve yet again
resolveStandard(srvhost,c); resolveStandard(srvhost,c);
} }
return; return;
} }
return resolveStandard(host,c); return resolveStandard(host,c);
}); });
} }
if(this.srvRecord) resolveSrv(this.srvRecord,host,c); if(this.srvRecord) resolveSrv(this.srvRecord,host,c);
else resolveStandard(host,c); else resolveStandard(host,c);
}, },
// utils // utils
reader: function(buffer) { reader: function(buffer) {
return new Reader(this,buffer); return new Reader(this,buffer);
}, },
translate: function(obj,trans) { translate: function(obj,trans) {
for(var from in trans) { for(var from in trans) {
var to = trans[from]; var to = trans[from];
if(from in obj) { if(from in obj) {
if(to) obj[to] = obj[from]; if(to) obj[to] = obj[from];
delete obj[from]; delete obj[from];
} }
} }
}, },
setTimeout: function(c,t) { setTimeout: function(c,t) {
if(this.finished) return 0; if(this.finished) return 0;
var id = setTimeout(c,t); var id = setTimeout(c,t);
this.timers.push(id); this.timers.push(id);
return id; return id;
}, },
trueTest: function(str) { trueTest: function(str) {
if(typeof str == 'boolean') return str; if(typeof str == 'boolean') return str;
if(typeof str == 'number') return str != 0; if(typeof str == 'number') return str != 0;
if(typeof str == 'string') { if(typeof str == 'string') {
if(str.toLowerCase() == 'true') return true; if(str.toLowerCase() == 'true') return true;
if(str == 'yes') return true; if(str == 'yes') return true;
if(str == '1') return true; if(str == '1') return true;
} }
return false; return false;
}, },
debugBuffer: function(buffer) { debugBuffer: function(buffer) {
var out = ''; var out = '';
var out2 = ''; var out2 = '';
for(var i = 0; i < buffer.length; i++) { for(var i = 0; i < buffer.length; i++) {
var sliced = buffer.slice(i,i+1); var sliced = buffer.slice(i,i+1);
out += sliced.toString('hex')+' '; out += sliced.toString('hex')+' ';
var chr = sliced.toString(); var chr = sliced.toString();
if(chr < ' ' || chr > '~') chr = ' '; if(chr < ' ' || chr > '~') chr = ' ';
out2 += chr+' '; out2 += chr+' ';
if(out.length > 60) { if(out.length > 60) {
console.log(out); console.log(out);
console.log(out2); console.log(out2);
out = out2 = ''; out = out2 = '';
} }
} }
console.log(out); console.log(out);
console.log(out2); console.log(out2);
}, },
_tcpConnect: function(c) { _tcpConnect: function(c) {
var self = this; var self = this;
if(this.tcpSocket) return c(this.tcpSocket); if(this.tcpSocket) return c(this.tcpSocket);
var connected = false; var connected = false;
var received = new Buffer(0); var received = new Buffer(0);
var address = this.options.address; var address = this.options.address;
var port = this.options.port_query; var port = this.options.port_query;
var socket = this.tcpSocket = net.connect(port,address,function() { var socket = this.tcpSocket = net.connect(port,address,function() {
if(self.debug) console.log(address+':'+port+" TCPCONNECTED"); if(self.debug) console.log(address+':'+port+" TCPCONNECTED");
connected = true; connected = true;
c(socket); c(socket);
}); });
socket.setTimeout(10000); socket.setTimeout(10000);
socket.setNoDelay(true); socket.setNoDelay(true);
if(this.debug) console.log(address+':'+port+" TCPCONNECT"); if(this.debug) console.log(address+':'+port+" TCPCONNECT");
var writeHook = socket.write; var writeHook = socket.write;
socket.write = function(data) { socket.write = function(data) {
if(self.debug) console.log(address+':'+port+" TCP--> "+data.toString('hex')); if(self.debug) console.log(address+':'+port+" TCP--> "+data.toString('hex'));
writeHook.apply(this,arguments); writeHook.apply(this,arguments);
} }
socket.on('error', function() {}); socket.on('error', function() {});
socket.on('close', function() { socket.on('close', function() {
if(!self.tcpCallback) return; if(!self.tcpCallback) return;
if(connected) return self.fatal('Socket closed while waiting on TCP'); if(connected) return self.fatal('Socket closed while waiting on TCP');
else return self.fatal('TCP Connection Refused'); else return self.fatal('TCP Connection Refused');
}); });
socket.on('data', function(data) { socket.on('data', function(data) {
if(!self.tcpCallback) return; if(!self.tcpCallback) return;
if(self.debug) console.log(address+':'+port+" <--TCP "+data.toString('hex')); if(self.debug) console.log(address+':'+port+" <--TCP "+data.toString('hex'));
received = Buffer.concat([received,data]); received = Buffer.concat([received,data]);
if(self.tcpCallback(received)) { if(self.tcpCallback(received)) {
clearTimeout(self.tcpTimeoutTimer); clearTimeout(self.tcpTimeoutTimer);
self.tcpCallback = false; self.tcpCallback = false;
received = new Buffer(0); received = new Buffer(0);
} }
}); });
}, },
tcpSend: function(buffer,ondata) { tcpSend: function(buffer,ondata) {
var self = this; var self = this;
process.nextTick(function() { process.nextTick(function() {
if(self.tcpCallback) return self.fatal('Attempted to send TCP packet while still waiting on a managed response'); if(self.tcpCallback) return self.fatal('Attempted to send TCP packet while still waiting on a managed response');
self._tcpConnect(function(socket) { self._tcpConnect(function(socket) {
socket.write(buffer); socket.write(buffer);
}); });
if(!ondata) return; if(!ondata) return;
self.tcpTimeoutTimer = self.setTimeout(function() { self.tcpTimeoutTimer = self.setTimeout(function() {
self.tcpCallback = false; self.tcpCallback = false;
self.fatal('TCP Watchdog Timeout'); self.fatal('TCP Watchdog Timeout');
},self.options.tcpTimeout); },self.options.tcpTimeout);
self.tcpCallback = ondata; self.tcpCallback = ondata;
}); });
}, },
udpSend: function(buffer,onpacket,ontimeout) { udpSend: function(buffer,onpacket,ontimeout) {
var self = this; var self = this;
process.nextTick(function() { process.nextTick(function() {
if(self.udpCallback) return self.fatal('Attempted to send UDP packet while still waiting on a managed response'); if(self.udpCallback) return self.fatal('Attempted to send UDP packet while still waiting on a managed response');
self._udpSendNow(buffer); self._udpSendNow(buffer);
if(!onpacket) return; if(!onpacket) return;
self.udpTimeoutTimer = self.setTimeout(function() { self.udpTimeoutTimer = self.setTimeout(function() {
self.udpCallback = false; self.udpCallback = false;
var timeout = false; var timeout = false;
if(!ontimeout || ontimeout() !== true) timeout = true; if(!ontimeout || ontimeout() !== true) timeout = true;
if(timeout) self.fatal('UDP Watchdog Timeout'); if(timeout) self.fatal('UDP Watchdog Timeout');
},self.options.udpTimeout); },self.options.udpTimeout);
self.udpCallback = onpacket; self.udpCallback = onpacket;
}); });
}, },
_udpSendNow: function(buffer) { _udpSendNow: function(buffer) {
if(!('port_query' in this.options)) return this.fatal('Attempted to send without setting a port'); 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(!('address' in this.options)) return this.fatal('Attempted to send without setting an address');
if(typeof buffer == 'string') buffer = new Buffer(buffer,'binary'); 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')); 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); this.udpSocket.send(buffer,0,buffer.length,this.options.port_query,this.options.address);
}, },
_udpResponse: function(buffer) { _udpResponse: function(buffer) {
if(this.udpCallback) { if(this.udpCallback) {
var result = this.udpCallback(buffer); var result = this.udpCallback(buffer);
if(result === true) { if(result === true) {
// we're done with this udp session // we're done with this udp session
clearTimeout(this.udpTimeoutTimer); clearTimeout(this.udpTimeoutTimer);
this.udpCallback = false; this.udpCallback = false;
} }
} else { } else {
this.udpResponse(buffer); this.udpResponse(buffer);
} }
}, },
udpResponse: function() {} udpResponse: function() {}
}); });

View File

@ -1,95 +1,95 @@
module.exports = require('./core').extend({ module.exports = require('./core').extend({
init: function() { init: function() {
this._super(); this._super();
this.pretty = 'Doom 3'; this.pretty = 'Doom 3';
this.encoding = 'latin1'; this.encoding = 'latin1';
this.isEtqw = false; this.isEtqw = false;
this.hasSpaceBeforeClanTag = false; this.hasSpaceBeforeClanTag = false;
this.hasClanTag = false; this.hasClanTag = false;
this.hasTypeFlag = false; this.hasTypeFlag = false;
}, },
run: function(state) { run: function(state) {
var self = this; var self = this;
this.udpSend('\xff\xffgetInfo\x00PiNGPoNG\x00',function(buffer) { this.udpSend('\xff\xffgetInfo\x00PiNGPoNG\x00',function(buffer) {
var reader = self.reader(buffer); var reader = self.reader(buffer);
var header = reader.uint(2); var header = reader.uint(2);
if(header != 0xffff) return; if(header != 0xffff) return;
var header2 = reader.string(); var header2 = reader.string();
if(header2 != 'infoResponse') return; if(header2 != 'infoResponse') return;
var tailSize = 5; var tailSize = 5;
if(self.isEtqw) { if(self.isEtqw) {
var taskId = reader.uint(4); var taskId = reader.uint(4);
} }
var challenge = reader.uint(4); var challenge = reader.uint(4);
var protoVersion = reader.uint(4); var protoVersion = reader.uint(4);
state.raw.protocolVersion = (protoVersion>>16)+'.'+(protoVersion&0xffff); state.raw.protocolVersion = (protoVersion>>16)+'.'+(protoVersion&0xffff);
if(self.isEtqw) { if(self.isEtqw) {
var size = reader.uint(4); var size = reader.uint(4);
} }
while(!reader.done()) { while(!reader.done()) {
var key = reader.string(); var key = reader.string();
var value = self.stripColors(reader.string()); var value = self.stripColors(reader.string());
if(key == 'si_map') { if(key == 'si_map') {
value = value.replace('maps/',''); value = value.replace('maps/','');
value = value.replace('.entities',''); value = value.replace('.entities','');
} }
if(!key) break; if(!key) break;
state.raw[key] = value; state.raw[key] = value;
} }
var i = 0; var i = 0;
while(!reader.done()) { while(!reader.done()) {
i++; i++;
var player = {}; var player = {};
player.id = reader.uint(1); player.id = reader.uint(1);
if(player.id == 32) break; if(player.id == 32) break;
player.ping = reader.uint(2); player.ping = reader.uint(2);
if(!self.isEtqw) player.rate = reader.uint(4); if(!self.isEtqw) player.rate = reader.uint(4);
player.name = self.stripColors(reader.string()); player.name = self.stripColors(reader.string());
if(self.hasClanTag) { if(self.hasClanTag) {
if(self.hasSpaceBeforeClanTag) reader.uint(1); if(self.hasSpaceBeforeClanTag) reader.uint(1);
player.clantag = self.stripColors(reader.string()); player.clantag = self.stripColors(reader.string());
} }
if(self.hasTypeFlag) player.typeflag = reader.uint(1); if(self.hasTypeFlag) player.typeflag = reader.uint(1);
if(!player.ping || player.typeflag) if(!player.ping || player.typeflag)
state.bots.push(player); state.bots.push(player);
else else
state.players.push(player); state.players.push(player);
} }
state.raw.osmask = reader.uint(4); state.raw.osmask = reader.uint(4);
if(self.isEtqw) { if(self.isEtqw) {
state.raw.ranked = reader.uint(1); state.raw.ranked = reader.uint(1);
state.raw.timeleft = reader.uint(4); state.raw.timeleft = reader.uint(4);
state.raw.gamestate = reader.uint(1); state.raw.gamestate = reader.uint(1);
state.raw.servertype = reader.uint(1); state.raw.servertype = reader.uint(1);
// 0 = regular, 1 = tv // 0 = regular, 1 = tv
if(state.raw.servertype == 0) { if(state.raw.servertype == 0) {
state.raw.interestedClients = reader.uint(1); state.raw.interestedClients = reader.uint(1);
} else if(state.raw.servertype == 1) { } else if(state.raw.servertype == 1) {
state.raw.connectedClients = reader.uint(4); state.raw.connectedClients = reader.uint(4);
state.raw.maxClients = reader.uint(4); state.raw.maxClients = reader.uint(4);
} }
} }
if(state.raw.si_name) state.name = state.raw.si_name; 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_map) state.map = state.raw.si_map;
if(state.raw.si_maxplayers) state.maxplayers = parseInt(state.raw.si_maxplayers); if(state.raw.si_maxplayers) state.maxplayers = parseInt(state.raw.si_maxplayers);
if(state.raw.si_usepass == '1') state.password = true; if(state.raw.si_usepass == '1') state.password = true;
self.finish(state); self.finish(state);
return true; return true;
}); });
}, },
stripColors: function(str) { stripColors: function(str) {
// uses quake 3 color codes // uses quake 3 color codes
return str.replace(/\^(X.{6}|.)/g,''); return str.replace(/\^(X.{6}|.)/g,'');
} }
}); });

View File

@ -1,85 +1,85 @@
var async = require('async'); var async = require('async');
module.exports = require('./core').extend({ module.exports = require('./core').extend({
init: function() { init: function() {
this._super(); this._super();
this.sessionId = 1; this.sessionId = 1;
this.encoding = 'latin1'; this.encoding = 'latin1';
this.byteorder = 'be'; this.byteorder = 'be';
}, },
run: function(state) { run: function(state) {
var self = this; var self = this;
async.series([ async.series([
function(c) { function(c) {
self.sendPacket('info', function(data) { self.sendPacket('info', function(data) {
state.raw = data; state.raw = data;
if('hostname' in state.raw) state.name = state.raw.hostname; if('hostname' in state.raw) state.name = state.raw.hostname;
if('mapname' in state.raw) state.map = state.raw.mapname; if('mapname' in state.raw) state.map = state.raw.mapname;
if(self.trueTest(state.raw.password)) state.password = true; if(self.trueTest(state.raw.password)) state.password = true;
if('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers); if('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers);
c(); c();
}); });
}, },
function(c) { function(c) {
self.sendPacket('rules', function(data) { self.sendPacket('rules', function(data) {
state.raw.rules = data; state.raw.rules = data;
c(); c();
}); });
}, },
function(c) { function(c) {
self.sendPacket('players', function(data) { self.sendPacket('players', function(data) {
var players = {}; var players = {};
var teams = {}; var teams = {};
for(var ident in data) { for(var ident in data) {
var split = ident.split('_'); var split = ident.split('_');
var key = split[0]; var key = split[0];
var id = split[1]; var id = split[1];
var value = data[ident]; var value = data[ident];
if(key == 'teamname') { if(key == 'teamname') {
teams[id] = value; teams[id] = value;
} else { } else {
if(!(id in players)) players[id] = {}; if(!(id in players)) players[id] = {};
if(key == 'playername') key = 'name'; if(key == 'playername') key = 'name';
else if(key == 'team') value = parseInt(value); else if(key == 'team') value = parseInt(value);
else if(key == 'score' || key == 'ping' || key == 'deaths') value = parseInt(value); else if(key == 'score' || key == 'ping' || key == 'deaths') value = parseInt(value);
players[id][key] = value; players[id][key] = value;
} }
} }
state.raw.teams = teams; state.raw.teams = teams;
for(var i in players) state.players.push(players[i]); for(var i in players) state.players.push(players[i]);
self.finish(state); self.finish(state);
}); });
} }
]); ]);
}, },
sendPacket: function(type,callback) { sendPacket: function(type,callback) {
var self = this; var self = this;
var queryId = ''; var queryId = '';
var output = {}; var output = {};
this.udpSend('\\'+type+'\\',function(buffer) { this.udpSend('\\'+type+'\\',function(buffer) {
var reader = self.reader(buffer); var reader = self.reader(buffer);
var str = reader.string({length:buffer.length}); var str = reader.string({length:buffer.length});
var split = str.split('\\'); var split = str.split('\\');
split.shift(); split.shift();
var data = {}; var data = {};
while(split.length) { while(split.length) {
var key = split.shift(); var key = split.shift();
var value = split.shift() || ''; var value = split.shift() || '';
data[key] = value; data[key] = value;
} }
if(!('queryid' in data)) return; if(!('queryid' in data)) return;
if(queryId && data.queryid != queryId) return; if(queryId && data.queryid != queryId) return;
for(var i in data) output[i] = data[i]; for(var i in data) output[i] = data[i];
if('final' in output) { if('final' in output) {
delete output.final; delete output.final;
delete output.queryid; delete output.queryid;
callback(output); callback(output);
return true; return true;
} }
}); });
} }
}); });

View File

@ -1,96 +1,96 @@
module.exports = require('./core').extend({ module.exports = require('./core').extend({
init: function() { init: function() {
this._super(); this._super();
this.sessionId = 1; this.sessionId = 1;
this.encoding = 'latin1'; this.encoding = 'latin1';
this.byteorder = 'be'; this.byteorder = 'be';
}, },
run: function(state) { run: function(state) {
var self = this; var self = this;
var request = new Buffer([0xfe,0xfd,0x00,0x00,0x00,0x00,0x01,0xff,0xff,0xff]); var request = new Buffer([0xfe,0xfd,0x00,0x00,0x00,0x00,0x01,0xff,0xff,0xff]);
var packets = []; var packets = [];
this.udpSend(request, this.udpSend(request,
function(buffer) { function(buffer) {
if(packets.length && buffer.readUInt8(0) == 0) if(packets.length && buffer.readUInt8(0) == 0)
buffer = buffer.slice(1); buffer = buffer.slice(1);
packets.push(buffer); packets.push(buffer);
}, },
function() { function() {
var buffer = Buffer.concat(packets); var buffer = Buffer.concat(packets);
var reader = self.reader(buffer); var reader = self.reader(buffer);
var header = reader.uint(1); var header = reader.uint(1);
if(header != 0) return; if(header != 0) return;
var pingId = reader.uint(4); var pingId = reader.uint(4);
if(pingId != 1) return; if(pingId != 1) return;
while(!reader.done()) { while(!reader.done()) {
var key = reader.string(); var key = reader.string();
var value = reader.string(); var value = reader.string();
if(!key) break; if(!key) break;
state.raw[key] = value; state.raw[key] = value;
} }
if('hostname' in state.raw) state.name = state.raw.hostname; if('hostname' in state.raw) state.name = state.raw.hostname;
if('mapname' in state.raw) state.map = state.raw.mapname; if('mapname' in state.raw) state.map = state.raw.mapname;
if(self.trueTest(state.raw.password)) state.password = true; if(self.trueTest(state.raw.password)) state.password = true;
if('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers); if('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers);
state.players = self.readFieldData(reader); state.players = self.readFieldData(reader);
state.raw.teams = self.readFieldData(reader); state.raw.teams = self.readFieldData(reader);
self.finish(state); self.finish(state);
return true; return true;
} }
); );
}, },
readFieldData: function(reader) { readFieldData: function(reader) {
var count = reader.uint(1); var count = reader.uint(1);
// count is unreliable (often it's wrong), so we don't use it. // count is unreliable (often it's wrong), so we don't use it.
// read until we hit an empty first field string // read until we hit an empty first field string
if(this.debug) console.log("Reading fields, starting at: "+reader.rest()); if(this.debug) console.log("Reading fields, starting at: "+reader.rest());
var fields = []; var fields = [];
while(!reader.done()) { while(!reader.done()) {
var field = reader.string(); var field = reader.string();
if(!field) break; if(!field) break;
if(field.charCodeAt(0) <= 2) field = field.substring(1); if(field.charCodeAt(0) <= 2) field = field.substring(1);
fields.push(field); fields.push(field);
if(this.debug) console.log("field:"+field); if(this.debug) console.log("field:"+field);
} }
var units = []; var units = [];
outer: while(!reader.done()) { outer: while(!reader.done()) {
var unit = {}; var unit = {};
for(var iField = 0; iField < fields.length; iField++) { for(var iField = 0; iField < fields.length; iField++) {
var key = fields[iField]; var key = fields[iField];
var value = reader.string(); var value = reader.string();
if(!value && iField == 0) break outer; if(!value && iField == 0) break outer;
if(this.debug) console.log("value:"+value); if(this.debug) console.log("value:"+value);
if(key == 'player_') key = 'name'; if(key == 'player_') key = 'name';
else if(key == 'score_') key = 'score'; else if(key == 'score_') key = 'score';
else if(key == 'deaths_') key = 'deaths'; else if(key == 'deaths_') key = 'deaths';
else if(key == 'ping_') key = 'ping'; else if(key == 'ping_') key = 'ping';
else if(key == 'team_') key = 'team'; else if(key == 'team_') key = 'team';
else if(key == 'kills_') key = 'kills'; else if(key == 'kills_') key = 'kills';
else if(key == 'team_t') key = 'name'; else if(key == 'team_t') key = 'name';
else if(key == 'tickets_t') key = 'tickets'; else if(key == 'tickets_t') key = 'tickets';
if( if(
key == 'score' || key == 'deaths' key == 'score' || key == 'deaths'
|| key == 'ping' || key == 'team' || key == 'ping' || key == 'team'
|| key == 'kills' || key == 'tickets' || key == 'kills' || key == 'tickets'
) { ) {
if(value === '') continue; if(value === '') continue;
value = parseInt(value); value = parseInt(value);
} }
unit[key] = value; unit[key] = value;
} }
units.push(unit); units.push(unit);
} }
return units; return units;
} }
}); });

View File

@ -1,168 +1,168 @@
var async = require('async'); var async = require('async');
module.exports = require('./core').extend({ module.exports = require('./core').extend({
init: function() { init: function() {
this._super(); this._super();
this.sessionId = 1; this.sessionId = 1;
this.encoding = 'latin1'; this.encoding = 'latin1';
this.byteorder = 'be'; this.byteorder = 'be';
this.noChallenge = false; this.noChallenge = false;
this.useOnlySingleSplit = false; this.useOnlySingleSplit = false;
}, },
run: function(state) { run: function(state) {
var self = this; var self = this;
var challenge,packets; var challenge,packets;
async.series([ async.series([
function(c) { function(c) {
if(self.noChallenge) return c(); if(self.noChallenge) return c();
self.sendPacket(9,false,false,false,function(buffer) { self.sendPacket(9,false,false,false,function(buffer) {
var reader = self.reader(buffer); var reader = self.reader(buffer);
challenge = parseInt(reader.string()); challenge = parseInt(reader.string());
c(); c();
}); });
}, },
function(c) { function(c) {
self.sendPacket(0,challenge,new Buffer([0xff,0xff,0xff,0x01]),true,function(b) { self.sendPacket(0,challenge,new Buffer([0xff,0xff,0xff,0x01]),true,function(b) {
packets = b; packets = b;
c(); c();
}); });
}, },
function(c) { function(c) {
// iterate over the received packets // iterate over the received packets
// the first packet will start off with k/v pairs, followed with data fields // the first packet will start off with k/v pairs, followed with data fields
// the following packets will only have data fields // the following packets will only have data fields
var data = {}; var data = {};
for(var iPacket = 0; iPacket < packets.length; iPacket++) { for(var iPacket = 0; iPacket < packets.length; iPacket++) {
var packet = packets[iPacket]; var packet = packets[iPacket];
var reader = self.reader(packet); var reader = self.reader(packet);
if(self.debug) { if(self.debug) {
console.log("+++"+packet.toString('hex')); console.log("+++"+packet.toString('hex'));
console.log(":::"+packet.toString('ascii')); console.log(":::"+packet.toString('ascii'));
} }
if(iPacket == 0) { if(iPacket == 0) {
while(!reader.done()) { while(!reader.done()) {
var key = reader.string(); var key = reader.string();
if(!key) break; if(!key) break;
var value = reader.string(); var value = reader.string();
// reread the next line if we hit the weird ut3 bug // reread the next line if we hit the weird ut3 bug
if(value == 'p1073741829') value = reader.string(); if(value == 'p1073741829') value = reader.string();
state.raw[key] = value; state.raw[key] = value;
} }
} }
var firstMode = true; var firstMode = true;
while(!reader.done()) { while(!reader.done()) {
var mode = reader.string(); var mode = reader.string();
if(mode.charCodeAt(0) <= 2) mode = mode.substring(1); if(mode.charCodeAt(0) <= 2) mode = mode.substring(1);
if(!mode) continue; if(!mode) continue;
var offset = 0; var offset = 0;
if(iPacket != 0 && firstMode) offset = reader.uint(1); if(iPacket != 0 && firstMode) offset = reader.uint(1);
reader.skip(1); reader.skip(1);
firstMode = false; firstMode = false;
while(!reader.done()) { while(!reader.done()) {
var item = reader.string(); var item = reader.string();
if(!item) break; if(!item) break;
if( if(
mode == 'player_' mode == 'player_'
|| mode == 'score_' || mode == 'score_'
|| mode == 'ping_' || mode == 'ping_'
|| mode == 'team_' || mode == 'team_'
|| mode == 'deaths_' || mode == 'deaths_'
|| mode == 'pid_' || mode == 'pid_'
) { ) {
if(state.players.length <= offset) if(state.players.length <= offset)
state.players.push({}); state.players.push({});
} }
if(mode == 'player_') state.players[offset].name = item; if(mode == 'player_') state.players[offset].name = item;
if(mode == 'score_') state.players[offset].score = parseInt(item); if(mode == 'score_') state.players[offset].score = parseInt(item);
if(mode == 'ping_') state.players[offset].ping = parseInt(item); if(mode == 'ping_') state.players[offset].ping = parseInt(item);
if(mode == 'team_') state.players[offset].team = parseInt(item); if(mode == 'team_') state.players[offset].team = parseInt(item);
if(mode == 'deaths_') state.players[offset].deaths = parseInt(item); if(mode == 'deaths_') state.players[offset].deaths = parseInt(item);
if(mode == 'pid_') state.players[offset].pid = item; if(mode == 'pid_') state.players[offset].pid = item;
offset++; offset++;
} }
} }
} }
if('hostname' in state.raw) state.name = state.raw.hostname; if('hostname' in state.raw) state.name = state.raw.hostname;
else if('servername' in state.raw) state.name = state.raw.servername; else if('servername' in state.raw) state.name = state.raw.servername;
if('mapname' in state.raw) state.map = state.raw.mapname; if('mapname' in state.raw) state.map = state.raw.mapname;
if(state.raw.password == '1') state.password = true; if(state.raw.password == '1') state.password = true;
if('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers); if('maxplayers' in state.raw) state.maxplayers = parseInt(state.raw.maxplayers);
self.finish(state); self.finish(state);
} }
]); ]);
}, },
sendPacket: function(type,challenge,payload,assemble,c) { sendPacket: function(type,challenge,payload,assemble,c) {
var self = this; var self = this;
var challengeLength = (this.noChallenge || challenge === false) ? 0 : 4; var challengeLength = (this.noChallenge || challenge === false) ? 0 : 4;
var payloadLength = payload ? payload.length : 0; var payloadLength = payload ? payload.length : 0;
var b = new Buffer(7 + challengeLength + payloadLength); var b = new Buffer(7 + challengeLength + payloadLength);
b.writeUInt8(0xFE, 0); b.writeUInt8(0xFE, 0);
b.writeUInt8(0xFD, 1); b.writeUInt8(0xFD, 1);
b.writeUInt8(type, 2); b.writeUInt8(type, 2);
b.writeUInt32BE(this.sessionId, 3); b.writeUInt32BE(this.sessionId, 3);
if(challengeLength) b.writeInt32BE(challenge, 7); if(challengeLength) b.writeInt32BE(challenge, 7);
if(payloadLength) payload.copy(b, 7+challengeLength); if(payloadLength) payload.copy(b, 7+challengeLength);
var numPackets = 0; var numPackets = 0;
var packets = {}; var packets = {};
this.udpSend(b,function(buffer) { this.udpSend(b,function(buffer) {
var reader = self.reader(buffer); var reader = self.reader(buffer);
var iType = reader.uint(1); var iType = reader.uint(1);
if(iType != type) return; if(iType != type) return;
var iSessionId = reader.uint(4); var iSessionId = reader.uint(4);
if(iSessionId != self.sessionId) return; if(iSessionId != self.sessionId) return;
if(!assemble) { if(!assemble) {
c(reader.rest()); c(reader.rest());
return true; return true;
} }
if(self.useOnlySingleSplit) { if(self.useOnlySingleSplit) {
// has split headers, but they are worthless and only one packet is used // has split headers, but they are worthless and only one packet is used
reader.skip(11); reader.skip(11);
c([reader.rest()]); c([reader.rest()]);
return true; return true;
} }
reader.skip(9); // filler data -- usually set to 'splitnum\0' reader.skip(9); // filler data -- usually set to 'splitnum\0'
var id = reader.uint(1); var id = reader.uint(1);
var last = (id & 0x80); var last = (id & 0x80);
id = id & 0x7f; id = id & 0x7f;
if(last) numPackets = id+1; if(last) numPackets = id+1;
reader.skip(1); // "another 'packet number' byte, but isn't understood." reader.skip(1); // "another 'packet number' byte, but isn't understood."
packets[id] = reader.rest(); packets[id] = reader.rest();
if(self.debug) { if(self.debug) {
console.log("Received packet #"+id); console.log("Received packet #"+id);
if(last) console.log("(last)"); if(last) console.log("(last)");
} }
if(!numPackets || Object.keys(packets).length != numPackets) return; if(!numPackets || Object.keys(packets).length != numPackets) return;
// assemble the parts // assemble the parts
var list = []; var list = [];
for(var i = 0; i < numPackets; i++) { for(var i = 0; i < numPackets; i++) {
if(!(i in packets)) { if(!(i in packets)) {
self.fatal('Missing packet #'+i); self.fatal('Missing packet #'+i);
return true; return true;
} }
list.push(packets[i]); list.push(packets[i]);
} }
c(list); c(list);
return true; return true;
}); });
} }
}); });

View File

@ -1,37 +1,37 @@
module.exports = require('./core').extend({ module.exports = require('./core').extend({
init: function() { init: function() {
this._super(); this._super();
this.encoding = 'latin1'; this.encoding = 'latin1';
}, },
run: function(state) { run: function(state) {
var self = this; var self = this;
this.udpSend('M2MP',function(buffer) { this.udpSend('M2MP',function(buffer) {
var reader = self.reader(buffer); var reader = self.reader(buffer);
var header = reader.string({length:4}); var header = reader.string({length:4});
if(header != 'M2MP') return; if(header != 'M2MP') return;
state.name = self.readString(reader); state.name = self.readString(reader);
state.raw.numplayers = self.readString(reader); state.raw.numplayers = self.readString(reader);
state.maxplayers = self.readString(reader); state.maxplayers = self.readString(reader);
state.raw.gamemode = self.readString(reader); state.raw.gamemode = self.readString(reader);
state.password = !!reader.uint(1); state.password = !!reader.uint(1);
while(!reader.done()) { while(!reader.done()) {
var name = self.readString(reader); var name = self.readString(reader);
if(!name) break; if(!name) break;
state.players.push({ state.players.push({
name:name name:name
}); });
} }
self.finish(state); self.finish(state);
return true; return true;
}); });
}, },
readString: function(reader) { readString: function(reader) {
var length = reader.uint(1); var length = reader.uint(1);
return reader.string({length:length-1}); return reader.string({length:length-1});
}, },
}); });

View File

@ -1,93 +1,93 @@
var varint = require('varint'), var varint = require('varint'),
async = require('async'); async = require('async');
function varIntBuffer(num) { function varIntBuffer(num) {
return new Buffer(varint.encode(num)); return new Buffer(varint.encode(num));
} }
function buildPacket(id,data) { function buildPacket(id,data) {
if(!data) data = new Buffer(0); if(!data) data = new Buffer(0);
var idBuffer = varIntBuffer(id); var idBuffer = varIntBuffer(id);
return Buffer.concat([ return Buffer.concat([
varIntBuffer(data.length+idBuffer.length), varIntBuffer(data.length+idBuffer.length),
idBuffer, idBuffer,
data data
]); ]);
} }
module.exports = require('./core').extend({ module.exports = require('./core').extend({
run: function(state) { run: function(state) {
var self = this; var self = this;
var receivedData; var receivedData;
async.series([ async.series([
function(c) { function(c) {
// build and send handshake and status TCP packet // build and send handshake and status TCP packet
var portBuf = new Buffer(2); var portBuf = new Buffer(2);
portBuf.writeUInt16BE(self.options.port_query,0); portBuf.writeUInt16BE(self.options.port_query,0);
var addressBuf = new Buffer(self.options.address,'utf8'); var addressBuf = new Buffer(self.options.address,'utf8');
var bufs = [ var bufs = [
varIntBuffer(4), varIntBuffer(4),
varIntBuffer(addressBuf.length), varIntBuffer(addressBuf.length),
addressBuf, addressBuf,
portBuf, portBuf,
varIntBuffer(1) varIntBuffer(1)
]; ];
var outBuffer = Buffer.concat([ var outBuffer = Buffer.concat([
buildPacket(0,Buffer.concat(bufs)), buildPacket(0,Buffer.concat(bufs)),
buildPacket(0) buildPacket(0)
]); ]);
self.tcpSend(outBuffer, function(data) { self.tcpSend(outBuffer, function(data) {
if(data.length < 10) return false; if(data.length < 10) return false;
var expected = varint.decode(data); var expected = varint.decode(data);
data = data.slice(varint.decode.bytesRead); data = data.slice(varint.decode.bytesRead);
if(data.length < expected) return false; if(data.length < expected) return false;
receivedData = data; receivedData = data;
c(); c();
return true; return true;
}); });
}, },
function(c) { function(c) {
// parse response // parse response
var data = receivedData; var data = receivedData;
var packetId = varint.decode(data); var packetId = varint.decode(data);
data = data.slice(varint.decode.bytesRead); data = data.slice(varint.decode.bytesRead);
var strLen = varint.decode(data); var strLen = varint.decode(data);
data = data.slice(varint.decode.bytesRead); data = data.slice(varint.decode.bytesRead);
var str = data.toString('utf8'); var str = data.toString('utf8');
var json; var json;
try { try {
json = JSON.parse(str); json = JSON.parse(str);
delete json.favicon; delete json.favicon;
if(self.debug) console.log(json); if(self.debug) console.log(json);
} catch(e) { } catch(e) {
return self.fatal('Invalid JSON'); return self.fatal('Invalid JSON');
} }
state.raw.version = json.version.name; state.raw.version = json.version.name;
state.maxplayers = json.players.max; state.maxplayers = json.players.max;
state.raw.description = json.description.text; state.raw.description = json.description.text;
if(json.players.sample) { if(json.players.sample) {
for(var i = 0; i < json.players.sample.length; i++) { for(var i = 0; i < json.players.sample.length; i++) {
state.players.push({ state.players.push({
id: json.players.sample[i].id, id: json.players.sample[i].id,
name: json.players.sample[i].name name: json.players.sample[i].name
}); });
} }
} }
while(state.players.length < json.players.online) { while(state.players.length < json.players.online) {
state.players.push({}); state.players.push({});
} }
self.finish(state); self.finish(state);
} }
]); ]);
} }
}); });

View File

@ -1,44 +1,44 @@
var async = require('async'); var async = require('async');
module.exports = require('./core').extend({ module.exports = require('./core').extend({
init: function() { init: function() {
this._super(); this._super();
this.options.tcpTimeout = 5000; this.options.tcpTimeout = 5000;
}, },
run: function(state) { run: function(state) {
var self = this; var self = this;
this.tcpSend('json', function(buffer) { this.tcpSend('json', function(buffer) {
if(buffer.length < 10) return; if(buffer.length < 10) return;
var str = buffer.toString(); var str = buffer.toString();
var json; var json;
try { try {
json = JSON.parse(str); json = JSON.parse(str);
} catch(e) { } catch(e) {
// probably not all here yet // probably not all here yet
return; return;
} }
state.raw = json; state.raw = json;
state.name = json.name; state.name = json.name;
var channelStack = [state.raw.root]; var channelStack = [state.raw.root];
while(channelStack.length) { while(channelStack.length) {
var channel = channelStack.shift(); var channel = channelStack.shift();
channel.description = self.cleanComment(channel.description); channel.description = self.cleanComment(channel.description);
channelStack = channelStack.concat(channel.channels); channelStack = channelStack.concat(channel.channels);
for(var i = 0; i < channel.users.length; i++) { for(var i = 0; i < channel.users.length; i++) {
var user = channel.users[i]; var user = channel.users[i];
user.comment = self.cleanComment(user.comment); user.comment = self.cleanComment(user.comment);
state.players.push(user); state.players.push(user);
} }
} }
self.finish(state); self.finish(state);
return true; return true;
}); });
}, },
cleanComment: function(str) { cleanComment: function(str) {
return str.replace(/<.*>/g,''); return str.replace(/<.*>/g,'');
} }
}); });

View File

@ -1,29 +1,29 @@
var async = require('async'); var async = require('async');
module.exports = require('./core').extend({ module.exports = require('./core').extend({
init: function() { init: function() {
this._super(); this._super();
this.byteorder = 'be'; this.byteorder = 'be';
}, },
run: function(state) { run: function(state) {
var self = this; var self = this;
this.udpSend('\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08', function(buffer) { this.udpSend('\x00\x00\x00\x00\x01\x02\x03\x04\x05\x06\x07\x08', function(buffer) {
if(buffer.length < 24) return; if(buffer.length < 24) return;
var reader = self.reader(buffer); var reader = self.reader(buffer);
reader.skip(1); reader.skip(1);
state.raw.versionMajor = reader.uint(1); state.raw.versionMajor = reader.uint(1);
state.raw.versionMinor = reader.uint(1); state.raw.versionMinor = reader.uint(1);
state.raw.versionPatch = reader.uint(1); state.raw.versionPatch = reader.uint(1);
reader.skip(8); reader.skip(8);
state.raw.numplayers = reader.uint(4); state.raw.numplayers = reader.uint(4);
state.maxplayers = reader.uint(4); state.maxplayers = reader.uint(4);
state.raw.allowedbandwidth = reader.uint(4); state.raw.allowedbandwidth = reader.uint(4);
for(var i = 0; i < state.raw.numplayers; i++) { for(var i = 0; i < state.raw.numplayers; i++) {
state.players.push({}); state.players.push({});
} }
self.finish(state); self.finish(state);
return true; return true;
}); });
} }
}); });

View File

@ -1,54 +1,54 @@
var request = require('request'); var request = require('request');
module.exports = require('./protocols/core').extend({ module.exports = require('./protocols/core').extend({
run: function(state) { run: function(state) {
var self = this; var self = this;
request({ request({
uri: 'http://mutantfactions.net/game/receiveLobby.php', uri: 'http://mutantfactions.net/game/receiveLobby.php',
timeout: 3000, timeout: 3000,
}, function(e,r,body) { }, function(e,r,body) {
if(e) return self.fatal('Lobby request error'); if(e) return self.fatal('Lobby request error');
var split = body.split('<br/>'); var split = body.split('<br/>');
var found = false; var found = false;
for(var i = 0; i < split.length; i++) { for(var i = 0; i < split.length; i++) {
var line = split[i]; var line = split[i];
var fields = line.split('::'); var fields = line.split('::');
var ip = fields[2]; var ip = fields[2];
var port = fields[3]; var port = fields[3];
if(ip == self.options.address && port == self.options.port) { if(ip == self.options.address && port == self.options.port) {
found = fields; found = fields;
break; break;
} }
} }
if(!found) return self.fatal('Server not found in list'); if(!found) return self.fatal('Server not found in list');
state.raw.countrycode = fields[0]; state.raw.countrycode = fields[0];
state.raw.country = fields[1]; state.raw.country = fields[1];
state.name = fields[4]; state.name = fields[4];
state.map = fields[5]; state.map = fields[5];
state.raw.numplayers = fields[6]; state.raw.numplayers = fields[6];
state.maxplayers = fields[7]; state.maxplayers = fields[7];
// fields[8] is unknown? // fields[8] is unknown?
state.raw.rules = fields[9]; state.raw.rules = fields[9];
state.raw.gamemode = fields[10]; state.raw.gamemode = fields[10];
state.raw.gangsters = fields[11]; state.raw.gangsters = fields[11];
state.raw.cashrate = fields[12]; state.raw.cashrate = fields[12];
state.raw.missions = fields[13]; state.raw.missions = fields[13];
state.raw.vehicles = fields[14]; state.raw.vehicles = fields[14];
state.raw.customweapons = fields[15]; state.raw.customweapons = fields[15];
state.raw.friendlyfire = fields[16]; state.raw.friendlyfire = fields[16];
state.raw.mercs = fields[17]; state.raw.mercs = fields[17];
// fields[18] is unknown? listen server? // fields[18] is unknown? listen server?
state.raw.version = fields[19]; state.raw.version = fields[19];
for(var i = 0; i < state.raw.numplayers; i++) { for(var i = 0; i < state.raw.numplayers; i++) {
state.players.push({}); state.players.push({});
} }
self.finish(state); self.finish(state);
}); });
} }
}); });

View File

@ -1,75 +1,75 @@
var gbxremote = require('gbxremote'), var gbxremote = require('gbxremote'),
async = require('async'); async = require('async');
module.exports = require('./core').extend({ module.exports = require('./core').extend({
init: function() { init: function() {
this._super(); this._super();
this.options.port = 2350; this.options.port = 2350;
this.options.port_query = 5000; this.options.port_query = 5000;
this.gbxclient = false; this.gbxclient = false;
}, },
reset: function() { reset: function() {
this._super(); this._super();
if(this.gbxclient) { if(this.gbxclient) {
this.gbxclient.terminate(); this.gbxclient.terminate();
this.gbxclient = false; this.gbxclient = false;
} }
}, },
run: function(state) { run: function(state) {
var self = this; var self = this;
var cmds = [ var cmds = [
['Connect'], ['Connect'],
['Authenticate', this.options.login,this.options.password], ['Authenticate', this.options.login,this.options.password],
['GetStatus'], ['GetStatus'],
['GetPlayerList',500,0], ['GetPlayerList',500,0],
['GetServerOptions'], ['GetServerOptions'],
['GetCurrentChallengeInfo'], ['GetCurrentChallengeInfo'],
['GetCurrentGameInfo'] ['GetCurrentGameInfo']
]; ];
var results = []; var results = [];
async.eachSeries(cmds, function(cmdset,c) { async.eachSeries(cmds, function(cmdset,c) {
var cmd = cmdset[0]; var cmd = cmdset[0];
var params = cmdset.slice(1); var params = cmdset.slice(1);
if(cmd == 'Connect') { if(cmd == 'Connect') {
var client = self.gbxclient = gbxremote.createClient(self.options.port_query,self.options.host, function(err) { 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)); if(err) return self.fatal('GBX error '+JSON.stringify(err));
c(); c();
}); });
client.on('error',function(){}); client.on('error',function(){});
} else { } else {
self.gbxclient.methodCall(cmd, params, function(err, value) { self.gbxclient.methodCall(cmd, params, function(err, value) {
if(err) return self.fatal('XMLRPC error '+JSON.stringify(err)); if(err) return self.fatal('XMLRPC error '+JSON.stringify(err));
results.push(value); results.push(value);
c(); c();
}); });
} }
}, function() { }, function() {
var gamemode = ''; var gamemode = '';
var igm = results[5].GameMode; var igm = results[5].GameMode;
if(igm == 0) gamemode="Rounds"; if(igm == 0) gamemode="Rounds";
if(igm == 1) gamemode="Time Attack"; if(igm == 1) gamemode="Time Attack";
if(igm == 2) gamemode="Team"; if(igm == 2) gamemode="Team";
if(igm == 3) gamemode="Laps"; if(igm == 3) gamemode="Laps";
if(igm == 4) gamemode="Stunts"; if(igm == 4) gamemode="Stunts";
if(igm == 5) gamemode="Cup"; if(igm == 5) gamemode="Cup";
state.name = self.stripColors(results[3].Name); state.name = self.stripColors(results[3].Name);
state.password = (results[3].Password != 'No password'); state.password = (results[3].Password != 'No password');
state.maxplayers = results[3].CurrentMaxPlayers; state.maxplayers = results[3].CurrentMaxPlayers;
state.map = self.stripColors(results[4].Name); state.map = self.stripColors(results[4].Name);
state.raw.gametype = gamemode; state.raw.gametype = gamemode;
results[2].forEach(function(player) { results[2].forEach(function(player) {
state.players.push({name:self.stripColors(player.Name)}); state.players.push({name:self.stripColors(player.Name)});
}); });
self.finish(state); self.finish(state);
}); });
}, },
stripColors: function(str) { stripColors: function(str) {
return str.replace(/\$([0-9a-f][^\$]?[^\$]?|[^\$]?)/g,''); return str.replace(/\$([0-9a-f][^\$]?[^\$]?|[^\$]?)/g,'');
} }
}); });

View File

@ -1,7 +1,7 @@
module.exports = require('./quake2').extend({ module.exports = require('./quake2').extend({
init: function() { init: function() {
this._super(); this._super();
this.responseHeader = 'n'; this.responseHeader = 'n';
this.isQuake1 = true; this.isQuake1 = true;
} }
}); });

View File

@ -1,85 +1,85 @@
module.exports = require('./core').extend({ module.exports = require('./core').extend({
init: function() { init: function() {
this._super(); this._super();
this.encoding = 'latin1'; this.encoding = 'latin1';
this.delimiter = '\n'; this.delimiter = '\n';
this.sendHeader = 'status'; this.sendHeader = 'status';
this.responseHeader = 'print'; this.responseHeader = 'print';
this.isQuake1 = false; this.isQuake1 = false;
}, },
run: function(state) { run: function(state) {
var self = this; var self = this;
this.udpSend('\xff\xff\xff\xff'+this.sendHeader+'\x00',function(buffer) { this.udpSend('\xff\xff\xff\xff'+this.sendHeader+'\x00',function(buffer) {
var reader = self.reader(buffer); var reader = self.reader(buffer);
var header = reader.string({length:4}); var header = reader.string({length:4});
if(header != '\xff\xff\xff\xff') return; if(header != '\xff\xff\xff\xff') return;
var response; var response;
if(this.isQuake1) { if(this.isQuake1) {
response = reader.string({length:1}); response = reader.string({length:1});
} else { } else {
response = reader.string(); response = reader.string();
} }
if(response != this.responseHeader) return; if(response != this.responseHeader) return;
var info = reader.string().split('\\'); var info = reader.string().split('\\');
if(info[0] == '') info.shift(); if(info[0] == '') info.shift();
while(true) { while(true) {
var key = info.shift(); var key = info.shift();
var value = info.shift(); var value = info.shift();
if(typeof value == 'undefined') break; if(typeof value == 'undefined') break;
state.raw[key] = value; state.raw[key] = value;
} }
while(!reader.done()) { while(!reader.done()) {
var line = reader.string(); var line = reader.string();
if(!line || line.charAt(0) == '\0') break; if(!line || line.charAt(0) == '\0') break;
var args = []; var args = [];
var split = line.split('"'); var split = line.split('"');
var inQuote = false; var inQuote = false;
split.forEach(function(part,i) { split.forEach(function(part,i) {
var inQuote = (i%2 == 1); var inQuote = (i%2 == 1);
if(inQuote) { if(inQuote) {
args.push(part); args.push(part);
} else { } else {
var splitSpace = part.split(' '); var splitSpace = part.split(' ');
splitSpace.forEach(function(subpart) { splitSpace.forEach(function(subpart) {
if(subpart) args.push(subpart); if(subpart) args.push(subpart);
}); });
} }
}); });
var player = {}; var player = {};
if(self.isQuake1) { if(self.isQuake1) {
player.id = parseInt(args.shift()); player.id = parseInt(args.shift());
player.score = parseInt(args.shift()); player.score = parseInt(args.shift());
player.time = parseInt(args.shift()); player.time = parseInt(args.shift());
player.ping = parseInt(args.shift()); player.ping = parseInt(args.shift());
player.name = args.shift(); player.name = args.shift();
player.skin = args.shift(); player.skin = args.shift();
player.color1 = parseInt(args.shift()); player.color1 = parseInt(args.shift());
player.color2 = parseInt(args.shift()); player.color2 = parseInt(args.shift());
} else { } else {
player.frags = parseInt(args.shift()); player.frags = parseInt(args.shift());
player.ping = parseInt(args.shift()); player.ping = parseInt(args.shift());
player.name = args.shift() || ''; player.name = args.shift() || '';
player.address = args.shift() || ''; player.address = args.shift() || '';
} }
(player.ping ? state.players : state.bots).push(player); (player.ping ? state.players : state.bots).push(player);
} }
if('g_needpass' in state.raw) state.password = state.raw.g_needpass; if('g_needpass' in state.raw) state.password = state.raw.g_needpass;
if('mapname' in state.raw) state.map = state.raw.mapname; if('mapname' in state.raw) state.map = state.raw.mapname;
if('sv_maxclients' in state.raw) state.maxplayers = state.raw.sv_maxclients; if('sv_maxclients' in state.raw) state.maxplayers = state.raw.sv_maxclients;
if('sv_hostname' in state.raw) state.name = state.raw.sv_hostname; if('sv_hostname' in state.raw) state.name = state.raw.sv_hostname;
self.finish(state); self.finish(state);
return true; return true;
}); });
} }
}); });

View File

@ -1,19 +1,19 @@
module.exports = require('./quake2').extend({ module.exports = require('./quake2').extend({
init: function() { init: function() {
this._super(); this._super();
this.sendHeader = 'getstatus'; this.sendHeader = 'getstatus';
this.responseHeader = 'statusResponse'; this.responseHeader = 'statusResponse';
}, },
finalizeState: function(state) { finalizeState: function(state) {
state.name = this.stripColors(state.name); state.name = this.stripColors(state.name);
for(var i in state.raw) { for(var i in state.raw) {
state.raw[i] = this.stripColors(state.raw[i]); state.raw[i] = this.stripColors(state.raw[i]);
} }
for(var i = 0; i < state.players.length; i++) { for(var i = 0; i < state.players.length; i++) {
state.players[i].name = this.stripColors(state.players[i].name); state.players[i].name = this.stripColors(state.players[i].name);
} }
}, },
stripColors: function(str) { stripColors: function(str) {
return str.replace(/\^(X.{6}|.)/g,''); return str.replace(/\^(X.{6}|.)/g,'');
} }
}); });

View File

@ -1,77 +1,77 @@
var async = require('async'); var async = require('async');
module.exports = require('./core').extend({ module.exports = require('./core').extend({
run: function(state) { run: function(state) {
var self = this; var self = this;
async.series([ async.series([
function(c) { function(c) {
self.sendCommand('sel '+self.options.port, function(data) { self.sendCommand('sel '+self.options.port, function(data) {
if(data != '[TS]') self.fatal('Invalid header'); if(data != '[TS]') self.fatal('Invalid header');
c(); c();
}); });
}, },
function(c) { function(c) {
self.sendCommand('si', function(data) { self.sendCommand('si', function(data) {
var split = data.split('\r\n'); var split = data.split('\r\n');
split.forEach(function(line) { split.forEach(function(line) {
var equals = line.indexOf('='); var equals = line.indexOf('=');
var key = equals == -1 ? line : line.substr(0,equals); var key = equals == -1 ? line : line.substr(0,equals);
var value = equals == -1 ? '' : line.substr(equals+1); var value = equals == -1 ? '' : line.substr(equals+1);
state.raw[key] = value; state.raw[key] = value;
}); });
c(); c();
}); });
}, },
function(c) { function(c) {
self.sendCommand('pl', function(data) { self.sendCommand('pl', function(data) {
var split = data.split('\r\n'); var split = data.split('\r\n');
var fields = split.shift().split('\t'); var fields = split.shift().split('\t');
split.forEach(function(line) { split.forEach(function(line) {
var split2 = line.split('\t'); var split2 = line.split('\t');
var player = {}; var player = {};
split2.forEach(function(value,i) { split2.forEach(function(value,i) {
var key = fields[i]; var key = fields[i];
if(!key) return; if(!key) return;
if(key == 'nick') key = 'name'; if(key == 'nick') key = 'name';
if(m = value.match(/^"(.*)"$/)) value = m[1]; if(m = value.match(/^"(.*)"$/)) value = m[1];
player[key] = value; player[key] = value;
}); });
state.players.push(player); state.players.push(player);
}); });
c(); c();
}); });
}, },
function(c) { function(c) {
self.sendCommand('cl', function(data) { self.sendCommand('cl', function(data) {
var split = data.split('\r\n'); var split = data.split('\r\n');
var fields = split.shift().split('\t'); var fields = split.shift().split('\t');
state.raw.channels = []; state.raw.channels = [];
split.forEach(function(line) { split.forEach(function(line) {
var split2 = line.split('\t'); var split2 = line.split('\t');
var channel = {}; var channel = {};
split2.forEach(function(value,i) { split2.forEach(function(value,i) {
var key = fields[i]; var key = fields[i];
if(!key) return; if(!key) return;
if(m = value.match(/^"(.*)"$/)) value = m[1]; if(m = value.match(/^"(.*)"$/)) value = m[1];
channel[key] = value; channel[key] = value;
}); });
state.raw.channels.push(channel); state.raw.channels.push(channel);
}); });
c(); c();
}); });
}, },
function(c) { function(c) {
self.finish(state); self.finish(state);
} }
]); ]);
}, },
sendCommand: function(cmd,c) { sendCommand: function(cmd,c) {
this.tcpSend(cmd+'\x0A', function(buffer) { this.tcpSend(cmd+'\x0A', function(buffer) {
if(buffer.length < 6) return; if(buffer.length < 6) return;
if(buffer.slice(-6).toString() != '\r\nOK\r\n') return; if(buffer.slice(-6).toString() != '\r\nOK\r\n') return;
c(buffer.slice(0,-6).toString()); c(buffer.slice(0,-6).toString());
return true; return true;
}); });
} }
}); });

View File

@ -1,74 +1,74 @@
var async = require('async'); var async = require('async');
module.exports = require('./core').extend({ module.exports = require('./core').extend({
run: function(state) { run: function(state) {
var self = this; var self = this;
async.series([ async.series([
function(c) { function(c) {
self.sendCommand('use port='+self.options.port, function(data) { self.sendCommand('use port='+self.options.port, function(data) {
var split = data.split('\n\r'); var split = data.split('\n\r');
if(split[0] != 'TS3') self.fatal('Invalid header'); if(split[0] != 'TS3') self.fatal('Invalid header');
c(); c();
}, true); }, true);
}, },
function(c) { function(c) {
self.sendCommand('serverinfo', function(data) { self.sendCommand('serverinfo', function(data) {
state.raw = data[0]; state.raw = data[0];
c(); c();
}); });
}, },
function(c) { function(c) {
self.sendCommand('clientlist', function(data) { self.sendCommand('clientlist', function(data) {
for(var i = 0; i < data.length; i++) { for(var i = 0; i < data.length; i++) {
data[i].name = data[i].client_nickname; data[i].name = data[i].client_nickname;
delete data[i].client_nickname; delete data[i].client_nickname;
state.players.push(data[i]); state.players.push(data[i]);
} }
c(); c();
}); });
}, },
function(c) { function(c) {
self.sendCommand('channellist -topic', function(data) { self.sendCommand('channellist -topic', function(data) {
state.raw.channels = data; state.raw.channels = data;
c(); c();
}); });
}, },
function(c) { function(c) {
self.finish(state); self.finish(state);
} }
]); ]);
}, },
sendCommand: function(cmd,c,raw) { sendCommand: function(cmd,c,raw) {
this.tcpSend(cmd+'\x0A', function(buffer) { this.tcpSend(cmd+'\x0A', function(buffer) {
if(buffer.length < 21) return; if(buffer.length < 21) return;
if(buffer.slice(-21).toString() != '\n\rerror id=0 msg=ok\n\r') return; if(buffer.slice(-21).toString() != '\n\rerror id=0 msg=ok\n\r') return;
var body = buffer.slice(0,-21).toString(); var body = buffer.slice(0,-21).toString();
var out; var out;
if(raw) { if(raw) {
out = body; out = body;
} else { } else {
var segments = body.split('|'); var segments = body.split('|');
out = []; out = [];
segments.forEach(function(line) { segments.forEach(function(line) {
var split = line.split(' '); var split = line.split(' ');
var unit = {}; var unit = {};
split.forEach(function(field) { split.forEach(function(field) {
var equals = field.indexOf('='); var equals = field.indexOf('=');
var key = equals == -1 ? field : field.substr(0,equals); var key = equals == -1 ? field : field.substr(0,equals);
var value = equals == -1 ? '' : field.substr(equals+1) var value = equals == -1 ? '' : field.substr(equals+1)
.replace(/\\s/g,' ').replace(/\\\//g,'/'); .replace(/\\s/g,' ').replace(/\\\//g,'/');
unit[key] = value; unit[key] = value;
}); });
out.push(unit); out.push(unit);
}); });
} }
c(out); c(out);
return true; return true;
}); });
} }
}); });

View File

@ -1,35 +1,35 @@
var request = require('request'); var request = require('request');
module.exports = require('./core').extend({ module.exports = require('./core').extend({
run: function(state) { run: function(state) {
var self = this; var self = this;
request({ request({
uri: 'http://'+this.options.address+':'+this.options.port_query+'/v2/server/status', uri: 'http://'+this.options.address+':'+this.options.port_query+'/v2/server/status',
timeout: 3000, timeout: 3000,
qs: { qs: {
players: 'true', players: 'true',
token: this.options.token token: this.options.token
} }
}, function(e,r,body) { }, function(e,r,body) {
if(e) return self.fatal('HTTP error'); if(e) return self.fatal('HTTP error');
var json; var json;
try { try {
json = JSON.parse(body); json = JSON.parse(body);
} catch(e) { } catch(e) {
return self.fatal('Invalid JSON'); return self.fatal('Invalid JSON');
} }
if(json.status != 200) return self.fatal('Invalid status'); if(json.status != 200) return self.fatal('Invalid status');
json.players.forEach(function(one) { json.players.forEach(function(one) {
state.players.push({name:one.nickname,team:one.team}); state.players.push({name:one.nickname,team:one.team});
}); });
state.name = json.name; state.name = json.name;
state.raw.port = json.port; state.raw.port = json.port;
state.raw.numplayers = json.playercount; state.raw.numplayers = json.playercount;
self.finish(state); self.finish(state);
}); });
} }
}); });

View File

@ -1,142 +1,142 @@
var async = require('async'); var async = require('async');
module.exports = require('./core').extend({ module.exports = require('./core').extend({
init: function() { init: function() {
this._super(); this._super();
this.encoding = 'latin1'; this.encoding = 'latin1';
}, },
run: function(state) { run: function(state) {
var self = this; var self = this;
async.series([ async.series([
function(c) { function(c) {
self.sendPacket(0,true,function(b) { self.sendPacket(0,true,function(b) {
var reader = self.reader(b); var reader = self.reader(b);
state.raw.serverid = reader.uint(4); state.raw.serverid = reader.uint(4);
state.raw.ip = self.readUnrealString(reader); state.raw.ip = self.readUnrealString(reader);
state.raw.port = reader.uint(4); state.raw.port = reader.uint(4);
state.raw.queryport = reader.uint(4); state.raw.queryport = reader.uint(4);
state.name = self.readUnrealString(reader,true); state.name = self.readUnrealString(reader,true);
state.map = self.readUnrealString(reader,true); state.map = self.readUnrealString(reader,true);
state.raw.gametype = self.readUnrealString(reader,true); state.raw.gametype = self.readUnrealString(reader,true);
state.raw.numplayers = reader.uint(4); state.raw.numplayers = reader.uint(4);
state.maxplayers = reader.uint(4); state.maxplayers = reader.uint(4);
self.readExtraInfo(reader,state); self.readExtraInfo(reader,state);
c(); c();
}); });
}, },
function(c) { function(c) {
self.sendPacket(1,true,function(b) { self.sendPacket(1,true,function(b) {
var reader = self.reader(b); var reader = self.reader(b);
state.raw.mutators = []; state.raw.mutators = [];
state.raw.rules = {}; state.raw.rules = {};
while(!reader.done()) { while(!reader.done()) {
var key = self.readUnrealString(reader,true); var key = self.readUnrealString(reader,true);
var value = self.readUnrealString(reader,true); var value = self.readUnrealString(reader,true);
if(key == 'Mutator') state.raw.mutators.push(value); if(key == 'Mutator') state.raw.mutators.push(value);
else state.raw.rules[key] = value; else state.raw.rules[key] = value;
} }
if('GamePassword' in state.raw.rules) if('GamePassword' in state.raw.rules)
state.password = state.raw.rules.GamePassword != 'True'; state.password = state.raw.rules.GamePassword != 'True';
c(); c();
}); });
}, },
function(c) { function(c) {
self.sendPacket(2,false,function(b) { self.sendPacket(2,false,function(b) {
var reader = self.reader(b); var reader = self.reader(b);
while(!reader.done()) { while(!reader.done()) {
var player = {}; var player = {};
player.id = reader.uint(4); player.id = reader.uint(4);
if(!player.id) break; if(!player.id) break;
if(player.id == 0) { if(player.id == 0) {
// Unreal2XMP Player (ID is always 0) // Unreal2XMP Player (ID is always 0)
reader.skip(4); reader.skip(4);
} }
player.name = self.readUnrealString(reader,true); player.name = self.readUnrealString(reader,true);
player.ping = reader.uint(4); player.ping = reader.uint(4);
player.score = reader.int(4); player.score = reader.int(4);
reader.skip(4); // stats ID reader.skip(4); // stats ID
// Extra data for Unreal2XMP players // Extra data for Unreal2XMP players
if(player.id == 0) { if(player.id == 0) {
var count = reader.uint(1); var count = reader.uint(1);
for(var iField = 0; iField < count; iField++) { for(var iField = 0; iField < count; iField++) {
var key = self.readUnrealString(reader,true); var key = self.readUnrealString(reader,true);
var value = self.readUnrealString(reader,true); var value = self.readUnrealString(reader,true);
player[key] = value; player[key] = value;
} }
} }
if(player.id == 0 && player.name == 'Player') { if(player.id == 0 && player.name == 'Player') {
// these show up in ut2004 queries, but aren't real // these show up in ut2004 queries, but aren't real
// not even really sure why they're there // not even really sure why they're there
continue; continue;
} }
(player.ping ? state.players : state.bots).push(player); (player.ping ? state.players : state.bots).push(player);
} }
c(); c();
}); });
}, },
function(c) { function(c) {
self.finish(state); self.finish(state);
} }
]); ]);
}, },
readExtraInfo: function(reader,state) { readExtraInfo: function(reader,state) {
if(this.debug) { if(this.debug) {
console.log("UNREAL2 EXTRA INFO:"); 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.uint(4)); console.log(reader.uint(4));
console.log(reader.uint(4)); console.log(reader.uint(4));
console.log(reader.buffer.slice(reader.i)); console.log(reader.buffer.slice(reader.i));
} }
}, },
readUnrealString: function(reader, stripColor) { readUnrealString: function(reader, stripColor) {
var length = reader.uint(1); var length = reader.uint(1);
var out; var out;
if(length < 0x80) { if(length < 0x80) {
//out = reader.string({length:length}); //out = reader.string({length:length});
out = ''; out = '';
if(length > 0) out = reader.string(); if(length > 0) out = reader.string();
} else { } else {
length = (length&0x7f)*2; length = (length&0x7f)*2;
if(this.debug) { if(this.debug) {
console.log("UCS2 STRING"); console.log("UCS2 STRING");
console.log(length,reader.buffer.slice(reader.i,reader.i+length)); console.log(length,reader.buffer.slice(reader.i,reader.i+length));
} }
out = reader.string({encoding:'ucs2',length:length}); out = reader.string({encoding:'ucs2',length:length});
} }
if(out.charCodeAt(out.length-1) == 0) if(out.charCodeAt(out.length-1) == 0)
out = out.substring(0,out.length-1); out = out.substring(0,out.length-1);
if(stripColor) if(stripColor)
out = out.replace(/\x1b...|[\x00-\x1a]/g,''); out = out.replace(/\x1b...|[\x00-\x1a]/g,'');
return out; return out;
}, },
sendPacket: function(type,required,callback) { sendPacket: function(type,required,callback) {
var self = this; var self = this;
var outbuffer = new Buffer([0x79,0,0,0,type]); var outbuffer = new Buffer([0x79,0,0,0,type]);
var packets = []; var packets = [];
this.udpSend(outbuffer,function(buffer) { this.udpSend(outbuffer,function(buffer) {
var reader = self.reader(buffer); var reader = self.reader(buffer);
var header = reader.uint(4); var header = reader.uint(4);
var iType = reader.uint(1); var iType = reader.uint(1);
if(iType != type) return; if(iType != type) return;
packets.push(reader.rest()); packets.push(reader.rest());
},function() { },function() {
if(!packets.length && required) return; if(!packets.length && required) return;
callback(Buffer.concat(packets)); callback(Buffer.concat(packets));
return true; return true;
}); });
} }
}); });

View File

@ -1,43 +1,43 @@
module.exports = require('./gamespy3').extend({ module.exports = require('./gamespy3').extend({
finalizeState: function(state) { finalizeState: function(state) {
this._super(state); this._super(state);
this.translate(state.raw,{ this.translate(state.raw,{
'mapname': false, 'mapname': false,
'p1073741825': 'map', 'p1073741825': 'map',
'p1073741826': 'gametype', 'p1073741826': 'gametype',
'p1073741827': 'servername', 'p1073741827': 'servername',
'p1073741828': 'custom_mutators', 'p1073741828': 'custom_mutators',
'gamemode': 'joininprogress', 'gamemode': 'joininprogress',
's32779': 'gamemode', 's32779': 'gamemode',
's0': 'bot_skill', 's0': 'bot_skill',
's6': 'pure_server', 's6': 'pure_server',
's7': 'password', 's7': 'password',
's8': 'vs_bots', 's8': 'vs_bots',
's10': 'force_respawn', 's10': 'force_respawn',
'p268435704': 'frag_limit', 'p268435704': 'frag_limit',
'p268435705': 'time_limit', 'p268435705': 'time_limit',
'p268435703': 'numbots', 'p268435703': 'numbots',
'p268435717': 'stock_mutators', 'p268435717': 'stock_mutators',
'p1073741829': 'stock_mutators', 'p1073741829': 'stock_mutators',
's1': false, 's1': false,
's9': false, 's9': false,
's11': false, 's11': false,
's12': false, 's12': false,
's13': false, 's13': false,
's14': false, 's14': false,
'p268435706': false, 'p268435706': false,
'p268435968': false, 'p268435968': false,
'p268435969': false 'p268435969': false
}); });
function split(a) { function split(a) {
var s = a.split('\x1c'); var s = a.split('\x1c');
s = s.filter(function(e) { return e }); s = s.filter(function(e) { return e });
return s; return s;
} }
if('custom_mutators' in state.raw) state.raw['custom_mutators'] = split(state.raw['custom_mutators']); 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('stock_mutators' in state.raw) state.raw['stock_mutators'] = split(state.raw['stock_mutators']);
if('map' in state.raw) state.map = state.raw.map; if('map' in state.raw) state.map = state.raw.map;
} }
}); });

View File

@ -1,325 +1,325 @@
var async = require('async'), var async = require('async'),
Bzip2 = require('compressjs').Bzip2; Bzip2 = require('compressjs').Bzip2;
module.exports = require('./core').extend({ module.exports = require('./core').extend({
init: function() { init: function() {
this._super(); this._super();
this.options.port = 27015; this.options.port = 27015;
// legacy goldsrc info response -- basically not used by ANYTHING now, // legacy goldsrc info response -- basically not used by ANYTHING now,
// as most (all?) goldsrc servers respond with the source info reponse // as most (all?) goldsrc servers respond with the source info reponse
// delete in a few years if nothing ends up using it anymore // delete in a few years if nothing ends up using it anymore
this.goldsrcInfo = false; this.goldsrcInfo = false;
// unfortunately, the split format from goldsrc is still around, but we // unfortunately, the split format from goldsrc is still around, but we
// can detect that during the query // can detect that during the query
this.goldsrcSplits = false; this.goldsrcSplits = false;
// some mods require a challenge, but don't provide them in the new format // some mods require a challenge, but don't provide them in the new format
// at all, use the old dedicated challenge query if needed // at all, use the old dedicated challenge query if needed
this.legacyChallenge = false; this.legacyChallenge = false;
// cs:go provides an annoying additional bot that looks exactly like a player, // cs:go provides an annoying additional bot that looks exactly like a player,
// but is always named "Max Players" // but is always named "Max Players"
this.isCsGo = false; this.isCsGo = false;
// 2006 engines don't pass packet switching size in split packet header // 2006 engines don't pass packet switching size in split packet header
// while all others do, this need is detected automatically // while all others do, this need is detected automatically
this._skipSizeInSplitHeader = false; this._skipSizeInSplitHeader = false;
this._challenge = ''; this._challenge = '';
}, },
run: function(state) { run: function(state) {
var self = this; var self = this;
async.series([ async.series([
function(c) { self.queryInfo(state,c); }, function(c) { self.queryInfo(state,c); },
function(c) { self.queryChallenge(state,c); }, function(c) { self.queryChallenge(state,c); },
function(c) { self.queryPlayers(state,c); }, function(c) { self.queryPlayers(state,c); },
function(c) { self.queryRules(state,c); }, function(c) { self.queryRules(state,c); },
function(c) { self.finish(state); } function(c) { self.finish(state); }
]); ]);
}, },
queryInfo: function(state,c) { queryInfo: function(state,c) {
var self = this; var self = this;
self.sendPacket( self.sendPacket(
0x54,false,'Source Engine Query\0', 0x54,false,'Source Engine Query\0',
self.goldsrcInfo ? 0x6D : 0x49, self.goldsrcInfo ? 0x6D : 0x49,
function(b) { function(b) {
var reader = self.reader(b); var reader = self.reader(b);
if(self.goldsrcInfo) state.raw.address = reader.string(); if(self.goldsrcInfo) state.raw.address = reader.string();
else state.raw.protocol = reader.uint(1); else state.raw.protocol = reader.uint(1);
state.name = reader.string(); state.name = reader.string();
state.map = reader.string(); state.map = reader.string();
state.raw.folder = reader.string(); state.raw.folder = reader.string();
state.raw.game = reader.string(); state.raw.game = reader.string();
state.raw.steamappid = reader.uint(2); state.raw.steamappid = reader.uint(2);
state.raw.numplayers = reader.uint(1); state.raw.numplayers = reader.uint(1);
state.maxplayers = reader.uint(1); state.maxplayers = reader.uint(1);
if(self.goldsrcInfo) state.raw.protocol = reader.uint(1); if(self.goldsrcInfo) state.raw.protocol = reader.uint(1);
else state.raw.numbots = reader.uint(1); else state.raw.numbots = reader.uint(1);
state.raw.listentype = reader.uint(1); state.raw.listentype = reader.uint(1);
state.raw.environment = reader.uint(1); state.raw.environment = reader.uint(1);
if(!self.goldsrcInfo) { if(!self.goldsrcInfo) {
state.raw.listentype = String.fromCharCode(state.raw.listentype); state.raw.listentype = String.fromCharCode(state.raw.listentype);
state.raw.environment = String.fromCharCode(state.raw.environment); state.raw.environment = String.fromCharCode(state.raw.environment);
} }
state.password = !!reader.uint(1); state.password = !!reader.uint(1);
if(self.goldsrcInfo) { if(self.goldsrcInfo) {
state.raw.ismod = reader.uint(1); state.raw.ismod = reader.uint(1);
if(state.raw.ismod) { if(state.raw.ismod) {
state.raw.modlink = reader.string(); state.raw.modlink = reader.string();
state.raw.moddownload = reader.string(); state.raw.moddownload = reader.string();
reader.skip(1); reader.skip(1);
state.raw.modversion = reader.uint(4); state.raw.modversion = reader.uint(4);
state.raw.modsize = reader.uint(4); state.raw.modsize = reader.uint(4);
state.raw.modtype = reader.uint(1); state.raw.modtype = reader.uint(1);
state.raw.moddll = reader.uint(1); state.raw.moddll = reader.uint(1);
} }
} }
state.raw.secure = reader.uint(1); state.raw.secure = reader.uint(1);
if(self.goldsrcInfo) { if(self.goldsrcInfo) {
state.raw.numbots = reader.uint(1); state.raw.numbots = reader.uint(1);
} else { } else {
if(state.raw.folder == 'ship') { if(state.raw.folder == 'ship') {
state.raw.shipmode = reader.uint(1); state.raw.shipmode = reader.uint(1);
state.raw.shipwitnesses = reader.uint(1); state.raw.shipwitnesses = reader.uint(1);
state.raw.shipduration = reader.uint(1); state.raw.shipduration = reader.uint(1);
} }
state.raw.version = reader.string(); state.raw.version = reader.string();
var extraFlag = reader.uint(1); var extraFlag = reader.uint(1);
if(extraFlag & 0x80) state.raw.port = reader.uint(2); if(extraFlag & 0x80) state.raw.port = reader.uint(2);
if(extraFlag & 0x10) state.raw.steamid = reader.uint(8); if(extraFlag & 0x10) state.raw.steamid = reader.uint(8);
if(extraFlag & 0x40) { if(extraFlag & 0x40) {
state.raw.sourcetvport = reader.uint(2); state.raw.sourcetvport = reader.uint(2);
state.raw.sourcetvname = reader.string(); state.raw.sourcetvname = reader.string();
} }
if(extraFlag & 0x20) state.raw.tags = reader.string(); if(extraFlag & 0x20) state.raw.tags = reader.string();
if(extraFlag & 0x01) state.raw.gameid = reader.uint(8); if(extraFlag & 0x01) state.raw.gameid = reader.uint(8);
} }
// from https://developer.valvesoftware.com/wiki/Server_queries // from https://developer.valvesoftware.com/wiki/Server_queries
if( if(
state.raw.protocol == 7 && ( state.raw.protocol == 7 && (
state.raw.steamappid == 215 state.raw.steamappid == 215
|| state.raw.steamappid == 17550 || state.raw.steamappid == 17550
|| state.raw.steamappid == 17700 || state.raw.steamappid == 17700
|| state.raw.steamappid == 240 || state.raw.steamappid == 240
) )
) { ) {
self._skipSizeInSplitHeader = true; self._skipSizeInSplitHeader = true;
} }
if(self.debug) { if(self.debug) {
console.log("STEAM APPID: "+state.raw.steamappid); console.log("STEAM APPID: "+state.raw.steamappid);
console.log("PROTOCOL: "+state.raw.protocol); console.log("PROTOCOL: "+state.raw.protocol);
} }
if(state.raw.protocol == 48) { if(state.raw.protocol == 48) {
if(self.debug) console.log("GOLDSRC DETECTED - USING MODIFIED SPLIT FORMAT"); if(self.debug) console.log("GOLDSRC DETECTED - USING MODIFIED SPLIT FORMAT");
self.goldsrcSplits = true; self.goldsrcSplits = true;
} }
c(); c();
} }
); );
}, },
queryChallenge: function(state,c) { queryChallenge: function(state,c) {
var self = this; var self = this;
if(this.legacyChallenge) { if(this.legacyChallenge) {
self.sendPacket(0x57,false,false,0x41,function(b) { self.sendPacket(0x57,false,false,0x41,function(b) {
// sendPacket will catch the response packet and // sendPacket will catch the response packet and
// save the challenge for us // save the challenge for us
c(); c();
}); });
} else { } else {
c(); c();
} }
}, },
queryPlayers: function(state,c) { queryPlayers: function(state,c) {
var self = this; var self = this;
self.sendPacket(0x55,true,false,0x44,function(b) { self.sendPacket(0x55,true,false,0x44,function(b) {
var reader = self.reader(b); var reader = self.reader(b);
var num = reader.uint(1); var num = reader.uint(1);
var csgoHiddenPlayers = false; var csgoHiddenPlayers = false;
for(var i = 0; i < num; i++) { for(var i = 0; i < num; i++) {
reader.skip(1); reader.skip(1);
var name = reader.string(); var name = reader.string();
var score = reader.int(4); var score = reader.int(4);
var time = reader.float(); var time = reader.float();
if(self.debug) console.log("Found player: "+name+" "+score+" "+time); if(self.debug) console.log("Found player: "+name+" "+score+" "+time);
// connecting players don't count as players. // connecting players don't count as players.
if(!name) continue; if(!name) continue;
(time == -1 ? state.bots : state.players).push({ (time == -1 ? state.bots : state.players).push({
name:name, score:score, time:time name:name, score:score, time:time
}); });
} }
if(self.isCsGo && state.players.length == 1 && state.players[0].name == 'Max Players') { if(self.isCsGo && state.players.length == 1 && state.players[0].name == 'Max Players') {
if(self.debug) console.log("CSGO server using limited player details"); if(self.debug) console.log("CSGO server using limited player details");
state.players = []; state.players = [];
for(var i = 0; i < state.raw.numplayers; i++) { state.players.push({}); } for(var i = 0; i < state.raw.numplayers; i++) { state.players.push({}); }
} }
// if we didn't find the bots, iterate // if we didn't find the bots, iterate
// through and guess which ones they are // through and guess which ones they are
if(!state.bots.length && state.raw.numbots) { if(!state.bots.length && state.raw.numbots) {
var maxTime = 0; var maxTime = 0;
state.players.forEach(function(player) { state.players.forEach(function(player) {
maxTime = Math.max(player.time,maxTime); maxTime = Math.max(player.time,maxTime);
}); });
for(var i = 0; i < state.players.length; i++) { for(var i = 0; i < state.players.length; i++) {
var player = state.players[i]; var player = state.players[i];
if(state.bots.length >= state.raw.numbots) continue; if(state.bots.length >= state.raw.numbots) continue;
if(player.time != maxTime) continue; if(player.time != maxTime) continue;
state.bots.push(player); state.bots.push(player);
state.players.splice(i, 1); state.players.splice(i, 1);
i--; i--;
} }
} }
c(); c();
}); });
}, },
queryRules: function(state,c) { queryRules: function(state,c) {
var self = this; var self = this;
self.sendPacket(0x56,true,false,0x45,function(b) { self.sendPacket(0x56,true,false,0x45,function(b) {
var reader = self.reader(b); var reader = self.reader(b);
var num = reader.uint(2); var num = reader.uint(2);
state.raw.rules = {}; state.raw.rules = {};
for(var i = 0; i < num; i++) { for(var i = 0; i < num; i++) {
var key = reader.string(); var key = reader.string();
var value = reader.string(); var value = reader.string();
state.raw.rules[key] = value; state.raw.rules[key] = value;
} }
c(); c();
}, function() { }, function() {
// no rules were returned after timeout -- // no rules were returned after timeout --
// the server probably has them disabled // the server probably has them disabled
// ignore the timeout // ignore the timeout
c(); c();
return true; return true;
}); });
}, },
sendPacket: function(type,sendChallenge,payload,expect,callback,ontimeout) { sendPacket: function(type,sendChallenge,payload,expect,callback,ontimeout) {
var self = this; var self = this;
var packetStorage = {}; var packetStorage = {};
send(); send();
function send(c) { function send(c) {
if(typeof payload == 'string') payload = new Buffer(payload,'binary'); if(typeof payload == 'string') payload = new Buffer(payload,'binary');
var challengeLength = sendChallenge ? 4 : 0; var challengeLength = sendChallenge ? 4 : 0;
var payloadLength = payload ? payload.length : 0; var payloadLength = payload ? payload.length : 0;
var b = new Buffer(5 + challengeLength + payloadLength); var b = new Buffer(5 + challengeLength + payloadLength);
b.writeInt32LE(-1, 0); b.writeInt32LE(-1, 0);
b.writeUInt8(type, 4); b.writeUInt8(type, 4);
if(sendChallenge) { if(sendChallenge) {
var challenge = self._challenge; var challenge = self._challenge;
if(!challenge) challenge = 0xffffffff; if(!challenge) challenge = 0xffffffff;
if(self.byteorder == 'le') b.writeUInt32LE(challenge, 5); if(self.byteorder == 'le') b.writeUInt32LE(challenge, 5);
else b.writeUInt32BE(challenge, 5); else b.writeUInt32BE(challenge, 5);
} }
if(payloadLength) payload.copy(b, 5+challengeLength); if(payloadLength) payload.copy(b, 5+challengeLength);
self.udpSend(b,receivedOne,ontimeout); self.udpSend(b,receivedOne,ontimeout);
} }
function receivedOne(buffer) { function receivedOne(buffer) {
var reader = self.reader(buffer); var reader = self.reader(buffer);
var header = reader.int(4); var header = reader.int(4);
if(header == -1) { if(header == -1) {
// full package // full package
if(self.debug) console.log("Received full packet"); if(self.debug) console.log("Received full packet");
return receivedFull(reader); return receivedFull(reader);
} }
if(header == -2) { if(header == -2) {
// partial package // partial package
var uid = reader.uint(4); var uid = reader.uint(4);
if(!(uid in packetStorage)) packetStorage[uid] = {}; if(!(uid in packetStorage)) packetStorage[uid] = {};
var packets = packetStorage[uid]; var packets = packetStorage[uid];
var bzip = false; var bzip = false;
if(!self.goldsrcSplits && uid & 0x80000000) bzip = true; if(!self.goldsrcSplits && uid & 0x80000000) bzip = true;
var packetNum,payload,numPackets; var packetNum,payload,numPackets;
if(self.goldsrcSplits) { if(self.goldsrcSplits) {
packetNum = reader.uint(1); packetNum = reader.uint(1);
numPackets = packetNum & 0x0f; numPackets = packetNum & 0x0f;
packetNum = (packetNum & 0xf0) >> 4; packetNum = (packetNum & 0xf0) >> 4;
payload = reader.rest(); payload = reader.rest();
} else { } else {
numPackets = reader.uint(1); numPackets = reader.uint(1);
packetNum = reader.uint(1); packetNum = reader.uint(1);
if(!self._skipSizeInSplitHeader) reader.skip(2); if(!self._skipSizeInSplitHeader) reader.skip(2);
if(packetNum == 0 && bzip) reader.skip(8); if(packetNum == 0 && bzip) reader.skip(8);
payload = reader.rest(); payload = reader.rest();
} }
packets[packetNum] = payload; packets[packetNum] = payload;
if(self.debug) { if(self.debug) {
console.log("Received partial packet uid:"+uid+" num:"+packetNum); console.log("Received partial packet uid:"+uid+" num:"+packetNum);
console.log("Received "+Object.keys(packets).length+'/'+numPackets+" packets for this UID"); console.log("Received "+Object.keys(packets).length+'/'+numPackets+" packets for this UID");
} }
if(Object.keys(packets).length != numPackets) return; if(Object.keys(packets).length != numPackets) return;
// assemble the parts // assemble the parts
var list = []; var list = [];
for(var i = 0; i < numPackets; i++) { for(var i = 0; i < numPackets; i++) {
if(!(i in packets)) { if(!(i in packets)) {
self.fatal('Missing packet #'+i); self.fatal('Missing packet #'+i);
return true; return true;
} }
list.push(packets[i]); list.push(packets[i]);
} }
var assembled = Buffer.concat(list); var assembled = Buffer.concat(list);
if(bzip) { if(bzip) {
if(self.debug) console.log("BZIP DETECTED - Extracing packet..."); if(self.debug) console.log("BZIP DETECTED - Extracing packet...");
try { try {
assembled = new Buffer(Bzip2.decompressFile(assembled)); assembled = new Buffer(Bzip2.decompressFile(assembled));
} catch(e) { } catch(e) {
self.fatal('Invalid bzip packet'); self.fatal('Invalid bzip packet');
return true; return true;
} }
} }
var assembledReader = self.reader(assembled); var assembledReader = self.reader(assembled);
assembledReader.skip(4); // header assembledReader.skip(4); // header
return receivedFull(assembledReader); return receivedFull(assembledReader);
} }
} }
function receivedFull(reader) { function receivedFull(reader) {
var type = reader.uint(1); var type = reader.uint(1);
if(type == 0x41) { if(type == 0x41) {
if(self.debug) console.log('Received challenge key'); if(self.debug) console.log('Received challenge key');
if(self._challenge) return self.fatal('Received more than one challenge key'); if(self._challenge) return self.fatal('Received more than one challenge key');
self._challenge = reader.uint(4); self._challenge = reader.uint(4);
if(sendChallenge) { if(sendChallenge) {
if(self.debug) console.log('Restarting query'); if(self.debug) console.log('Restarting query');
send(); send();
return true; return true;
} }
} }
if(self.debug) console.log("Received "+type.toString(16)+" expected "+expect.toString(16)); if(self.debug) console.log("Received "+type.toString(16)+" expected "+expect.toString(16));
if(type != expect) return; if(type != expect) return;
callback(reader.rest()); callback(reader.rest());
return true; return true;
} }
} }
}); });

View File

@ -1,240 +1,240 @@
var async = require('async'); var async = require('async');
module.exports = require('./core').extend({ module.exports = require('./core').extend({
init: function() { init: function() {
this._super(); this._super();
this.byteorder = 'be'; this.byteorder = 'be';
}, },
run: function(state) { run: function(state) {
var self = this; var self = this;
this.sendCommand(2,'',function(data) { this.sendCommand(2,'',function(data) {
state.raw = splitFields(data.toString()); state.raw = splitFields(data.toString());
state.raw.CLIENTS.forEach(function(client) { state.raw.CLIENTS.forEach(function(client) {
client.name = client.NAME; client.name = client.NAME;
delete client.NAME; delete client.NAME;
client.ping = parseInt(client.PING); client.ping = parseInt(client.PING);
delete client.PING; delete client.PING;
state.players.push(client); state.players.push(client);
}); });
delete state.raw.CLIENTS; delete state.raw.CLIENTS;
if('NAME' in state.raw) state.name = state.raw.NAME; if('NAME' in state.raw) state.name = state.raw.NAME;
if('MAXCLIENTS' in state.raw) state.maxplayers = state.raw.MAXCLIENTS; if('MAXCLIENTS' in state.raw) state.maxplayers = state.raw.MAXCLIENTS;
if(self.trueTest(state.raw.AUTH)) state.password = true; if(self.trueTest(state.raw.AUTH)) state.password = true;
self.finish(state); self.finish(state);
}); });
}, },
sendCommand: function(cmd,password,c) { sendCommand: function(cmd,password,c) {
var self = this; var self = this;
var body = new Buffer(16); var body = new Buffer(16);
body.write(password,0,15,'utf8'); body.write(password,0,15,'utf8');
var encrypted = encrypt(cmd,body); var encrypted = encrypt(cmd,body);
var packets = {}; var packets = {};
this.udpSend(encrypted, function(buffer) { this.udpSend(encrypted, function(buffer) {
if(buffer.length < 20) return; if(buffer.length < 20) return;
var data = decrypt(buffer); var data = decrypt(buffer);
if(data.zero != 0) return; if(data.zero != 0) return;
packets[data.packetNum] = data.body; packets[data.packetNum] = data.body;
if(Object.keys(packets).length != data.packetTotal) return; if(Object.keys(packets).length != data.packetTotal) return;
var out = []; var out = [];
for(var i = 0; i < data.packetTotal; i++) { for(var i = 0; i < data.packetTotal; i++) {
if(!(i in packets)) return self.fatal('Missing packet #'+i); if(!(i in packets)) return self.fatal('Missing packet #'+i);
out.push(packets[i]); out.push(packets[i]);
} }
c(Buffer.concat(out)); c(Buffer.concat(out));
return true; return true;
}); });
} }
}); });
function splitFields(str,subMode) { function splitFields(str,subMode) {
var splitter,delim; var splitter,delim;
if(subMode) { if(subMode) {
splitter = '='; splitter = '=';
delim = ','; delim = ',';
} else { } else {
splitter = ': '; splitter = ': ';
delim = '\n'; delim = '\n';
} }
var split = str.split(delim); var split = str.split(delim);
var out = {}; var out = {};
if(!subMode) { if(!subMode) {
out.CHANNELS = []; out.CHANNELS = [];
out.CLIENTS = []; out.CLIENTS = [];
} }
split.forEach(function(one) { split.forEach(function(one) {
var equal = one.indexOf(splitter); var equal = one.indexOf(splitter);
var key = equal == -1 ? one : one.substr(0,equal); var key = equal == -1 ? one : one.substr(0,equal);
if(!key || key == '\0') return; if(!key || key == '\0') return;
var value = equal == -1 ? '' : one.substr(equal+splitter.length); var value = equal == -1 ? '' : one.substr(equal+splitter.length);
if(!subMode && key == 'CHANNEL') out.CHANNELS.push(splitFields(value,true)); if(!subMode && key == 'CHANNEL') out.CHANNELS.push(splitFields(value,true));
else if(!subMode && key == 'CLIENT') out.CLIENTS.push(splitFields(value,true)); else if(!subMode && key == 'CLIENT') out.CLIENTS.push(splitFields(value,true));
else out[key] = value; else out[key] = value;
}); });
return out; return out;
} }
function randInt(min,max) { function randInt(min,max) {
return Math.floor(Math.random()*(max-min+1)+min) return Math.floor(Math.random()*(max-min+1)+min)
} }
function crc(body) { function crc(body) {
var crc = 0; var crc = 0;
for(var i = 0; i < body.length; i++) { for(var i = 0; i < body.length; i++) {
crc = crc_table[crc>>8] ^ body.readUInt8(i) ^ (crc<<8); crc = crc_table[crc>>8] ^ body.readUInt8(i) ^ (crc<<8);
crc &= 0xffff; crc &= 0xffff;
} }
return crc; return crc;
} }
function encrypt(cmd,body) { function encrypt(cmd,body) {
var headerKeyStart = randInt(0,0xff); var headerKeyStart = randInt(0,0xff);
var headerKeyAdd = randInt(1,0xff); var headerKeyAdd = randInt(1,0xff);
var bodyKeyStart = randInt(0,0xff); var bodyKeyStart = randInt(0,0xff);
var bodyKeyAdd = randInt(1,0xff); var bodyKeyAdd = randInt(1,0xff);
var header = new Buffer(20); var header = new Buffer(20);
header.writeUInt8(headerKeyStart,0); header.writeUInt8(headerKeyStart,0);
header.writeUInt8(headerKeyAdd,1); header.writeUInt8(headerKeyAdd,1);
header.writeUInt16BE(cmd,4); header.writeUInt16BE(cmd,4);
header.writeUInt16BE(body.length,8); header.writeUInt16BE(body.length,8);
header.writeUInt16BE(body.length,10); header.writeUInt16BE(body.length,10);
header.writeUInt16BE(1,12); header.writeUInt16BE(1,12);
header.writeUInt16BE(0,14); header.writeUInt16BE(0,14);
header.writeUInt8(bodyKeyStart,16); header.writeUInt8(bodyKeyStart,16);
header.writeUInt8(bodyKeyAdd,17); header.writeUInt8(bodyKeyAdd,17);
header.writeUInt16BE(crc(body),18); header.writeUInt16BE(crc(body),18);
var offset = headerKeyStart; var offset = headerKeyStart;
for(var i = 2; i < header.length; i++) { for(var i = 2; i < header.length; i++) {
var val = header.readUInt8(i); var val = header.readUInt8(i);
val += code_head.charCodeAt(offset) + ((i-2) % 5); val += code_head.charCodeAt(offset) + ((i-2) % 5);
val = val & 0xff; val = val & 0xff;
header.writeUInt8(val,i); header.writeUInt8(val,i);
offset = (offset+headerKeyAdd) & 0xff; offset = (offset+headerKeyAdd) & 0xff;
} }
offset = bodyKeyStart; offset = bodyKeyStart;
for(var i = 0; i < body.length; i++) { for(var i = 0; i < body.length; i++) {
var val = body.readUInt8(i); var val = body.readUInt8(i);
val += code_body.charCodeAt(offset) + (i % 72); val += code_body.charCodeAt(offset) + (i % 72);
val = val & 0xff; val = val & 0xff;
body.writeUInt8(val,i); body.writeUInt8(val,i);
offset = (offset+bodyKeyAdd) & 0xff; offset = (offset+bodyKeyAdd) & 0xff;
} }
return Buffer.concat([header,body]); return Buffer.concat([header,body]);
} }
function decrypt(data) { function decrypt(data) {
var header = data.slice(0,20); var header = data.slice(0,20);
var body = data.slice(20); var body = data.slice(20);
var headerKeyStart = header.readUInt8(0); var headerKeyStart = header.readUInt8(0);
var headerKeyAdd = header.readUInt8(1); var headerKeyAdd = header.readUInt8(1);
var offset = headerKeyStart; var offset = headerKeyStart;
for(var i = 2; i < header.length; i++) { for(var i = 2; i < header.length; i++) {
var val = header.readUInt8(i); var val = header.readUInt8(i);
val -= code_head.charCodeAt(offset) + ((i-2) % 5); val -= code_head.charCodeAt(offset) + ((i-2) % 5);
val = val & 0xff; val = val & 0xff;
header.writeUInt8(val,i); header.writeUInt8(val,i);
offset = (offset+headerKeyAdd) & 0xff; offset = (offset+headerKeyAdd) & 0xff;
} }
var bodyKeyStart = header.readUInt8(16); var bodyKeyStart = header.readUInt8(16);
var bodyKeyAdd = header.readUInt8(17); var bodyKeyAdd = header.readUInt8(17);
offset = bodyKeyStart; offset = bodyKeyStart;
for(var i = 0; i < body.length; i++) { for(var i = 0; i < body.length; i++) {
var val = body.readUInt8(i); var val = body.readUInt8(i);
val -= code_body.charCodeAt(offset) + (i % 72); val -= code_body.charCodeAt(offset) + (i % 72);
val = val & 0xff; val = val & 0xff;
body.writeUInt8(val,i); body.writeUInt8(val,i);
offset = (offset+bodyKeyAdd) & 0xff; offset = (offset+bodyKeyAdd) & 0xff;
} }
// header format: // header format:
// key, zero, cmd, echo, totallength, thislength // key, zero, cmd, echo, totallength, thislength
// totalpacket, packetnum, body key, crc // totalpacket, packetnum, body key, crc
return { return {
zero: header.readUInt16BE(2), zero: header.readUInt16BE(2),
cmd: header.readUInt16BE(4), cmd: header.readUInt16BE(4),
packetTotal: header.readUInt16BE(12), packetTotal: header.readUInt16BE(12),
packetNum: header.readUInt16BE(14), packetNum: header.readUInt16BE(14),
body: body body: body
}; };
} }
var code_head = var code_head =
'\x80\xe5\x0e\x38\xba\x63\x4c\x99\x88\x63\x4c\xd6\x54\xb8\x65\x7e'+ '\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'+ '\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'+ '\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'+ '\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'+ '\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'+ '\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'+ '\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'+ '\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'+ '\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'+ '\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'+ '\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'+ '\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'+ '\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'+ '\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'+ '\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'; '\xff\x95\x6d\xc7\x04\xa2\x3b\xc4\x1b\x72\xc7\x6c\x82\x60\xd1\x0d';
var code_body = var code_body =
'\x82\x8b\x7f\x68\x90\xe0\x44\x09\x19\x3b\x8e\x5f\xc2\x82\x38\x23'+ '\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'+ '\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'+ '\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\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'+ '\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'+ '\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'+ '\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'+ '\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'+ '\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'+ '\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'+ '\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'+ '\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'+ '\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'+ '\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'+ '\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'; '\xdb\x77\xdd\xea\x38\x59\x89\x32\xbc\x00\x3a\x6d\x61\x4e\xdb\x29';
var crc_table = [ var crc_table = [
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
]; ];

View File

@ -1,12 +1,12 @@
module.exports = require('./quake3').extend({ module.exports = require('./quake3').extend({
finalizeState: function(state) { finalizeState: function(state) {
this._super(state); this._super(state);
if(state.players) { if(state.players) {
for(var i = 0; i < state.players.length; i++) { for(var i = 0; i < state.players.length; i++) {
var player = state.players[i]; var player = state.players[i];
player.team = player.address; player.team = player.address;
delete player.address; delete player.address;
} }
} }
} }
}); });