pax_global_header00006660000000000000000000000064141245747370014530gustar00rootroot0000000000000052 comment=fab391255297c437fb84676104508a8b489fa8bf qstat-2.17/000077500000000000000000000000001412457473700126155ustar00rootroot00000000000000qstat-2.17/.github/000077500000000000000000000000001412457473700141555ustar00rootroot00000000000000qstat-2.17/.github/workflows/000077500000000000000000000000001412457473700162125ustar00rootroot00000000000000qstat-2.17/.github/workflows/build.yaml000066400000000000000000000020231412457473700201720ustar00rootroot00000000000000name: Build CI on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build_linux: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: autogen run: ./autogen.sh - name: autoreconf force run: autoreconf --force - name: configure run: ./configure - name: make check run: make check - name: make distcheck run: make distcheck build_macos: runs-on: macos-latest steps: - uses: actions/checkout@v2 - name: install automake run: brew install automake - name: autogen run: ./autogen.sh - name: autoreconf force run: autoreconf --force - name: configure run: ./configure - name: make check run: make check build_windows: runs-on: windows-latest steps: - uses: actions/checkout@v2 - uses: ilammy/msvc-dev-cmd@v1 - name: nmake debug build run: | nmake -f Makefile.noauto windows_debug qstat-2.17/.github/workflows/release.yaml000066400000000000000000000037041412457473700205220ustar00rootroot00000000000000name: Tag Release on: push: tags: - 'v*' jobs: build_linux: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: autogen run: ./autogen.sh - name: configure run: ./configure - name: make run: QSTAT_VERSION=${{ github.event.release.tag_name }} make - uses: actions/upload-artifact@v2 with: name: linux_amd64 path: qstat build_macos: runs-on: macos-latest steps: - uses: actions/checkout@v2 - name: install automake run: brew install automake - name: autogen run: ./autogen.sh - name: configure run: ./configure - name: make run: QSTAT_VERSION=${{ github.event.release.tag_name }} make - uses: actions/upload-artifact@v2 with: name: darwin_amd64 path: qstat build_windows: runs-on: windows-latest env: QSTAT_VERSION: ${{ github.event.release.tag_name }} steps: - uses: actions/checkout@v2 - uses: ilammy/msvc-dev-cmd@v1 - name: nmake run: | nmake -f Makefile.noauto windows windows_debug - uses: actions/upload-artifact@v2 with: name: windows_amd64 path: qstat.exe # TODO(austin) build for mac-os which would cover freebsd # https://github.com/vmactions/freebsd-vm release: runs-on: ubuntu-latest needs: - build_windows - build_linux - build_macos steps: - uses: actions/checkout@v2 - uses: actions/download-artifact@v2 with: path: bin - name: zip run: zip -r release.zip bin/* qstat.cfg contrib.cfg LICENSE.* - name: Release uses: "softprops/action-gh-release@v1" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: prerelease: false tag_name: "${{ github.event.release.tag_name }}" files: | release.zip qstat-2.17/.gitignore000066400000000000000000000003441412457473700146060ustar00rootroot00000000000000*.in aclocal.m4 autom4te.cache compile config.guess config.sub config.log config.status configure depcomp install-sh missing .deps Makefile gnuconfig.h stamp-h1 *.o *.obj *.pdb qstat qstat.exe .compiler_flags .version version.h qstat-2.17/CHANGES.txt000066400000000000000000001443561412457473700144430ustar00rootroot00000000000000Qstat version 2.15 Summary of New Features ----------------------- New protocols: KSP [-ksp] Qstat version 2.14 Summary of New Features ----------------------- New protocols: DirtyBomb aka Extraction [-dirtybomb] Starmade [-starmade] Farmsim [-farmsim] Refactored output splitting out xform processing into its own files improving various portions of it. Fixes ----- hcache segv fixes packetmanip fixes for packet combining -- Steven Harltand QStat version 2.11 ** UPDATED for 2.11 ** November 04, 2006 Summary of New Features ----------------------- new protocols: Warsow [-warsows, -warsowm] Prey [-preys, -preym] TrackMania [-tm] Tremulous [-tremulous, -tremulousm] add -nx and -nnx options that enable resp. disable name transformation calculate player score for AMS servers Fixes ----- fix segfault when a "tribes2 master" returns garbage -- Steve Jankowski Steven Hartland Ludwig Nussel QStat version 2.10 ** UPDATED for 2.10 ** October 22, 2005 Fixes ----- fix Quake4 master only returning 231 servers fix busy loop when waiting for Quake3 and Quake4/Doom3 master packets fix win32 build -- Steve Jankowski Steven Hartland Ludwig Nussel QStat version 2.9 ** UPDATED for 2.9 ** October 20, 2005 Summary of New Features ----------------------- new protocols: Pariah [-prs] Steam Master for A2S [-stma2s] Nexuiz [-nexuizm, -nexuizs] Gamespy V3 [-gs3] Quake 4 [-q4m, -q4s] add option -allowserverdups to be able to query ts2 servers support for LAN broadcasts with A2S add option -sendinterval for tuning send throttling add support for port ranges support HL1 protocol variant in A2S add 'S' player sort option to sort by score Fixes ----- fix eye protocol not showing the last player always use an offset of 123 with eye protocol fix infinite loop and memory expansion when port is 65535 -- Steve Jankowski Steven Hartland Ludwig Nussel ** UPDATED for 2.8 ** April 03, 2005 Summary of New Features ----------------------- add support for new steam protocol (-a2s) add derived type STMHL2 for steam master returning a2s instead of hls change timing for sending packets to improve ping times of low-ping servers Fixes ----- fix ut2004 master server support -- Steve Jankowski Steven Hartland Ludwig Nussel QStat version 2.7 ** UPDATED for 2.7 ** Dec 22, 2004 Summary of New Features ----------------------- new protocols: UT2004 master [-ut2004m] update steam master protocol to new version add additional output to the player xml of -gps add option -progress,n to print out progress every n packets add doom3 protocol and osmask as server rule use autoconf+automake, a simple Makefile is provided as fallback add "flags" to qstat.cfg Fixes ----- fix hanging qstat when receiving UDP packets with incorrect checksum fix colored names in America's Army (use -ams instead of -gps) -- Steve Jankowski Steven Hartland Ludwig Nussel QStat version 2.6 ** UPDATED for 2.6 ** Aug 15, 2004 Summary of New Features ----------------------- new protocols: All Seeing Eye [-eye] Ravenshield [-rvs] Savage [-sas] FarCry [-fcs] Jedi Knight: Jedi Academy [-jk3m, -jk3s] Gamespy2 [-gs2] Steam master [-stm] Doom3 [-dm3s, -dm3s] Half-Life 2 [-hl2s] (experimental) add option -hsn to display server names in hex [no docs] add "servers/sec" to -progress output add -mdelim parameter to specify the multi value delimiter ( default '|' ) add \final\\ to end of gamespy master request add UT2K4 colored name parsing add UT2 XMP colored name parsing add SOF coloring of server names Fixes ----- fix custom q3 master query for masters created via qstat.cfg fix gamespy protocol to better handle Halo and BF1942 fix for multi value fields in UT2003 e.g. Mutators. fix XML escaping bugs New template variables ---------------------- $SCORE player score Thanks ------ Timothee Besset from id Software for providing Doom3 protocol information Alexander Schäfer for fixing version and password field for Ravenshield and of course everyone else who provided feedback and bug reports -- Steve Jankowski Steven Hartland Ludwig Nussel ** UPDATED for 2.5c ** Nov 11, 2002 Summary of New Features ----------------------- Unreal Tournament 2003 support [-ut2s] Including support for the current UT2003 patch Ghost Recon support [-grs] Bob Marriott provided the patch for this. I integrated with very few changes. See info/GhostRecon.txt for more detail. Thanks Bob! Supports versions 1.2, 1.3, 1.4 and expansions -noportoffset to disable adding the port offset to query packets There's a "noportoffset" query argument to set this per-server. -showgameport to output the game port instead of the query port With -showgameport, qstat will report the game port instead of the query port in $ARG, $PORT, and $HOSTNAME. If the query port is different from game port, the query port will be set in the "_queryport" server rule. There's a "showgameport" query argument to set this per-server. New template variables ---------------------- $TOTALMAXPLAYERS Sum of max players from all servers $PLAYERSTATID UT2003 global player stat id (not yet supported by Epic). $UNREALTOURNAMENT2003 $GHOSTRECON $PLAYERLEVEL Character level for NWN $IF:DEATHS True if $DEATHS > 0 $TOTALUTILIZATION How full the servers are (0 to 100 percent) Fixes ----- Fixed packet parsing for Battlefield 1942. Use -gps to query BF1942 servers. The query port is usually 23000. Fix broadcast queries for Tribes 2, Quake 3, UT2003 (hopefully) Fix all output modes to support team-numbers, team-names, and no-team styles Fix xml output; accidentally disabled and Fix player info for NWN and other GPS games Fix sv_password rule on Half-Life servers Removed some debug output Thanks ------ A big round of applause for Bob Marriott, author of the Ghost Recon support. He reverse engineered the GR status protocol and maintained the patch through several protocol revisions. And he wrote documentation! Thanks to Ludwig Nussel and Alex Burger of XQF for testing, suggestions, and some timely stubborness :-) Thanks for testing and suggestions: Kingsley Foreman, Pierre Smolarek, Simon Garner and Paul Witt. Steve, steve@qstat.org ** UPDATED for 2.5b ** August 8, 2002 Whoops, I broke XQF. When using QStat raw mode, XQF and other programs rely on the number of fields returned staying fixed. I added a game/mod field to most raw output which breaks these programs. Summary of New Features ----------------------- Extended -raw with a -raw,game variation that adds the game/mod name to the end of the raw server output. Otherwise, the game/mod info is not output in raw mode. Thanks to Ludwig Nussel for not biting my head off (too much). Steve, steve@qstat.org ** UPDATED for 2.5a ** August 6, 2002 Final version of QStat 2.5a. Please send feedback and bug reports to qstat-users@yahoogroups.com or to steve@qstat.org This version of QStat is being distributed under the Artistic License. The terms of the license can be found in LICENSE.txt in the QStat package. Summary of New Features ----------------------- Config file support to define new game types This is a major new feature. This will allow users to define new game types and master servers, including setting custom request packets. I expect that support for most new game types will be accomplished via config files. The default config file is "qstat.cfg". More game types from QStat users can be found in "contrib.cfg". Added support for "status port offset" to config file; UNS (unreal) has this set to 1. If you extend UNS, the new server type will have the same status port offset. Added a GNUmakefile to support gmake Updated COMPILE.txt with new instructions Improve Half-Life server status; extracts mod information and secure status Add support for server rules template (-Tr file) See qstatdoc.html for instructions. Support broadcast queries for Tribes and Tribes 2 Star Trek: Elite Force server and master (-efs and -efm) Return to Castle Wolfenstein server and master (-rws and -rwm) Debug flag (-d) (uses an improved packet output format) Support color player names in Soldier of Fortune Default Config File (qstat.cfg) ------------------------------- Command and Conquer: Renegade server (-crs) Soldier of Fortune 2 (-sof2s) Soldier of Fortune 2 Master (-sof2m) Medal of Honor: Allied Assault server (-mas) Medal of Honor: Allied Assault server, Quake 3 protocol (-maqs) Half-Life "infostring" protocol (-hlqs) This is a Quake 2 style protocol Fixes ----- Fixed raw output to include the game or mod name as the last item Fix template to allow numbers, '.' and ' ' in rule names Refers to $(RULE:someserverrule) Allowing space ' ' may break existing templates if they use syntax like $RULE:maxbullets Max Bullets This can be fixed by change it to $(RULE:maxbullets) This change was made because SOF2 allows spaces in server rule names. Fix Unreal and Gamespy based servers to support backslashes '\' in player names (only partially worked before) Support Half-Life split packets; this happens to the rules info from some Half-Life servers (usually AdminMod and DOD). XML output; added element, UTF-8 output option (-utf8) XML; added Fix crash on broadcast queries Avoid duplicates in the rule list Fixed -srcip to put address in the right byte-order Work-around for crash on Windows when bind() returns temporary error Finish output when -timeout expires Changed Q3 based servers to use a two-packet status query; I think this gets more accurate mod information. More accurate player count on Quake based servers Compiles on OSX/Darwin Fix one byte overrun on QuakeWorld packet New template variables ---------------------- $TYPEPREFIX The server's game type prefix $RULENAMESPACES Allow spaces in rule names $RULETEMPLATE Invoke the server rule template $RULENAME The rule name $RULEVALUE The rule value $(IF:RULENAME(name)) True if rule name equals "name" $(IF:RULEVALUE(value)) True if rule value equals "value" (see qstat.cfg for gametype variables) Thanks ------ Many thanks to all the beta testers for bug reports and feature suggestions. There are many to list, but the following provided significant help: Kingsley Foreman, Mike Davis, Ludwig Nussel, Pierre Smolarek, aphax, and Simon Garner. and I'm sure I forgot someone Steve, steve@qstat.org ** UPDATED for 2.4e ** Oct 1, 2001 This release is basically to fix Tribes 2 support. That was all my fault, Dynamix/Sierra did nothing wrong. Since I was going through the trouble of a full release, I decided to toss in Descent 3 support. Don't be impressed, a patch for D3 was sitting in my inbox. :-) Summary of New Features ----------------------- Descent 3 support -htmlmode (same as $HTML, but for -raw mode) Fixes ----- Fixed Tribes 2 queries Fixed $SHIRTCOLOR when no color names are used Thanks ------ Many thanks to Matthew Mueller for a patch to support Descent 3. Matthew thanks Brian Hayes, Kevin Bentley, and tcpdump. Thanks to Thomas Hager for putting me on the right track for the Tribe 2 bug. Sorry, the real -noconsole support did not make it into this release. Steve, steve@qstat.org ** UPDATED for 2.4d ** August 8, 2001 Summary of New Features ----------------------- Half-Life master filters Names for Quake 3 game types XML output mode Broadcast support for Gamespy style protocols Append output file option [-af] Option to print carets in Q3 player names [-carets] OS/2 Warp EMX compiler port OpenBSD support Options to specify source ports [-srcport] and source IP address [-srcip] for packets sent by qstat. Handy for getting through firewalls. See docs for details. Fixes ----- Tribes 2 teamdamage flag Fixed writing beyond array bounds when reading templates on Windows. (maybe this will fix the garbage characters some people see) Fixed player sorting for games with teams (tribes 1 and 2). Removed win32/qstat.exe from the tar.gz Trying the MS C 6.0 compiler again (win32/qstat.exe) Fixed running QStat from PHP; would crash or give no data New template variables ---------------------- $CLEARNEWLINES Convert newlines to spaces in all variable output. Handy for Tribes 2 servers with long descriptions. $GAMETYPE Quake 3 only - the name of the game type Thanks ------ Most of this release was contributed by QStat users: XML output Simon Garner Half-Life master filters Ludwig Nussel OS/2 Warp EMX Mikulas Patocka OpenBSD subset A three rail gun salut to ya! And thanks to Tahi 'Linus' Walcher for helping find the PHP bug. strace is a thing of the gods. I was hoping to fix one other problem for Windows users. QStat is a command-line style program, so Windows always gives it a console window. This is annoying if you're running QStat from a web-page or a GUI server browser. I can fix this, but it's not that easy. In the mean time, I've added a -noconsole option that will delete the console as soon as qstat starts. The effect is that a console window will flash on the screen briefly. QStat still runs, you just won't see any output on screen. Anyway, I'll craft a real implementation of -noconsole in the next release. Steve, steve@qstat.org ** UPDATED for 2.4c ** Apr 19, 2001 Summary of New Features ----------------------- Tribes 2 player type and tribe tag information Changed Quake 3 default version to "48" (1.27g) New Tribes 2 master filters. (see documentation) Bug Fixes --------- Fixed win32/qstat.exe binary; re-compiled using old compiler Convert newlines into spaces when printing Tribes 2 server info in raw mode. Fixed problems with the 22337 build of Tribes 2. Tribes 2 raw output is now the same format as most other servers. Note that this reverses the order of current and max players from what they were in 2.4b. And adds ping and # retries values. New template variables ---------------------- $IF:ISBOT $IF:ISALIAS $IF:TRIBETAG $TRIBETAG Notes ----- [ Blarg, I hate it when I blow a release. Many people had problems with the win32/qstat.exe in 2.4b release. I had compiled this with a new compiler version on a new Win2k install. It worked fine for me, but apparently the new compiler has compatibility problems. So, I've gone back to the old compiler and old NT 4.0 install. Several people also reported problems using 2.4b with the new 22337 build of Tribes 2. Sorry about the problems. This release should fix things up. ] The new Tribes 2 player info is available in raw mode (see docs) and templates. But it is not displayed in the interactive output. I've updated the sample Tribes 2 templates to display the new player info. Finally, there seem to be a number of bugs with the Tribes 2 master servers. One of the bugs is noted in the documentation. Dynamix is aware of the problems and working to fix them. Steve, steve@qstat.org ** UPDATED for 2.4b ** Apr 13, 2001 Summary of New Features ----------------------- Support for Tribes 2 [-t2s] (builds numbered 22075 or higher) Support for Tribes 2 master [-t2m] Support for Quake 3 and Tribes 2 colorized player names [-htmlnames] Sample HTML templates for Tribes 2 [template/tribes2*] New QStat web site! http://www.qstat.org New mailing lists for QStat users and announcements! (see web site) New template variables ---------------------- $ISMASTER $TRIBES2 $TRIBES2MASTER $HTMLPLAYERNAME Bug Fixes --------- Fix 'game' value for some Quake 2 based servers Fixed some cases where QStat will hang Fixed some picky compiler warnings Notes ----- The Tribes 2 master server has a number of fancy filtering options. Check the "Tribes 2 Master" section in the documentation for details. Many thanks to Brad Heinz and Dynamix for their help with Tribes 2. Thanks to the QStat beta testers: Dr. Chmod, Marauder, Leif Sawyer, Luca Spada, Simon Garner, and Jose Ivey Steve, steve@qstat.org ** UPDATED for 2.4a ** Oct 5, 2000 Summary of New Features ----------------------- Support for Gamespy master [-gsm] Support for "Gamespy style" protocol queries (adds 12+ games) [-gsm and -gps] More server sort options [-sort] Player sort options [-sort] Option to set the output file [-of] Quake 3 master query argument; can query by protocol version Other Improvements ------------------ Way way way faster Fixed Q3 master server queries Fixed queries of SoF 1.05 servers Servers now queried in the order provided to QStat (was reverse order) Fixed ^ display in Q3 player names Partial fix for '\' in player names Players now displayed in the order the server reports them (was reverse order) Rules now displayed in the order the server reports them (was reverse order) Server variables can be referenced inside player templates Fixed raw display of SoF servers Improved HalfLife queries; fetches sv_type and sv_os server rules Much much much faster New template variables ---------------------- $IF:GAMESPYMASTER $IF:GAMESPYPROTOCOL Notes ----- I'm probably treading on some toes with the Gamespy master support. Someone figured out that the Gamespy server lists are not protected, and sent me the protocol. Once I had that, supporting the master was trivial. See the documentation for details on using a gamespy master. Many games are using the Unreal style server status protocol. Maybe because there are lots of Unreal engine games. Or maybe because developers want Gamespy support, so they use a protocol that Gamespy already supports. There are too many of these games to add individual game types and command-line options. So I've lumped them together as "Gamespy Protocol" servers using the game type "GPS". They all seem to have a server rule called "gamename" set to the name of the game (eg. "roguespear", "turok2"). I made a pile of performance improvements to QStat. Start-up time for large server lists is very fast. Queries on large servers lists is also much faster. And queries require much less CPU time. If you have -maxsim set high to reduce query times, you might see more server timeouts now. QStat can spew out packets so fast, you'll see more packet loss and hence more timeouts. Thanks ------ Many thanks to the beta testers and contributors. They provided bug fixes, bug reports, suggestions, ideas, protocol traces, and kind words. I'll spare them the spam by just mentioning real names: Alex Burger, Conan Ford, Vitaliy Fuks, Mike Dowell, Nico de Vries, and Jose Ivey Steve ** UPDATED for 2.3g ** Feb 3, 2000 BETA is done! The 2.3 release is stable enough now to drop the beta qualifier. Summary of New Features ----------------------- Added options to support Soldier of Fortune [-sfs] New template variables ---------------------- $IF:SOLDIEROFFORTUNE There are command-line options to control the following, but it's handy to have control within the templates as well. $COLORNUMBERS Set the format of $SHIRTCOLOR and $PANTSCOLOR $COLORNAMES $COLORRGB $TIMESECONDS Set the format of $CONNECTTIME $TIMECLOCK $TIMESTOPWATCH Summary of Fixes ---------------- Fix crashes in Unreal -raw player output (duh). Fix hangs doing Unreal server queries. Fix $GAME for Quake 3 servers Fix Kingpin servers reported as Q2 servers Notes ----- The SOF server does not return map information, so you'll just get a '?' or blank for an SOF map. The map info is returned by the master server. But QStat does not yet support the SOF master server. I traced the SOF master protocol, but it's totally different from any other master server. Before I invest the time into writing a parser for the SOF master packets, I'm going to wait to see if it changes before the final retail release. Notes on Future Releases ------------------------ Now that the 2.3 release train is winding down, what's up for QStat 2.4? I don't know, but there's a few frequently requested features: - More sort options; server and player - More games; Delta Force, Descent III - Fetch server lists from web pages - Better templates; variables and full expression evaluation - Better performance on large server lists - Examples and samples I don't know which features will be available when, so don't ask. There may be one or two more 2.3 releases to fix critical bugs or add a new game. Getränk und ist fröhlich, Steve ** UPDATED for 2.3f BETA ** Jan 11, 2000 Summary of Fixes ---------------- Added player ping and face to Unreal -raw output Fix queries for version 405 Unreal servers New template variable --------------------- For the player template: $FACE The texture used on a player's face. This was added to Unreal a while back, but I didn't notice. Notes ----- The fine blokes at Unreal decided to fiddle with the Unreal query protocol. When asked why PingTool and QStat did not work with the new servers, they responded that we must not be following the new GameSpy spec. Ha! As if they publish this information or care to keep other server browser authors informed. Sheesh. Steve ** UPDATED for 2.3e BETA ** Jan 7, 2000 Summary of Fixes ---------------- Quake III Arena master queries Half-Life master queries $(IF:GAME) was true when $GAME was blank (Tribes) Improved Q3A player name translation Fixed raw mode output for Unreal (minor incompatible change) Updated docs for raw mode output Add one to Unreal/UT port to get query port (incompatible change) $GAME and "-sort g" now work for Unreal/UT Summary of New Features ----------------------- Options added for Kingpin and Heretic II game servers. These two games were previously supported with the -q2s option. New options: -kps and -hrs New template conditions: $(IF:KINGPIN) and $(IF:HERETIC2) Notes ----- Numerous people sent me the fix for the Q3A master query problem. I think the first was Ted Milker, but thanks to all for your suggestions and patience. The Half-Life master support wasn't using the latest protocol, so needed an upgrade. Now you can get the full 2100+ server list from the master. BTW, there are more HL servers than Q3A servers. Unreal support was changed to automatically add one to the port number to find the query port. This is probably why people thought QStat didn't support UT. For those that figure it out already, you'll have to remove your own +1 hacks to use this release. I apologize for the long delay between releases. Work, holidays, and health problems cut my free time to less than zero. Best wishes for the new millennium, Steve ** UPDATED for 2.3d BETA ** May 12, 1999 More bug reports and suggestions prompted this release. Quake 3 master -------------- The id Q3 master server isn't working very well. The protocol is lame and its network is way over subscribed. I've enhanced QStat to deal with the changes, but I actually think they (id) have made matters worse rather than better. QStat can now, sometimes, get all the servers from the master, but it often can't get 30-60% of the servers. This is not QStat problem, but id's. I've sent them a detailed analysis of the problems and made some suggestions. Summary of Fixes ---------------- Improve reliability of Q3 master server queries Support Tribes servers with 3+ teams Strip escape sequences from Q3 player names; use -hpn if you want the complete content of player names. Accurately calculate number of servers on a master (ignore duplicates). Fixed $(ISFULL) to be false if the server is empty [duh] New Features ------------ New option to control master server retry interval ("-mi") independent of server retry interval. Increase maximum -maxsim to 256 for Win32. Added $(TYPESTRING) template var; shows server's type string (eg. q2s, hls) Added $(NOWINT) template var; shows the current time in seconds since 00:00:00 UTC, January 1, 1970. Notes ----- Many many thanks to Nico de Vries (Nico.de.Vries@ucc.nl) of CLQ for the many bugs reports, suggestions, etc. Thanks to stincey@nireland.com for figuring out the Tribes 3+ teams bug. Steve, steve@activesw.com ** UPDATED for 2.3c BETA ** May 3, 1999 A slew of bug reports arrived over the weekend. I guess QStat users are weekend warriors. Thanks to the four people that sent patches for the Q3 master breakage. I won't say who's patch I used, but I selected the most elegant. id has said that the Q3 master protocol will be changing alot over the next weeks. I'll try to track their progress, but no promises. They also asked for suggestions on reducing packet size, so I sent them my ideas (all quite clever ). Summary of Fixes ---------------- Q3 master server Q3 server name Tribes -raw mode [forgot to write it] Removed leftover debug print Switched snprintf() to sprintf() [much more portable] Half-Life game rules [useful for TFC] Fixed -progress output Added player ping to Unreal player output Fixed minor mistakes in docs Notes ----- Many thanks to: "Dark Grue" of QStatList Nico de Vries of CLQ "Joe S." of ... I forget what Joe does Sven Grundmann for their bug reports, patches, and suggestions. Steve, steve@activesw.com ** UPDATED for 2.3b BETA ** April 29, 1999 New: Quake III and BFRIS support Not many complaints about problems with 2.3a, looks like the new code is working fine. Summary of New Features ----------------------- Quake III: Arena (Q3Test) Quake III master BFRIS (www.aegistech.com) Minor Changes ------------- A couple people pointed out that the docs and the code did not exactly agree on the server type strings. So, I've fixed the code to match the docs. The following type strings were changed: 2.3a 2.3b ---- ---- QW --> QWS Q2 --> Q2S Notes ----- Voluminous thanks to Dave "Zoid" Kirsch of id Software for supplying diffs to support Quake III. Zoid says has used QStat "from time to time" over the years. Makes me smile. And a sweeping bow to Pete Ratzlaff of Harvard for the diff to support Linux game BFRIS. See www.aegistech.com for info. I gave the diffs the wary eye for fuggly programming, but both were written nicely and applied without a hitch. I performed minimal testing of the new code, please let me know if there are problems. Steve, steve@activesw.com ** UPDATED for 2.3a BETA ** April 19, 1999 Lots of changes and additions in this release, far more than I have patience to test. Please try this release with your web page, stats program, server browser, and what not. If you encounter any problems _please_ send me email! Flames on my sloppy coding are acceptable. Summary of New Features ----------------------- Complete Half-Life support (players and server rules) Half-Life master server (option -hlm) Tribes servers (option -tbs) Tribes master server (option -tbm) Shogo (option -sgs) Hex player colors (option -hc) Several new template variables Created server types table to simplify code Bug Fixes --------- Servers without "hostname" rule appeared to TIMEOUT One or two other boo-boos Incompatible ------------ The option "-qw" has been removed. Please use "-qwm" instead. New variables for output templates ---------------------------------- Generic variables $(DEFAULTTYPE) Full name of the default server type (-default) Server variables $(ISEMPTY) True if the server has no players $(ISFULL) True if the server is full of players Player variables $(PACKETLOSS) Players packets loss (Tribes only) $(ISTEAM) True if this player represents a team (Tribes only) $(TEAMNAME) Name of this player's team (Tribes only) Notes ----- Many thanks to the following for their assistance: zarjazz@barrysworld.com Tribes seb@club-internet.fr, carl@d-n-a.net Shogo sean@msiconsulting.com Half-Life Thanks to the many people who have sent suggestions for improvements. I've incorporated some of them in this release, more will follow in later releases. This release was focused on supporting popular new games and making it easier to add new games to the code. The backlog of new games support is now empty. If there's a game you would like to see supported, please send email. The Linux game BFRIS (http://www.aegistech.com) is the only planned new game support. Steve, steve@activesw.com ** UPDATED for 2.2b ** Jan 15, 1999 D'oh! I need to do more testing. The -raw mode did not work at all for Half-Life. Thanks to Dark Grue for bring that to my attention. Bug Fixes --------- Fix Half-Life support with -raw mode Shush compiler warning on Linux Steve, steve@activesw.com ** UPDATED for 2.2a ** Jan 14, 1999 Not too much in this release, but I've been getting an email a day asking about Half-Life support. The Half-Life status packets are totally different from Quake II, so I've only completed the general info so far. Players and rules from Half-Life will be available in the next release. I also threw in flags to support Sin. Sin was supported in previous releases by pretending it was a Q2 server, but now it has its own flags. Future releases of QStat will support even more games (Shogo, Blood 2, Heretic 2, etc). Summary of New Features ----------------------- Sin support (option -sns) Half-Life (partial) support (option -hls) Bug Fixes --------- Divide-by-zero bug with sorting Memory allocation bug using -H (hostname lookup) New variables for output templates ---------------------------------- Server template variables $SIN True if the server is running Sin $HALFLIFE True if the server is running Half-Life Steve, steve@activesw.com ** UPDATED for 2.1a ** Oct 4, 1998 This release supercedes previous 2.1 releases (2.1z BETA and 2.1y BETA) Summary of New Features ----------------------- Unreal 2.15+ support (option -uns) Broadcast queries (prefix address with '+') Save lists from master servers (option -qw,outfile and -q2m,outfile) Bug Fixes --------- Fixed host cache on Intel platforms Report Host Not Found as a server error (so it appears in templates and raw output) New variables for output templates ---------------------------------- Server template variables $HOSTNOTFOUND True if the host name lookup failed Player template variables $MESH Player mesh (model) name (Unreal only) Notes ----- QStat will support the public Unreal master server once it's done. The current Unreal master server is private to GameSpy (snarl). The broadcast queries is an experimental feature. I've only tested it with Quake II. Other games will be tested and supported in later releases. For example, querying the local net for Q2 servers on the default port: qstat -q2s +255.255.255.255 The '+' makes QStat broadcast to the given address. The default broadcast address for all nets is 255.255.255.255. You can also use a network specific broadcast (eg. 199.2.18.255). On Unixes, 'ifconfig -a' will display the broadcast address for all attached networks. The ",outfile" option for master servers is handy for dealing with unreliable master server (such as the id Q2 master). Run qstat once to save the servers lists to files, and a second time to query the servers: qstat -q2m,outfile satan.idsoftware.com,idq2.lst qstat -f idq2.lst The first command saves the server list from the id Q2 master into "idq2.lst". If the master isn't working, then the file retains its previous contents. The second command queries the servers in that file. I've also included the templates for an example Unreal server list web page. Thanks to my wonderful users for reporting bugs and making suggestions. And a special thanks to the Unreal development team for actually documenting their query protocol (unlike some other well known first person shooter developers). Steve, steve@activesw.com ** UPDATED for 2.1y BETA ** Aug 22, 1998 Summary of New Features ----------------------- HexenWorld support Revived support for id's Q2 master New variables for output templates Faster host cache initialization More efficient server query (Unix only) Support for AIX 4.2 and HPUX 11.0 Bug Fixes --------- Fixed bogus query failure on second server on same IP address. Fixed to ignore QW and Q2 server packets that contain lots of error messages. Also disabled printing of the "Odd packet" messages. They can be enabled with the -errors option. Fixed output templates on Windows Non-feature ----------- Unreal support. Unreal needs to get fixed before QStat can support it. HexenWorld support ------------------ Use the -hws command line option, or the HWS server type key. Revived support for id's Q2 master ---------------------------------- id's Quake II master server was broken for a long time. QStat supported id's Q2 master, but the master only rarely returned anything (and you had to wait up to 30 seconds for a response). When QuakeSpy announced that id had fixed their Q2 master, I was surprised that QStat did not work on the fixed master. Turns out the "fix" also changed the query protocol slightly. New variables for output templates ---------------------------------- General variables $\ Inhibit output of the next newline. Server template variables $HEXENWORLD True if the server is HexenWorld (use with $IF) $UNREAL True if the server is Unreal (use with $IF) Player template variables $DEATHS Number of deaths (Unreal only) $TEAMNUM Team number (Unreal only) un-Unreal support ----------------- I implemented support for the original Unreal server status protocol. However, the Unreal server worked so poorly as to be unusable. Unreal is getting improved Internet support, but it's not ready yet. When Unreal gets fixed, QStat will support the new protocol. I've also included the templates I use for the status page of my own Quake II servers. Steve, steve@activesw.com ** UPDATED for 2.1z BETA ** Feb 28, 1998 Summary of New Features ----------------------- Output templates (HTML generation) Server sorting Host name and IP address cache Support for Quake II master New Flags --------- See the documentation (qstatdoc.html) for complete details. -default server-type Set the default server type which should be one of: QS, QW, QWM, H2S, Q2, or Q2M. -Hcache file Host name cache file -sort sort-key Sort the servers by the sort-key p sort by ping g sort by game -Tserver file Server output template. Displayed once per server. -Tplayer file Player output template. Displayed once per player (if -P is used) -Theader file Header output template. Displayed once before any servers are output -Ttrailer file Trailer output template. Displayed once after all servers and players are output. -q2m Get servers from Quake II master server Summary of Enhancements ----------------------- Remove duplicate server addresses before query Reduce memory usage when -R and/or -P are not specified Work-around a memory leak in Solaris 2.5 Wait for results from all master servers before starting to query servers VMS support Release Notes ------------- This is a major new release of QStat. There is over 1400 lines of new code with all the benefits and drawbacks therein. I hope you like the features, but I really hope it's not riddled with bugs. There might be some portability issues since I only have access to Solaris, HP-UX, Irix, and Windows NT. If you have a problem compiling, please send me a note. No major new features are planned for 2.1 beyond what you see here. However, I plan to make enhancements to the sorting and template code before final release. Please tell me what is missing. Obvious deficiencies include: - No per-server output files (can't have a server list with a link to a page with details about each server). - Missing $ELSE ($IFNOT is a cheap replacement) - Flexible $IF expressions would be nice - The output template variable syntax is kinda lame: you use $(IF:RULE(email)) to test for a rule and $(RULE:email) to output the value - Can only sort on ping and game. Would like to add map, players, etc - Can't sort the player lists - Host cache administration might be nice (re-verify command) - Host cache sharing. Use file locking to allow multiple qstats to share the same host cache. Presence on the list above does not guarantee implementation! Please tell me what you want, even if it's listed above. A host name cache can take a _long_ time to build the first time (30 minutes, minimum). I will try to keep starter cache files on the QStat web site. The current cache file for servers in the QW masters contains over 1000 entries. I've included sample output templates for HTML. They are not the best HTML, but they demonstrate some of the neat tricks you can do. If you use the templates with '-R', then server rules like email and web will be output as links next to the server. If you get player status (-P), then a subtable is output with player info formatted with the correct columns for the server type. A sample command line to use the templates: qstat -f myservers.txt -Ts template/server1.html -Th template/header1.html -Tp template/player1.html -Tt template/trailer1.html > myservers.html (The template files in qstat21z.zip have a ".htm" extension) Finally, the id Quake II master has a bug (gasp!) that makes it _very_ slow to respond some times. QStat tries to work-around the bug by increasing the retry interval by 20 times while waiting for a response from a Quake II master. The retry interval is restored once all the masters have been queried. Thank you for your support. ** UPDATED for 2.0b ** Dec 29, 1997 - Fix map name for Quake II 3.07-09 The rule key changed from "map" to "mapname". If both are present, then "mapname" takes precedence. - Q2: Noticed some servers are running custom games. The rule keys for games are confusing: "gamename" Always seems to be set to "baseq2", unless a custom game is being run, in which case "gamename" is not set at all. "gamedir" Probably the same as "*gamedir" in QW. "game" Always the same as "gamedir" in the servers I stat'd. Maybe this can be used to indicate the name as well as the version of the game being run. For now, QStat displays the value of the "game" key in the non-raw output of Q2 servers. - Print file name and line number information with errors when reading files. - new option: -progress display progress meter Thanks maynard@ultra.net Leave for the Holidays and the id boys change the protocol. A minor change, but a few QStat users noticed right away. The change does not affect people using -raw. ** UPDATED for 2.0a ** Dec 9, 1997 - Support for Quake II servers, see documentation for usage. - Minor fix to connect time formatting. I don't have Quake II, but the status protocol is very similar to QuakeWorld, so I was able to figure it out by poking existing Quake II servers on the net. Id has stated that the network protocol will get an upgrade soon after the in-store release. As always, QStat will track these changes as they become available. ** UPDATED for 1.6 ** Nov 30, 1997 Changes since beta5. Minor enhancements and a few fixes. - Removed all the annoying copyright restrictions. - Added option -ne : no empty servers Thanks secabeen@fnord.rh.uchicago.edu - Added option -raw-arg : special for QStatList (see qstat docs) Thanks darkgrue@iname.com - Added option -timeout : total time in seconds before giving up Can prevent overlapping runs when cron runs qstat every five minutes. - Added gamedir to QW server output (not -raw) - Fixed bug getting long server lists from QuakeWorld masters. Can now get lists in excess of 16,000 servers. How long til that limit is exceeded? - Fixed bug using -H with -qw - Fixed use of select() so systems with large file descriptor limits can query more than 64 servers at a time. - Overhauled the web page, but it is still gif-less. QStat 1.6 has been in alpha / beta just short of a year (1.6 alpha was released Dec 20, 1996). It has been a good year. Many thanks to those who sent suggestions, kind words, and bug reports. And appologies to those who have not seen their request implemented, or who did not even receive a reply. "Mmmgufm gumerfm rerfgmmf." "That's right, Kenny, Steve is a busy guy and doesn't care about your stupid problems." "Merfgl! gumerfm rerfgmmf!" "Dammit, Kenny, turn down the computer, I can't hear you over the splattering bodies." "Gumerfm rerfgmmf." "Oh. Kenny says, Steve does care about your problems, but not the stupid ones." [ A large core dump falls on Kenny, killing him. ] "Steve killed Kenny! You bastard!" Quake II gives me an excuse to crank up the major version dial. QStat 2.0 will feature support for Quake II servers. ** UPDATED for 1.6 beta5 ** Oct 5, 1997 Kitchen sink release to add some of the most requested features. - Extended file format. The file used with the -f option can include server type information to distinguish between Hexen II, Quake, and QuakeWorld servers. You can put all your servers into one file and query them all with one qstat invocation. - Fix bug that caused a QuakeWorld master server query to never timeout. - New option -h2s for specifying a single Hexen II server to query. - New option -maxsimultaneous limits the number of simultaneous server queries. Previously configurable by modifying the MAXFD #define in qstat.h. - Updated docs [FINALLY!] - Compiles on HP-UX 10.20 - Changed packaging: files are in a directory named for the version of qstat, and use gzip instead of compress on unix - Removed pre-QuakeWorld 1.5 support. The -qw1.5 option is obsolete and no longer needed. As are the -qwuserinfo and -qwseeninfo options. - Smarter about when to include server type prefix in formatted output Unless I hear cries for more features, this will be the last beta before the final release of qstat 1.6. Now that the docs are up to date, I've run out of excuses for doing the release. ** UPDATED for 1.6 beta4 ** Sept 2, 1997 Qstat has gone through some minor revs of beta3 and the arrival of Hexen II sparked the need for another release. Summary of fixes and changes: - Support for Hexen II servers. Use the -hexen2 mode to query Hexen II servers: qstat -hexen2 -R -P 165.166.143.15 208.131.24.189 You cannot mix queries of Quake and Hexen II servers. This limitation will be fixed in the final 1.6 release. However you can mix QW and Hexen II queries in one qstat command. [Thanks to Michael Long, mlong@infoave.net, for getting me the protocol magic for Hexen II.] - In the formatted display output (as opposed to -raw), a Hexen II server is indicated with a H2S prefix. This will only be seen if qstat is using different quake protocols at the same time (eg. query a Hexen II and QW server in the same command). - Various fixes to deal with bogus packets from QW servers. The lastest one is caused when the QW server info packet is truncated. It appears that there is a fixed buffer of 1600 bytes in the QW server for building server info responses. The QW server programmers may want to consider increasing this value. On Ethernet, two packets must be used to send 1600 bytes (Ethernet frame size 1518, subtract ~50 for header, leaving ~1468 for data). Might as well set the buffer size to ~3000 bytes so two full packets can be used. - There's some wierd problem on Linux which causes bind() to occasionally fail with EADDRINUSE. Fixed the code to consider this a transient error and retry the address at a later time. If you saw messages like: bind: Address already in use send: Invalid argument from qstat, then this fixes that. No time table for the final 1.6 release. Other than updating the docs, I don't plan any significant new features. ** The docs have not yet been updated. This is all you get for now. ** ** UPDATED for 1.6 beta3 ** April 3, 1997 D'oh! Should never do releases just after midnight. Beta2 had an old win32 executable. Also forgot to check for NULL map name in the -raw display code. Sorry! ** UPDATED for 1.6 beta2 ** April 2, 1997 Features in 1.6 beta2 beyond beta1 Fix crash on negative player colors New option -qw1.5 Use the QuakeWorld 1.5 protocol. Affects the interaction with a QW master (-qw). Fortunately, the rest of the protocol hasn't changed. I imagine the user and seen info won't be available from a QW 1.5 master. The user and seen flags will remain until I have a better understanding of where QW is going. Had to fix a bunch of crashes related to QW 1.5 servers. Some of them don't have common keys like "map". ** UPDATED for 1.6 beta1 ** Feb 7, 1997 Features in 1.6 beta1 over the 1.6 alpha releases: Changes to -qw option Support for user names and user ids Defaults values ("qstat") for user id and password. Most of the QW masters have a qstat user with "qstat" as the password. New option -qws Fetch and display stats for a single QW server. New option -qwuserinfo Fetch and display user information from a QW master. New option -qwseeninfo Fetch and display user information from a QW master. bug fix Better handling of error packets from QW servers and masters. No more "huh?" Thanks to Kris Nosack (kn@byu.edu) for comments and bug reports. ** The following has been updated to reflect the changes and ** additions in 1.6 beta1. The main feature of this release is support for QuakeWorld. A couple minor bug fixes have also been made. QuakeWorld support has been integrated into qstat. You can query normal Quake servers and QuakeWorld servers at the same time. A new prefix is used to distinguish between the different server types. New options ----------- All of these options can be used together in a single run of qstat. To query QuakeWorld servers, use the -qw or -qws options. The first will get the list of QW servers from a QW master and query all of them for status information. The latter, -qws, will just query the given QW server for status information. These options are the only way to get QuakeWorld server stats. Hosts added with -f or on the command-line are treated like normal Quake servers. -qw host:port:uid:password host host name or IP address of QW Master Server port port number (defaults to 27000 if blank) uid valid QW user id or name on host password password for user To get the server list from a master server, qstat needs a valid login on the master. A default login and password can be used if the uid and password are not given. The default is "qstat" for both values. All the known masters should have a "qstat" user with the same password. -qws host:port host host name or IP address of QW server port post number (defaults to 27500 if blank) If you don't use -qw or -qws, qstat will behave identically to qstat 1.5. I use the following command to query QW servers: qstat -qw 204.50.178.66 To get QuakeWorld user information, use the -qwuserinfo and -qwseeninfo options. A QW login is not required to get user information. -qwuserinfo host:port user-list host host name or IP address of QW Master Server port port number (defaults to 27000 if blank) user-list list of QW users (ids or names) The display for a user will include everything in their record. The color name option (-ncn) is applied to 'topcolor' and 'bottomcolor'. The user-list must be _one_ command line argument; use double-quotes to enclose multiple users if they're separated with spaces. You can also use back-slashes to separate users. -qwseeninfo host:port user-list host host name or IP address of QW Master Server port port number (defaults to 27000 if blank) user-list list of QW users (ids or names) The last seen information comes directly from the master, qstat does not perform any additional formatting. The user-list should be _one_ command line argument; use double-quotes to enclose multiple users if they're separated with spaces. You can also use back-slashes to separate users. To get user information for me and qstat and find out who was last slagging me: qstat -qwuserinfo 204.50.178.66 "Act-Steve qstat" -qwseeninfo 204.50.178.66 Act-Steve Display format -------------- The QW servers return different information than the normal servers, so I had to extend the output style. The first field of a server status line is the server type: QS normal Quake server QW QW server QWM QW master server QWU QW user information QWE QW last seen information The for-human-comsumption output is pretty self-evident, but the raw output needs some explanation. The output for normal Quake servers has not changed. QWM server status fields server address, number of QW servers [the uid and password are removed before display, no rules or player info is output] QW server status server address, server name, map name, max players, current players, avg response time, number of retries [If -R is specified, a line of server rules is output. The format is the same as for normal servers: key1=value1,key2=value2] [If -P is specified, one line is output for each player.] player uid, player name, frags, connect time, shirt color, pants color, ping, skin QWU key1, value1, key2, value2, ... [The first seven keys will always be the same; name, userid, skill, efficiency, rank, frags, deaths. But I would not count on that as I'm a fickle programmer and may change my mind.] QWE seen-info [qstat just displays the string returned by the master. The user name is embedded in there, but you'll have to parse it out.] All the existing display options apply to QW servers. Run qstat by hand to better understand the new output before trying to incorporate it into your web page. Errors ------ QStat has a variety of ways of reporting errors such as time outs and server errors. If you see something between angle brackets, , that's an error message directly from a server or master. Some day I'll document how errors are displayed in the raw format. But for now I trust you web masters can figure it out Final words ----------- This version is very beta. If you would like qstat to work differently let me know. And of course, if you have problems or questions, please let me know. Known bugs ---------- qstat often fails to get all the QW server lists if multiple QW masters are specified. You'll see a TIMEOUT or "no response" from the QWM when this happens. I have a fix in mind, but I'm tired and there's beer waiting for me at home. An error is not displayed if qstat times out getting user or last seen information. Steve Jankowski steve@activesw.com --- Version 1.5 adds player info, server rules, response times, and performance improvements. A large number of flags were added to support different output formatting options. Web masters should check out the -raw option which displays all server info with your choice of delimiter. Updated copyright to be more specific about allowable use. Updated the web page with links to Quake protocol pages. Version 1.4 includes a number of new features and bug fixes. There is now support for Linux, flags to set retry timeout and interval, flags to limit output to running or not full servers. A bug was fixed which caused qstat to have a long delay the first time it was run on Windows 95/NT. Version 1.3 fixes a bug introduced in 1.2 and adds a Windows 95/NT executable. The bug caused DOWN servers to be reported multiple times. Version 1.2 fixes the bug with running out of file descriptors. qstat-2.17/COMPILE.md000066400000000000000000000012051412457473700142250ustar00rootroot00000000000000# Compilation instructions for qStat ## Linux and other GNU systems ### Build ```shall ./configure && make ``` ### Install To install qstat run ```shell make install ``` ### Configuration To see which configuration parameters can be tweaked, run ```shell ./configure --help ``` If you want to compile from GIT you need to first install `autoconf` and `automake`, then run ```shell ./autogen.sh ``` ## Windows Release build ```shell nmake /f Makefile.noauto windows ``` Debug build ```shell nmake /f Makefile.noauto windows_debug ``` ## Solaris ```shell make -f Makefile.noauto solaris ``` ## HPUX ```shell make -f Makefile.noauto hpux ``` qstat-2.17/ChangeLog000066400000000000000000000011701412457473700143660ustar00rootroot00000000000000Sun Feb 23 10:52:18 PST 2003 Added $SCORE for player score Fixed XML escaping bugs Fixed repeated rule values from UT2003 servers Fixed excessive retries to UT2003 servers that set minplayers Fixed reported # players for UT2003 server that set minplayers Changed sof2m default protocol to 2004 (SOF 1.02) Tue Jan 7 16:24:57 PST 2003 Fixed manual compile instructions in COMPILE.txt Updated RTCW master protocol from 58 to 60 Added support for All-Seeing Eye protocol (-eye) [still need to write documentation] Fixed rare divide by zero displaying ping time Added information about UT2003 master server lists to info/UT2003.txt qstat-2.17/LICENSE.txt000066400000000000000000000213061412457473700144420ustar00rootroot00000000000000 The Artistic License 2.0 Copyright (c) 2000-2006, The Perl Foundation. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble This license establishes the terms under which a given free software Package may be copied, modified, distributed, and/or redistributed. The intent is that the Copyright Holder maintains some artistic control over the development of that Package while still keeping the Package available as open source and free software. You are always permitted to make arrangements wholly outside of this license directly with the Copyright Holder of a given Package. If the terms of this license do not permit the full use that you propose to make of the Package, you should contact the Copyright Holder and seek a different licensing arrangement. Definitions "Copyright Holder" means the individual(s) or organization(s) named in the copyright notice for the entire Package. "Contributor" means any party that has contributed code or other material to the Package, in accordance with the Copyright Holder's procedures. "You" and "your" means any person who would like to copy, distribute, or modify the Package. "Package" means the collection of files distributed by the Copyright Holder, and derivatives of that collection and/or of those files. A given Package may consist of either the Standard Version, or a Modified Version. "Distribute" means providing a copy of the Package or making it accessible to anyone else, or in the case of a company or organization, to others outside of your company or organization. "Distributor Fee" means any fee that you charge for Distributing this Package or providing support for this Package to another party. It does not mean licensing fees. "Standard Version" refers to the Package if it has not been modified, or has been modified only in ways explicitly requested by the Copyright Holder. "Modified Version" means the Package, if it has been changed, and such changes were not explicitly requested by the Copyright Holder. "Original License" means this Artistic License as Distributed with the Standard Version of the Package, in its current version or as it may be modified by The Perl Foundation in the future. "Source" form means the source code, documentation source, and configuration files for the Package. "Compiled" form means the compiled bytecode, object code, binary, or any other form resulting from mechanical transformation or translation of the Source form. Permission for Use and Modification Without Distribution (1) You are permitted to use the Standard Version and create and use Modified Versions for any purpose without restriction, provided that you do not Distribute the Modified Version. Permissions for Redistribution of the Standard Version (2) You may Distribute verbatim copies of the Source form of the Standard Version of this Package in any medium without restriction, either gratis or for a Distributor Fee, provided that you duplicate all of the original copyright notices and associated disclaimers. At your discretion, such verbatim copies may or may not include a Compiled form of the Package. (3) You may apply any bug fixes, portability changes, and other modifications made available from the Copyright Holder. The resulting Package will still be considered the Standard Version, and as such will be subject to the Original License. Distribution of Modified Versions of the Package as Source (4) You may Distribute your Modified Version as Source (either gratis or for a Distributor Fee, and with or without a Compiled form of the Modified Version) provided that you clearly document how it differs from the Standard Version, including, but not limited to, documenting any non-standard features, executables, or modules, and provided that you do at least ONE of the following: (a) make the Modified Version available to the Copyright Holder of the Standard Version, under the Original License, so that the Copyright Holder may include your modifications in the Standard Version. (b) ensure that installation of your Modified Version does not prevent the user installing or running the Standard Version. In addition, the Modified Version must bear a name that is different from the name of the Standard Version. (c) allow anyone who receives a copy of the Modified Version to make the Source form of the Modified Version available to others under (i) the Original License or (ii) a license that permits the licensee to freely copy, modify and redistribute the Modified Version using the same licensing terms that apply to the copy that the licensee received, and requires that the Source form of the Modified Version, and of any works derived from it, be made freely available in that license fees are prohibited but Distributor Fees are allowed. Distribution of Compiled Forms of the Standard Version or Modified Versions without the Source (5) You may Distribute Compiled forms of the Standard Version without the Source, provided that you include complete instructions on how to get the Source of the Standard Version. Such instructions must be valid at the time of your distribution. If these instructions, at any time while you are carrying out such distribution, become invalid, you must provide new instructions on demand or cease further distribution. If you provide valid instructions or cease distribution within thirty days after you become aware that the instructions are invalid, then you do not forfeit any of your rights under this license. (6) You may Distribute a Modified Version in Compiled form without the Source, provided that you comply with Section 4 with respect to the Source of the Modified Version. Aggregating or Linking the Package (7) You may aggregate the Package (either the Standard Version or Modified Version) with other packages and Distribute the resulting aggregation provided that you do not charge a licensing fee for the Package. Distributor Fees are permitted, and licensing fees for other components in the aggregation are permitted. The terms of this license apply to the use and Distribution of the Standard or Modified Versions as included in the aggregation. (8) You are permitted to link Modified and Standard Versions with other works, to embed the Package in a larger work of your own, or to build stand-alone binary or bytecode versions of applications that include the Package, and Distribute the result without restriction, provided the result does not expose a direct interface to the Package. Items That are Not Considered Part of a Modified Version (9) Works (including, but not limited to, modules and scripts) that merely extend or make use of the Package, do not, by themselves, cause the Package to be a Modified Version. In addition, such works are not considered parts of the Package itself, and are not subject to the terms of this license. General Provisions (10) Any use, modification, and distribution of the Standard or Modified Versions is governed by this Artistic License. By using, modifying or distributing the Package, you accept this license. Do not use, modify, or distribute the Package, if you do not accept this license. (11) If your Modified Version has been derived from a Modified Version made by someone other than you, you are nevertheless required to ensure that your Modified Version complies with the requirements of this license. (12) This license does not grant you the right to use any trademark, service mark, tradename, or logo of the Copyright Holder. (13) This license includes the non-exclusive, worldwide, free-of-charge patent license to make, have made, use, offer to sell, sell, import and otherwise transfer the Package with respect to any patent claims licensable by the Copyright Holder that are necessarily infringed by the Package. If you institute patent litigation (including a cross-claim or counterclaim) against any party alleging that the Package constitutes direct or contributory patent infringement, then this Artistic License to you shall terminate on the date that such litigation is filed. (14) Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES. THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. qstat-2.17/Makefile.am000066400000000000000000000033251412457473700146540ustar00rootroot00000000000000SUBDIRS = template info CFLAGS = -Dsysconfdir=\"$(sysconfdir)\" @CFLAGS@ .PHONY: $(builddir)/.compiler_flags $(builddir)/.compiler_flags: echo '$(CFLAGS)' | cmp -s - $@ || echo '$(CFLAGS)' > $@ .PHONY: $(builddir)/.version $(builddir)/.version: ver=$$($(srcdir)/scripts/version.sh); \ echo $$ver | cmp -s - $@ || echo $$ver > $@ $(builddir)/version.h: $(builddir)/.version $(srcdir)/version.h.tmpl ver=$$(cat $<); \ file=$$(sed "s/CHANGEME/$$ver/g" $(srcdir)/version.h.tmpl); \ echo "$$file" | cmp -s - $@ || echo "$$file" > $@ clean-local: -rm -f $(builddir)/.compiler_flags $(builddir)/.version $(builddir)/version.h distclean-local: clean-local BUILT_SOURCES = \ .compiler_flags \ .version \ version.h bin_PROGRAMS = qstat qstat_DEPENDENCIES = \ $(builddir)/.compiler_flags qstat_SOURCES = \ version.h.tmpl \ version.h \ xform.c xform.h \ config.c config.h \ debug.c debug.h \ utils.c utils.h \ hcache.c \ md5.c md5.h \ qserver.c qserver.h \ qstat.c qstat.h \ template.c \ display_json.c display_json.h \ a2s.c a2s.h \ packet_manip.c packet_manip.h \ ut2004.c ut2004.h \ doom3.c doom3.h \ gps.c gps.h \ gs2.c gs2.h \ gs3.c gs3.h \ ts2.c ts2.h \ tm.c tm.h \ haze.c haze.h \ ottd.c ottd.h \ wic.c wic.h \ fl.c fl.h \ tee.c tee.h \ cube2.c cube2.h \ ts3.c ts3.h \ bfbc2.c bfbc2.h \ ventrilo.c ventrilo.h \ mumble.c mumble.h \ terraria.c terraria.h \ crysis.c crysis.h \ dirtybomb.c dirtybomb.h \ starmade.c starmade.h \ farmsim.c farmsim.h \ ksp.c ksp.h \ tf.c tf.h \ armyops.c armyops.h dist_configfiles_DATA = qstat.cfg configfilesdir = $(sysconfdir) EXTRA_DIST = CHANGES.txt COMPILE.md LICENSE.txt \ Makefile.noauto \ ChangeLog \ qstatdoc.html \ contrib.cfg \ scripts/version.sh qstat-2.17/Makefile.noauto000066400000000000000000000050711412457473700155640ustar00rootroot00000000000000## Uncomment if you have gcc #CC = gcc #CFLAGS = -Wall -g -O2 #LDFLAGS = #LDLIBS = #CFLAGS += -Dsysconfdir=\"/etc\" CFLAGS = -DDEBUG=1 -DENABLE_DUMP=1 ## NOTE: if you get errors when linking qstat (missing symbols or ## libraries), then modify LDFLAGS or LDLIBS SRC = \ utils.c \ xform.c \ config.c \ debug.c \ hcache.c \ md5.c \ qserver.c \ qstat.c \ template.c \ display_json.c \ ut2004.c \ a2s.c \ packet_manip.c \ gs3.c \ gs2.c \ gps.c \ ts2.c \ doom3.c \ tm.c \ haze.c \ wic.c \ ottd.c \ fl.c \ tee.c \ ts3.c \ bfbc2.c \ ventrilo.c \ cube2.c \ mumble.c \ terraria.c \ crysis.c \ dirtybomb.c \ starmade.c \ farmsim.c \ ksp.c \ tf.c \ armyops.c SOURCES = \ version.h \ $(SRC) OBJ = $(SRC:.c=.obj) O = $(SRC:.c=.o) SOLARIS_LIBS = -lsocket -lnsl WINDOWS_LIBS = wsock32.lib OS2_LIBS = so32dll.lib tcp32dll.lib EMX_LIBS = -lsocket ifdef MAKEDIR: # gmake: false; nmake: unused target !ifdef MAKEDIR # gmake: not seen; nmake: true # nmake specific code LINK = link.exe LFLAGS = /nologo CFLAGS = $(CFLAGS) /nologo PHONY = .phony .phony: .version: $(PHONY) call scripts/gen-version.cmd version.h: .version !else # and now the other else # gnu make targets .PHONY: .compiler_flags .compiler_flags: echo '$(CFLAGS)' | cmp -s - $@ || echo '$(CFLAGS)' > $@ .PHONY: .version .version: @ver=$$(./scripts/version.sh); \ echo "$$ver" | cmp -s - $@ || echo "$$ver" > $@ version.h: .version @ver=$$(cat .version); \ file=$$(sed "s/CHANGEME/$$ver/g" ./version.h.tmpl); \ echo "$$file" | cmp -s - $@ || echo "$$file" > $@ endif # gmake: close condition; nmake: not seen !endif : # gmake: unused target; nmake close conditional all: qstat qstat: .compiler_flags version.h $(O) $(CC) $(CFLAGS) -o qstat $(O) $(LDFLAGS) $(LDLIBS) solaris: .compiler_flags $(SOURCES) $(CC) $(CFLAGS) -o qstat $(SRC) $(LDFLAGS) $(LDLIBS) $(SOLARIS_LIBS) aix sgi freebsd macosx osx openbsd irix linux: qstat hp hpux: .compiler_flags $(SOURCES) $(CC) $(CFLAGS) -Ae -o qstat $(SRC) $(LDFLAGS) $(LDLIBS) win32: windows .c.obj: $(CC) $(CFLAGS) -c $< # windows dynamic rebuild but doesn't handle a QSTAT_VERSION change. # use the clean target to force a rebuild. qstat.exe: version.h $(OBJ) $(LINK) $(LFLAGS) $(OBJ) $(WINDOWS_LIBS) /out:$@ # windows release forced windows: $(SOURCES) $(CC) $(CFLAGS) $(SRC) /Feqstat.exe $(WINDOWS_LIBS) # windows debug forced windows_debug: $(SOURCES) $(CC) $(CFLAGS) /Zi $(SRC) /Feqstat.exe $(WINDOWS_LIBS) /link /fixed:no /incremental:no clean: -rm -f qstat core qstat.exe *.pdb .compiler_flags .version version.h $(O) $(OBJ) qstat-2.17/README.md000066400000000000000000000105571412457473700141040ustar00rootroot00000000000000# qstat QStat is a command-line program that displays information about Internet game servers. The servers are either down, non-responsive, or running a game. For servers running a game, the server name, map name, current number of players, and response time are displayed. Server rules and player information may also be displayed. ## Supported Games Games supported include Quake, QuakeWorld, Hexen II, Quake II, HexenWorld, Unreal, Half-Life, Sin, Shogo, Tribes, Tribes 2, Quake III: Arena, BFRIS, Kingpin, and Heretic II, Unreal Tournament, Soldier of Fortune, Rogue Spear, Redline, Turok II, Blood 2, Descent 3, Drakan, KISS, Nerf Arena Blast, Rally Master, Terminous, Wheel of Time, and Daikatana and many more. Note for Tribes 2: QStat only supports Tribes 2 builds numbered 22075 or higher. Note for Ghost Recon QStat only supports GhostRecon patch 1.2, 1.3, 1.4, Desert Siege, and Island Thunder. This list is currently outdated, for a full list see the output from qstat itself. The different server types can be queried simultaneously. If qStat detects that this is being done, the output is keyed by the type of server being displayed. See Display Options. The game server may be specified as an IP address or a hostname. Servers can be listed on the command-line or, with the use of the -f option, a text file. ## Display Options One line will be displayed for each server queried. The first component of the line will be the server's address as given on the command-line or the file. This can be used as a key to match input addresses to server status. Server rules and player information are displayed under the server info, indented by one tab stop. qstat supports three additional display modes: raw, templates, and XML. In raw mode, the server information is displayed using simple delimiters and no formatting. This mode is good for programs that parse and reformat QStat's output. The template mode uses text files to layout the server information within existing text. This is ideal for generating web pages. The XML mode outputs server information wrapped in simple XML tags. The raw mode is enabled using the -raw option, template output is enabled using -Ts, and XML output is enabled with -xml. ## Game Options These options select which servers to query and what game type they are running. Servers are specified by IP address (for example: 199.2.18.4) or hostname. Servers can be listed on the command-line or in a file (see option -f.) The game type of a server can be specified with its address, or a default game type can be set for all addresses that don't have a game type. The following table shows the command-line option and type strings for the supported game types. The type string is used with the -default option and in files with the -f option. ## Configuration files The games supported by qstat can be customized with configuration files. The query parameters of built-in game types can be modified and new games can be defined. For built-in game types, certain parameters can be modified. The parameters are limited to the master server protocol and master server query string. New game types can be defined as a variation on an existing game type. Most new games use a Quake 3 or Gamespy/Unreal based network engine. These games can already be queried using -q3s or -gps, but they don't have game specific details such as the correct default port, the game name, and the correct "game" or "mod" server rule. And, mostly importantly, they don't get their own game type string (e.g. q3s, rws, t2s). All of these details can be specified in the QStat config file. qstat comes with a default configuration file called 'qstat.cfg'. If this file is found in the directory where qstat is run, the file will be loaded. Configuration files can also be specified with the QSTAT_CONFIG environment variable and the -cfg command-line option. See Appendix B for a description of the configuration file format. ## Migration to Github I've been the only active maintainer for the old [qstat sourceforge repo](http://sourceforge.net/projects/qstat/) for a while now. Unfortunately I don't have full admin their and have been unable to contact the original qstat project creator [Steve Jankowski](https://sourceforge.net/u/stevejankowski/profile/) for some time, so I've decided to move the project to github. Hence for all intensive purposes https://github.com/multiplay/qstat is the new official home of the qstat repro. qstat-2.17/a2s.c000066400000000000000000000416201412457473700134510ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * New Half-Life2 query protocol * Copyright 2005 Ludwig Nussel * * Licensed under the Artistic License, see LICENSE.txt for license terms * */ #include #ifndef _WIN32 #include #include #endif #include #include #include "debug.h" #include "qstat.h" #include "packet_manip.h" #define A2S_GETCHALLENGE "\xFF\xFF\xFF\xFF\x57" #define A2S_CHALLENGERESPONSE 0x41 #define A2S_INFO "\xFF\xFF\xFF\xFF\x54Source Engine Query" #define A2S_INFORESPONSE_HL1 0x6D #define A2S_INFORESPONSE_HL2 0x49 #define A2S_PLAYER "\xFF\xFF\xFF\xFF\x55" #define A2S_PLAYERRESPONSE 0x44 #define A2S_PLAYER_INVALID_CHALLENGE "\xFF\xFF\xFF\xFF\x55\xFF\xFF\xFF\xFF" #define A2S_RULES "\xFF\xFF\xFF\xFF\x56" #define A2S_RULESRESPONSE 0x45 struct a2s_status { unsigned sent_challenge : 1; unsigned have_challenge : 1; unsigned sent_info : 1; unsigned have_info : 1; unsigned sent_player : 1; unsigned have_player : 1; unsigned sent_rules : 1; unsigned have_rules : 1; unsigned challenge; unsigned char type; }; query_status_t send_a2s_request_packet(struct qserver *server) { struct a2s_status *status = (struct a2s_status *)server->master_query_tag; debug(3, "sending info query"); if (qserver_send_initial(server, A2S_INFO, sizeof(A2S_INFO)) == SOCKET_ERROR) { return (DONE_FORCE); } status->sent_info = 1; status->type = 0; if (get_server_rules || get_player_info) { server->next_rule = ""; // trigger calling send_a2s_rule_request_packet } return (INPROGRESS); } query_status_t send_a2s_rule_request_packet(struct qserver *server) { struct a2s_status *status = (struct a2s_status *)server->master_query_tag; debug(3, "rule request"); if (!get_server_rules && !get_player_info && status->have_info) { debug(3, "force done"); return (DONE_FORCE); } if (server->retry1 < 0) { debug(3, "too may retries"); return (DONE_FORCE); } while (1) { if (!status->have_challenge) { debug(3, "sending challenge"); // Challenge Request was broken so instead we use a player request with an invalid // challenge of -1 (0xFFFFFFFF) which prompts the server to send a valid challenge // This was fixed as of the update 2009-08-26 but then subsequently broken again. //if (SOCKET_ERROR == qserver_send_initial(server, A2S_GETCHALLENGE, sizeof(A2S_GETCHALLENGE)-1)) { char buf[sizeof(A2S_PLAYER) - 1 + 4] = A2S_PLAYER; // We use a challenge of -1 to ensure compatibility with 3rd party implementations // as that's whats documented: https://developer.valvesoftware.com/wiki/Server_queries#Request_Format_5 status->challenge = -1; memcpy(buf + sizeof(A2S_PLAYER) - 1, &status->challenge, 4); if (qserver_send_initial(server, buf, sizeof(buf)) == SOCKET_ERROR) { return (SOCKET_ERROR); } status->sent_challenge = 1; break; } else if (status->sent_info && !status->have_info) { // Need to resend info due to enhanced DDoS protection // See: https://steamcommunity.com/discussions/forum/14/2974028351344359625/ char buf[sizeof(A2S_INFO) + 4] = A2S_INFO; memcpy(buf + sizeof(A2S_INFO), &status->challenge, 4); debug(3, "sending info query with challenge"); if (qserver_send_initial(server, buf, sizeof(buf)) == SOCKET_ERROR) { return (SOCKET_ERROR); } break; } else if (get_server_rules && !status->have_rules) { char buf[sizeof(A2S_RULES) - 1 + 4] = A2S_RULES; memcpy(buf + sizeof(A2S_RULES) - 1, &status->challenge, 4); debug(3, "sending rule query"); if (qserver_send_initial(server, buf, sizeof(buf)) == SOCKET_ERROR) { return (SOCKET_ERROR); } status->sent_rules = 1; break; } else if (get_player_info && !status->have_player) { char buf[sizeof(A2S_PLAYER) - 1 + 4] = A2S_PLAYER; memcpy(buf + sizeof(A2S_PLAYER) - 1, &status->challenge, 4); debug(3, "sending player query"); if (qserver_send_initial(server, buf, sizeof(buf)) == SOCKET_ERROR) { return (SOCKET_ERROR); } status->sent_player = 1; break; } else { debug(3, "timeout"); // we are probably called due to timeout, restart. status->have_challenge = 0; status->have_rules = 0; } } return (INPROGRESS); } query_status_t deal_with_a2s_packet(struct qserver *server, char *rawpkt, int pktlen) { struct a2s_status *status = (struct a2s_status *)server->master_query_tag; char *pkt = rawpkt; char buf[16]; char *str; unsigned cnt; if (server->server_name == NULL) { server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); server->n_requests++; } if (pktlen < 5) { goto out_too_short; } if (0 == memcmp(pkt, "\xFE\xFF\xFF\xFF", 4)) { // fragmented packet unsigned char pkt_index, pkt_max; unsigned int pkt_id = 1; SavedData *sdata; if (pktlen < 9) { goto out_too_short; } pkt += 4; // format: // int sequenceNumber // byte packetId // packetId format: // bits 0 - 3 = packets position in the sequence ( 0 .. N - 1 ) // bits 4 - 7 = total number of packets // sequenceId memcpy(&pkt_id, pkt, 4); debug(3, "sequenceId: %d", pkt_id); pkt += 4; // packetId if ((1 == status->type) || (200 > server->protocol_version)) { // HL1 format // The lower four bits represent the number of packets (2 to 15) and // the upper four bits represent the current packet starting with 0 pkt_max = ((unsigned char)*pkt) & 15; pkt_index = ((unsigned char)*pkt) >> 4; debug(3, "packetid[1]: 0x%hhx => idx: %hhu, max: %hhu", *pkt, pkt_index, pkt_max); pkt++; pktlen -= 9; } else if (2 == status->type) { // HL2 format // The next two bytes are: // 1. the max packets sent ( byte ) // 2. the index of this packet starting from 0 ( byte ) // 3. Size of the split ( short ) if (pktlen < 10) { goto out_too_short; } pkt_max = ((unsigned char)*pkt); pkt_index = ((unsigned char)*(pkt + 1)); debug(3, "packetid[2]: 0x%hhx => idx: %hhu, max: %hhu", *pkt, pkt_index, pkt_max); pkt += 4; pktlen -= 12; } else { malformed_packet(server, "Unable to determine packet format"); return (PKT_ERROR); } // pkt_max is the total number of packets expected // pkt_index is a bit mask of the packets received. if (server->saved_data.data == NULL) { sdata = &server->saved_data; } else { sdata = (SavedData *)calloc(1, sizeof(SavedData)); sdata->next = server->saved_data.next; server->saved_data.next = sdata; } sdata->pkt_index = pkt_index; sdata->pkt_max = pkt_max; sdata->pkt_id = pkt_id; sdata->datalen = pktlen; sdata->data = (char *)malloc(sdata->datalen); if (NULL == sdata->data) { malformed_packet(server, "Out of memory"); return (MEM_ERROR); } memcpy(sdata->data, pkt, sdata->datalen); // combine_packets will call us recursively return (combine_packets(server)); } else if (0 != memcmp(pkt, "\xFF\xFF\xFF\xFF", 4)) { malformed_packet(server, "invalid packet header"); return (PKT_ERROR); } pkt += 4; pktlen -= 4; pktlen -= 1; debug(2, "A2S type = %x", *pkt); switch (*pkt++) { case A2S_CHALLENGERESPONSE: if (pktlen < 4) { goto out_too_short; } memcpy(&status->challenge, pkt, 4); // do not count challenge as retry if (!status->have_challenge && (server->retry1 != n_retries)) { ++server->retry1; if (server->n_retries) { --server->n_retries; } } status->have_challenge = 1; debug(3, "challenge %x", status->challenge); return (send_a2s_rule_request_packet(server)); case A2S_INFORESPONSE_HL1: if (pktlen < 28) { goto out_too_short; } status->type = 1; // ip:port str = memchr(pkt, '\0', pktlen); if (!str) { goto out_too_short; } //server->server_name = strdup(pkt); pktlen -= str - pkt + 1; pkt += str - pkt + 1; // server name str = memchr(pkt, '\0', pktlen); if (!str) { goto out_too_short; } server->server_name = strdup(pkt); pktlen -= str - pkt + 1; pkt += str - pkt + 1; // map str = memchr(pkt, '\0', pktlen); if (!str) { goto out_too_short; } server->map_name = strdup(pkt); pktlen -= str - pkt + 1; pkt += str - pkt + 1; // mod dir str = memchr(pkt, '\0', pktlen); if (!str) { goto out_too_short; } server->game = strdup(pkt); add_rule(server, "gamedir", pkt, 0); pktlen -= str - pkt + 1; pkt += str - pkt + 1; // mod description str = memchr(pkt, '\0', pktlen); if (!str) { goto out_too_short; } add_rule(server, "gamename", pkt, 0); pktlen -= str - pkt + 1; pkt += str - pkt + 1; if (pktlen < 7) { goto out_too_short; } // num players server->num_players = (unsigned char)pkt[0]; // max players server->max_players = (unsigned char)pkt[1]; // version sprintf(buf, "%hhu", pkt[2]); add_rule(server, "version", buf, 0); // dedicated add_rule(server, "dedicated", pkt[3] == 'd' ? "1" : "0", 0); // os switch (pkt[4]) { case 'l': add_rule(server, "sv_os", "linux", 0); break; case 'w': add_rule(server, "sv_os", "windows", 0); break; default: buf[0] = pkt[4]; buf[1] = '\0'; add_rule(server, "sv_os", buf, 0); } // password add_rule(server, "password", pkt[5] ? "1" : "0", 0); pkt += 6; pktlen -= 6; // mod info if (pkt[0]) { pkt++; pktlen--; // mod URL str = memchr(pkt, '\0', pktlen); if (!str) { goto out_too_short; } add_rule(server, "mod_url", strdup(pkt), 0); pktlen -= str - pkt + 1; pkt += str - pkt + 1; // mod DL str = memchr(pkt, '\0', pktlen); if (!str) { goto out_too_short; } add_rule(server, "mod_dl", strdup(pkt), 0); pktlen -= str - pkt + 1; pkt += str - pkt + 1; // mod Empty str = memchr(pkt, '\0', pktlen); pktlen -= str - pkt + 1; pkt += str - pkt + 1; if (pktlen < 10) { goto out_too_short; } // mod version sprintf(buf, "%u", swap_long_from_little(pkt)); add_rule(server, "mod_ver", buf, 0); pkt += 4; pktlen -= 4; // mod size sprintf(buf, "%u", swap_long_from_little(pkt)); add_rule(server, "mod_size", buf, 0); pkt += 4; pktlen -= 4; // svonly add_rule(server, "mod_svonly", (*pkt) ? "1" : "0", 0); pkt++; pktlen--; // cldll add_rule(server, "mod_cldll", (*pkt) ? "1" : "0", 0); pkt++; pktlen--; } if (pktlen < 2) { goto out_too_short; } // Secure add_rule(server, "secure", *pkt ? "1" : "0", 0); pkt++; pktlen--; // Bots sprintf(buf, "%hhu", *pkt); add_rule(server, "bots", buf, 0); pkt++; pktlen--; status->have_info = 1; server->retry1 = n_retries; server->next_player_info = server->num_players; break; case A2S_INFORESPONSE_HL2: if (pktlen < 1) { goto out_too_short; } status->type = 2; snprintf(buf, sizeof(buf), "%hhX", *pkt); add_rule(server, "protocol", buf, 0); ++pkt; --pktlen; // server name str = memchr(pkt, '\0', pktlen); if (!str) { goto out_too_short; } server->server_name = strdup(pkt); pktlen -= str - pkt + 1; pkt += str - pkt + 1; // map str = memchr(pkt, '\0', pktlen); if (!str) { goto out_too_short; } server->map_name = strdup(pkt); pktlen -= str - pkt + 1; pkt += str - pkt + 1; // mod str = memchr(pkt, '\0', pktlen); if (!str) { goto out_too_short; } server->game = strdup(pkt); add_rule(server, "gamedir", pkt, 0); pktlen -= str - pkt + 1; pkt += str - pkt + 1; // description str = memchr(pkt, '\0', pktlen); if (!str) { goto out_too_short; } add_rule(server, "gamename", pkt, 0); pktlen -= str - pkt + 1; pkt += str - pkt + 1; if (pktlen < 9) { goto out_too_short; } // pkt[0], pkt[1] steam appid server->protocol_version = (unsigned short)*pkt; server->num_players = (unsigned char)pkt[2]; server->max_players = (unsigned char)pkt[3]; // pkt[4] number of bots sprintf(buf, "%hhu", pkt[4]); add_rule(server, "bots", buf, 0); add_rule(server, "dedicated", pkt[5] ? "1" : "0", 0); if (pkt[6] == 'l') { add_rule(server, "sv_os", "linux", 0); } else if (pkt[6] == 'w') { add_rule(server, "sv_os", "windows", 0); } else { buf[0] = pkt[6]; buf[1] = '\0'; add_rule(server, "sv_os", buf, 0); } if (pkt[7]) { snprintf(buf, sizeof(buf), "%hhu", (unsigned char)pkt[7]); add_rule(server, "password", buf, 0); } if (pkt[8]) { snprintf(buf, sizeof(buf), "%hhu", (unsigned char)pkt[8]); add_rule(server, "secure", buf, 0); } pkt += 9; pktlen -= 9; // version str = memchr(pkt, '\0', pktlen); if (!str) { goto out_too_short; } add_rule(server, "version", pkt, 0); pktlen -= str - pkt + 1; pkt += str - pkt + 1; // EDF if (1 <= pktlen) { unsigned char edf = *pkt; debug(1, "EDF: 0x%02hhx", edf); pkt++; pktlen--; if (edf & 0x80) { // game port unsigned short gameport; if (pktlen < 2) { goto out_too_short; } gameport = swap_short_from_little(pkt); sprintf(buf, "%hu", gameport); add_rule(server, "game_port", buf, 0); change_server_port(server, gameport, 0); pkt += 2; pktlen -= 2; } if (edf & 0x10) { // SteamId (long long) if (pktlen < 8) { goto out_too_short; } pkt += 8; pktlen -= 8; } if (edf & 0x40) { // spectator port unsigned short spectator_port; if (pktlen < 3) { goto out_too_short; } spectator_port = swap_short_from_little(pkt); sprintf(buf, "%hu", spectator_port); add_rule(server, "spectator_port", buf, 0); pkt += 2; pktlen -= 2; // spectator server name str = memchr(pkt, '\0', pktlen); if (!str) { goto out_too_short; } add_rule(server, "spectator_server_name", pkt, 0); pktlen -= str - pkt + 1; pkt += str - pkt + 1; } if (edf & 0x20) { // Keywords str = memchr(pkt, '\0', pktlen); if (!str) { goto out_too_short; } add_rule(server, "game_tags", pkt, 0); if (strncmp(pkt, "rust", 4) == 0) { // Rust is comma seperated tags char *keyword = strtok(pkt, ","); while (keyword != NULL) { if (strncmp(keyword, "cp", 2) == 0) { // current players override server->num_players = atoi(keyword + 2); } else if (strncmp(keyword, "mp", 2) == 0) { // max players override server->max_players = atoi(keyword + 2); } keyword = strtok(NULL, ","); } } pktlen -= str - pkt + 1; pkt += str - pkt + 1; } if (edf & 0x01) { // GameId (long long) if (pktlen < 8) { goto out_too_short; } pkt += 8; pktlen -= 8; } } status->have_info = 1; server->retry1 = n_retries; server->next_player_info = server->num_players; break; case A2S_RULESRESPONSE: if (pktlen < 2) { goto out_too_short; } cnt = (unsigned char)pkt[0] + ((unsigned char)pkt[1] << 8); pktlen -= 2; pkt += 2; debug(3, "num_rules: %d", cnt); for ( ; cnt && pktlen > 0; --cnt) { char *key, *value; str = memchr(pkt, '\0', pktlen); if (!str) { break; } key = pkt; pktlen -= str - pkt + 1; pkt += str - pkt + 1; str = memchr(pkt, '\0', pktlen); if (!str) { break; } value = pkt; pktlen -= str - pkt + 1; pkt += str - pkt + 1; add_rule(server, key, value, NO_FLAGS); } if (cnt) { malformed_packet(server, "packet contains too few rules, missing %d", cnt); server->missing_rules = 1; } if (pktlen) { malformed_packet(server, "garbage at end of rules, %d bytes left", pktlen); } status->have_rules = 1; server->retry1 = n_retries; break; case A2S_PLAYERRESPONSE: if (pktlen < 1) { goto out_too_short; } cnt = (unsigned char)pkt[0]; pktlen -= 1; pkt += 1; debug(3, "num_players: %d", cnt); for ( ; cnt && pktlen > 0; --cnt) { unsigned idx; const char *name; struct player *p; idx = *pkt++; --pktlen; str = memchr(pkt, '\0', pktlen); if (!str) { break; } name = pkt; pktlen -= str - pkt + 1; pkt += str - pkt + 1; if (pktlen < 8) { goto out_too_short; } debug(3, "player index %d = %s", idx, name); p = add_player(server, server->n_player_info); if (p) { p->name = strdup(name); p->frags = swap_long_from_little(pkt); p->connect_time = swap_float_from_little(pkt + 4); } pktlen -= 8; pkt += 8; } if (pktlen) { malformed_packet(server, "garbage at end of player info, %d bytes left", pktlen); } status->have_player = 1; // Workaround broken implementations which don't send a challenge to a player request // that hasn't seen a challenge yet. Without this we would end up in an infinite loop. if (status->have_challenge == 0) { if (show_errors) { fprintf(stderr, "server has broken challenge so is a DDoS source!\n"); } status->have_challenge = 1; } server->retry1 = n_retries; break; default: malformed_packet(server, "invalid packet id %hhx", *--pkt); return (PKT_ERROR); } if ( (!get_player_info || (get_player_info && status->have_player)) && (!get_server_rules || (get_server_rules && status->have_rules)) ) { server->next_rule = NULL; } return (DONE_AUTO); out_too_short: malformed_packet(server, "packet too short"); return (PKT_ERROR); } // vim: sw=4 ts=4 noet qstat-2.17/a2s.h000066400000000000000000000007321412457473700134550ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * New Half-Life2 query protocol * Copyright 2005 Ludwig Nussel * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_A2S_H #define QSTAT_A2S_H #include "qserver.h" query_status_t send_a2s_request_packet(struct qserver *server); query_status_t send_a2s_rule_request_packet(struct qserver *server); query_status_t deal_with_a2s_packet(struct qserver *server, char *rawpkt, int pktlen); #endif qstat-2.17/armyops.c000066400000000000000000000006771412457473700144650ustar00rootroot00000000000000/* * qstat * * Armyops Protocol * Copyright 2005 Omnix * * Licensed under the Artistic License, see LICENSE.txt for license terms * */ #include "qstat.h" void json_display_armyops_player_info(struct qserver *server) { struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { player->score = calculate_armyops_score(player); } json_display_player_info(server); } // vim: sw=4 ts=4 noet qstat-2.17/armyops.h000066400000000000000000000004661412457473700144660ustar00rootroot00000000000000/* * qstat * * Armyops Protocol * Copyright 2005 Omnix * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_ARMOPS_H #define QSTAT_ARMOPS_H int calculate_armyops_score(struct player *player); void json_display_armyops_player_info(struct qserver *server); #endif qstat-2.17/autogen.sh000077500000000000000000000031311412457473700146140ustar00rootroot00000000000000#!/bin/sh # Global variables... AUTOCONF="autoconf" AUTOHEADER="autoheader" AUTOM4TE="autom4te" AUTOMAKE="automake" ACLOCAL="aclocal" # Please add higher versions first. The last version number is the minimum # needed to compile KDE. Do not forget to include the name/version # # separator if one is present, e.g. -1.2 where - is the separator. AUTOCONF_VERS="-2.61 261 -2.59 259 -2.58 258 -2.57 257 -2.54 254 -2.53 253" AUTOMAKE_VERS="-1.9 19 -1.8 18 -1.7 17 -1.6 16 -1.5 15" # We don't use variable here for remembering the type ... strings. Local # variables are not that portable, but we fear namespace issues with our # includer. check_autoconf() { echo "Checking autoconf version..." for ver in $AUTOCONF_VERS; do if test -x "`$WHICH $AUTOCONF$ver 2>/dev/null`"; then AUTOCONF="`$WHICH $AUTOCONF$ver`" AUTOHEADER="`$WHICH $AUTOHEADER$ver`" AUTOM4TE="`$WHICH $AUTOM4TE$ver`" break fi done } check_automake() { echo "Checking automake version..." for ver in $AUTOMAKE_VERS; do if test -x "`$WHICH $AUTOMAKE$ver 2>/dev/null`"; then AUTOMAKE="`$WHICH $AUTOMAKE$ver`" ACLOCAL="`$WHICH $ACLOCAL$ver`" break fi done if test -n "$UNSERMAKE"; then AUTOMAKE="$UNSERMAKE" fi } check_which() { WHICH="" for i in "type -p" "which" "type" ; do T=`$i sh 2> /dev/null` test -x "$T" && WHICH="$i" && break done } check_which check_autoconf check_automake export AUTOCONF AUTOHEADER AUTOM4TE AUTOMAKE ACLOCAL set -e echo "Running aclocal..." $ACLOCAL echo "Running autoconf..." $AUTOCONF echo "Running autoheader..." $AUTOHEADER echo "Running automake..." $AUTOMAKE -a --foreign qstat-2.17/bfbc2.c000066400000000000000000000047711412457473700137500ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * Battlefield Bad Company 2 query protocol * Copyright 2009 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms * */ #include #ifndef _WIN32 #include #include #include #else #include #endif #include #include #include #include "debug.h" #include "qstat.h" #include "packet_manip.h" query_status_t send_bfbc2_request_packet(struct qserver *server) { char buf[50]; int size = 0; switch (server->challenge) { case 0: // Initial connect send serverInfo size = 27; memcpy(buf, "\x00\x00\x00\x00\x1b\x00\x00\x00\x01\x00\x00\x00\x0a\x00\x00\x00serverInfo\x00", size); break; case 1: // All Done send quit size = 21; memcpy(buf, "\x01\x00\x00\x00\x15\x00\x00\x00\x01\x00\x00\x00\x04\x00\x00\x00quit\x00", size); break; case 2: return (DONE_FORCE); } debug(3, "send_bfbc2_request_packet: state = %ld", server->challenge); return (send_packet(server, buf, size)); } query_status_t deal_with_bfbc2_packet(struct qserver *server, char *rawpkt, int pktlen) { char *s, *end, *crlf; int words, word = 0; debug(2, "processing..."); if (17 > pktlen) { // Invalid packet return (REQ_ERROR); } rawpkt[pktlen - 1] = '\0'; end = &rawpkt[pktlen - 1]; s = rawpkt; server->ping_total = time_delta(&packet_recv_time, &server->packet_time1); server->n_requests++; // Header Sequence s += 4; // Packet Size s += 4; // Num Words words = *(int *)s; s += 4; // Words while (words > 0 && s + 5 < end) { // Size int ws = *(int *)s; s += 4; // Content debug(6, "word: %s\n", s); switch (word) { case 0: // Status break; case 1: // Server Name // prevent CR & LF in the server name crlf = strchr(s, '\015'); if (NULL != crlf) { *crlf = '\0'; } crlf = strchr(s, '\012'); if (NULL != crlf) { *crlf = '\0'; } server->server_name = strdup(s); break; case 2: // Player Count server->num_players = atoi(s); break; case 3: // Max Players server->max_players = atoi(s); break; case 4: // Game Mode add_rule(server, "gametype", s, NO_FLAGS); break; case 5: // Map server->map_name = strdup(s); break; } word++; s += ws + 1; words--; } server->challenge++; gettimeofday(&server->packet_time1, NULL); if (1 == server->challenge) { send_bfbc2_request_packet(server); return (INPROGRESS); } return (DONE_FORCE); } qstat-2.17/bfbc2.h000066400000000000000000000006761412457473700137550ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * Battlefield Bad Company 2 protocol * Copyright 2005 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_BFBC2_H #define QSTAT_BFBC2_H #include "qserver.h" // Packet processing methods query_status_t deal_with_bfbc2_packet(struct qserver *server, char *pkt, int pktlen); query_status_t send_bfbc2_request_packet(struct qserver *server); #endif qstat-2.17/config.c000066400000000000000000000604401412457473700142320ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * steve@qstat.org * http://www.qstat.org * * Copyright 1996,1997,1998,1999 by Steve Jankowski * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #include #include #include #include #ifndef _WIN32 #include #include #endif #include "config.h" #ifdef __hpux #define STATIC static #else #define STATIC #endif #ifdef _WIN32 #define strcasecmp stricmp #endif #define CONFIG_FILE "qstat.cfg" #ifdef _WIN32 #define HOME_CONFIG_FILE CONFIG_FILE #define SEP "\\" #else #define HOME_CONFIG_FILE ".qstatrc" #define SEP "/" #endif static server_type **config_types; static int n_config_types; static int max_config_types; static int next_gametype_id = LAST_BUILTIN_SERVER + 1; static int load_config_file(char const *filename); static int try_load_config_file(char const *filename, int const show_error); static int pf_top_level(char *text, void *context); static int pf_gametype_new(char *text, void *context); static int pf_gametype_modify(char *text, void *context); static int set_game_type_value(server_type *gametype, int key, char *value); static int modify_game_type_value(server_type *gametype, int key, char *value); static int set_packet_value(server_type *gametype, char const *value, char const *packet_name, char **packet, int *len); static char *first_token(char *text); static char *next_token(); static char *next_token_dup(); static char *next_value(); static char *parse_value(char *source, int len); static unsigned int parse_hex(char *hex, int len, int *error); static unsigned int parse_oct(char *oct, int len, int *error); static char *force_lower_case(char *string); static char *force_upper_case(char *string); static char *get_token(); static void *memdup(const void *mem, unsigned int len); int parse_config_file(FILE *file); static int (*parse_func)(char *, void *); static void *parse_context; static int line; static char const *current_file_name; static char *parse_text; static char *parse_end; static char *lex; static char *token_end; static char token_buf[1024]; static int value_len; static int debug = 0; #define REPORT_ERROR(a) print_location(); fprintf a; fprintf(stderr, "\n") enum { CK_NONE = 0, CK_MASTER_PROTOCOL, CK_MASTER_QUERY, CK_MASTER_PACKET, CK_FLAGS, CK_NAME, CK_DEFAULT_PORT, CK_GAME_RULE, CK_TEMPLATE_VAR, CK_MASTER_TYPE, CK_STATUS_PACKET, CK_STATUS2_PACKET, CK_PLAYER_PACKET, CK_RULE_PACKET, CK_PORT_OFFSET }; typedef struct _config_key { int key; char *key_name; } ConfigKey; static ConfigKey const new_keys[] = { { CK_MASTER_PROTOCOL, "master protocol" }, { CK_MASTER_QUERY, "master query" }, { CK_MASTER_PACKET, "master packet" }, { CK_FLAGS, "flags" }, { CK_NAME, "name" }, { CK_DEFAULT_PORT, "default port" }, { CK_GAME_RULE, "game rule" }, { CK_TEMPLATE_VAR, "template var" }, { CK_MASTER_TYPE, "master for gametype" }, { CK_STATUS_PACKET, "status packet" }, { CK_STATUS2_PACKET, "status2 packet" }, { CK_PLAYER_PACKET, "player packet" }, { CK_RULE_PACKET, "rule packet" }, { CK_PORT_OFFSET, "status port offset" }, { 0, NULL }, }; static ConfigKey const modify_keys[] = { { CK_MASTER_PROTOCOL, "master protocol" }, { CK_MASTER_QUERY, "master query" }, { CK_MASTER_PACKET, "master packet" }, { CK_FLAGS, "flags" }, { CK_MASTER_TYPE, "master for gametype" }, { 0, NULL }, }; typedef struct { const char *name; int value; } ServerFlag; #define SERVER_FLAG(x) { # x, x } ServerFlag const server_flags[] = { SERVER_FLAG(TF_SINGLE_QUERY), SERVER_FLAG(TF_OUTFILE), SERVER_FLAG(TF_MASTER_MULTI_RESPONSE), SERVER_FLAG(TF_TCP_CONNECT), SERVER_FLAG(TF_QUERY_ARG), SERVER_FLAG(TF_QUERY_ARG_REQUIRED), SERVER_FLAG(TF_QUAKE3_NAMES), SERVER_FLAG(TF_TRIBES2_NAMES), SERVER_FLAG(TF_SOF_NAMES), SERVER_FLAG(TF_U2_NAMES), SERVER_FLAG(TF_RAW_STYLE_QUAKE), SERVER_FLAG(TF_RAW_STYLE_TRIBES), SERVER_FLAG(TF_RAW_STYLE_GHOSTRECON), SERVER_FLAG(TF_NO_PORT_OFFSET), SERVER_FLAG(TF_SHOW_GAME_PORT), { NULL, 0 } }; #undef SERVER_FLAG static int get_config_key(char *first_token, const ConfigKey *keys); static void add_config_type(server_type *gametype); static server_type *get_config_type(char *game_type); static void copy_server_type(server_type *dest, server_type *source); static server_type *get_server_type(char *game_type); static server_type *get_builtin_type(char *game_type); typedef struct _gametype_context { char *type; char *extend_type; server_type *gametype; } GameTypeContext; static void print_location() { fprintf(stderr, "%s line %d: ", current_file_name, line); } /* * 1. $QSTAT_CONFIG * 2. UNIX: $HOME/.qstatrc WIN: $HOME/qstat.cfg * 3. UNIX: sysconfdir/qstat.cfg WIN: qstat.exe-dir/qstat.cfg */ int qsc_load_default_config_files() { int rc = 0; char *filename = NULL, *var; char path[PATH_MAX]; var = getenv("QSTAT_CONFIG"); if ((var != NULL) && (var[0] != '\0')) { rc = try_load_config_file(var, 1); if ((rc == 0) || (rc == -1)) { return (rc); } } var = getenv("HOME"); if ((var != NULL) && (var[0] != '\0')) { int len = strlen(var) + 1; if (len > PATH_MAX - strlen(HOME_CONFIG_FILE) + 2) { fprintf(stderr, "Path for HOME \"%s\" too long\n", var); return (-1); } strncpy(path, var, len); strcat(path, SEP HOME_CONFIG_FILE); rc = try_load_config_file(path, 0); if ((rc == 0) || (rc == -1)) { return (rc); } } #ifdef sysconfdir strcpy(path, sysconfdir SEP CONFIG_FILE); filename = path; #elif defined(_WIN32) // Look in the binaries directory rc = GetModuleFileName(NULL, path, PATH_MAX); if (rc == PATH_MAX) { fprintf(stderr, "Module path too long\n"); return (1); } var = strrchr(path, '\\'); if (var == NULL) { fprintf(stderr, "Unexpected module path \"%s\" (no seperator %s)\n", path, SEP); return (-1); } *var = '\0'; if (strlen(path) >= PATH_MAX - 11) { fprintf(stderr, "Module path \"%s\" too long\n", path); return (-1); } strcat(path, SEP CONFIG_FILE); filename = path; #endif if (filename != NULL) { rc = try_load_config_file(filename, 0); } return (rc); } int qsc_load_config_file(char const *filename) { int rc = load_config_file(filename); if (rc == -2) { perror(filename); fprintf(stderr, "Could not open config file \"%s\"\n", filename); return (-1); } return (rc); } server_type ** qsc_get_config_server_types(int *n_config_types_ref) { if (n_config_types_ref) { *n_config_types_ref = n_config_types; } return (config_types); } STATIC int try_load_config_file(char const *filename, int const show_error) { int rc = load_config_file(filename); if ((rc == -2) && show_error) { perror(filename); fprintf(stderr, "Error: Could not open config file \"%s\"\n", filename); } else if (rc == -1) { fprintf(stderr, "Error: Could not load config file \"%s\"\n", filename); } return (rc); } STATIC int load_config_file(char const *filename) { FILE *file; int rc; file = fopen(filename, "r"); if (file == NULL) { return (-2); } current_file_name = filename; rc = parse_config_file(file); fclose(file); return (rc); } int parse_config_file(FILE *file) { char buf[4096]; int rc; line = 0; parse_func = pf_top_level; while (fgets(buf, sizeof(buf), file) != NULL) { int len = strlen(buf); while (len && (buf[len - 1] == '\n' || buf[len - 1] == '\r')) { len--; } buf[len] = '\0'; line++; rc = parse_func(buf, parse_context); if (rc != 0) { return (rc); } } return (0); } /* Top level * Keywords: gametype */ STATIC int pf_top_level(char *text, void *_context) { GameTypeContext *context; server_type *extend; char *token, *game_type, *extend_type; token = first_token(text); if (token == NULL) { return (0); } if (strcmp(token, "gametype") != 0) { REPORT_ERROR((stderr, "Unknown config command \"%s\"", token)); return (-1); } game_type = next_token_dup(); if (game_type == NULL) { REPORT_ERROR((stderr, "Missing game type")); return (-1); } force_lower_case(game_type); token = next_token(); if (token == NULL) { REPORT_ERROR((stderr, "Expecting \"new\" or \"modify\"")); return (-1); } if (strcmp(token, "new") == 0) { parse_func = pf_gametype_new; } else if (strcmp(token, "modify") == 0) { parse_func = pf_gametype_modify; } else { REPORT_ERROR((stderr, "Expecting \"new\" or \"modify\"")); return (-1); } context = (GameTypeContext *)malloc(sizeof(GameTypeContext)); context->type = game_type; context->extend_type = NULL; context->gametype = NULL; token = next_token(); if (parse_func == pf_gametype_modify) { if (token != NULL) { REPORT_ERROR((stderr, "Extra text after gametype modify")); return (-1); } context->gametype = get_server_type(game_type); if (context->gametype == NULL) { REPORT_ERROR((stderr, "Unknown game type \"%s\"", game_type)); free(context); return (-1); } parse_context = context; return (0); } if ((token == NULL) || (strcmp(token, "extend") != 0)) { REPORT_ERROR((stderr, "Expecting \"extend\"")); return (-1); } extend_type = next_token(); if (extend_type == NULL) { REPORT_ERROR((stderr, "Missing extend game type")); return (-1); } force_lower_case(extend_type); if (strcasecmp(extend_type, game_type) == 0) { REPORT_ERROR((stderr, "Identical game type and extend type")); return (-1); } context->extend_type = extend_type; extend = get_server_type(extend_type); if (extend == NULL) { REPORT_ERROR((stderr, "Unknown extend game type \"%s\"", extend_type)); return (-1); } /* Over-write a previous gametype new */ context->gametype = get_config_type(game_type); if (context->gametype == NULL) { context->gametype = (server_type *)malloc(sizeof(server_type)); } copy_server_type(context->gametype, extend); /* Set flag for new type-id if not re-defining previous config type */ if (get_config_type(game_type) == NULL) { context->gametype->id = 0; } context->gametype->type_string = game_type; context->gametype->type_prefix = strdup(game_type); force_upper_case(context->gametype->type_prefix); context->gametype->type_option = (char *)malloc(strlen(game_type) + 2); context->gametype->type_option[0] = '-'; strcpy(&context->gametype->type_option[1], game_type); parse_context = context; return (0); } STATIC int pf_gametype_modify(char *text, void *_context) { GameTypeContext *context = (GameTypeContext *)_context; char *token; int key; token = first_token(text); if (token == NULL) { return (0); } if (strcmp(token, "end") == 0) { parse_func = pf_top_level; parse_context = NULL; return (0); } key = get_config_key(token, modify_keys); token = next_token(); if (strcmp(token, "=") != 0) { REPORT_ERROR((stderr, "Expecting \"=\", found \"%s\"", token)); return (-1); } token = next_value(); if (token == NULL) { REPORT_ERROR((stderr, "Missing value after \"=\"")); return (-1); } if (debug) { printf("%d %s = <%s>\n", key, modify_keys[key - 1].key_name, token ? token : ""); } return (modify_game_type_value(context->gametype, key, token)); } STATIC int pf_gametype_new(char *text, void *_context) { GameTypeContext *context = (GameTypeContext *)_context; char *token; int key; token = first_token(text); if (token == NULL) { return (0); } if (strcmp(token, "end") == 0) { add_config_type(context->gametype); parse_func = pf_top_level; parse_context = NULL; return (0); } key = get_config_key(token, new_keys); if (key <= 0) { return (key); } token = next_token(); if (strcmp(token, "=") != 0) { REPORT_ERROR((stderr, "Expecting \"=\", found \"%s\"", token)); return (-1); } token = next_value(); if ((token == NULL) && ((key != CK_MASTER_PROTOCOL) && (key != CK_MASTER_QUERY))) { REPORT_ERROR((stderr, "Missing value after \"=\"")); return (-1); } if (debug) { printf("%d %s = <%s>\n", key, new_keys[key - 1].key_name, token ? token : ""); } return (set_game_type_value(context->gametype, key, token)); } STATIC int get_config_key(char *first_token, const ConfigKey *keys) { char key_name[1024], *token; int key = 0; strcpy(key_name, first_token); do { int k; for (k = 0; keys[k].key_name; k++) { if (strcmp(keys[k].key_name, key_name) == 0) { key = keys[k].key; break; } } if (key) { break; } token = next_token(); if ((token == NULL) || (strcmp(token, "=") == 0)) { break; } if (strlen(key_name) + strlen(token) > sizeof(key_name) - 2) { REPORT_ERROR((stderr, "Key name too long")); return (-1); } strcat(key_name, " "); strcat(key_name, token); } while (1); if (key == 0) { REPORT_ERROR((stderr, "Unknown config key \"%s\"", key_name)); return (-1); } return (key); } STATIC int get_server_flag_value(const char *value, unsigned len) { int i = 0; for (i = 0; server_flags[i].name; ++i) { if ((len == strlen(server_flags[i].name)) && (strncmp(server_flags[i].name, value, len) == 0)) { return (server_flags[i].value); } } return (-1); } STATIC int parse_server_flags(const char *value) { int val = 0, v, first = 1; const char *s = value; const char *e; while (*s) { while (isspace((unsigned char)*s)) { ++s; } if (!first) { if (*s != '|') { REPORT_ERROR((stderr, "Syntax error: expecting |")); val = -1; break; } ++s; while (isspace((unsigned char)*s)) { ++s; } } else { first = 0; } e = s; while (isalnum((unsigned char)*e) || *e == '_') { ++e; } if (e == s) { REPORT_ERROR((stderr, "Syntax error: expecting flag")); val = -1; break; } v = get_server_flag_value(s, e - s); if (v == -1) { REPORT_ERROR((stderr, "Syntax error: invalid flag")); val = -1; break; } s = e; val |= v; } return (val); } STATIC int set_game_type_value(server_type *gametype, int key, char *value) { switch (key) { case CK_NAME: gametype->game_name = strdup(value); break; case CK_FLAGS: { int flags = parse_server_flags(value); if (flags == -1) { return (-1); } gametype->flags = flags; } break; case CK_DEFAULT_PORT: { unsigned short port; if (sscanf(value, "%hu", &port) != 1) { REPORT_ERROR((stderr, "Syntax error on port. Should be a number between 1 and 65535.")); return (-1); } gametype->default_port = port; break; } case CK_GAME_RULE: gametype->game_rule = strdup(value); break; case CK_TEMPLATE_VAR: gametype->template_var = strdup(value); force_upper_case(gametype->template_var); break; case CK_MASTER_PROTOCOL: if (!gametype->master) { REPORT_ERROR((stderr, "Cannot set master protocol on non-master game type\n")); return (-1); } if (value) { gametype->master_protocol = strdup(value); } else { gametype->master_protocol = NULL; } break; case CK_MASTER_QUERY: if (!gametype->master) { REPORT_ERROR((stderr, "Cannot set master query on non-master game type\n")); return (-1); } if (value) { gametype->master_query = strdup(value); } else { gametype->master_query = NULL; } break; case CK_MASTER_TYPE: { server_type *type; if (!gametype->master) { REPORT_ERROR((stderr, "Cannot set master type on non-master game type\n")); return (-1); } force_lower_case(value); type = get_server_type(value); if (type == NULL) { REPORT_ERROR((stderr, "Unknown server type \"%s\"\n", value)); return (-1); } gametype->master = type->id; } break; case CK_MASTER_PACKET: if (!gametype->master) { REPORT_ERROR((stderr, "Cannot set master packet on non-master game type")); return (-1); } if ((value == NULL) || (value[0] == '\0')) { REPORT_ERROR((stderr, "Empty master packet")); return (-1); } gametype->master_packet = memdup(value, value_len); gametype->master_len = value_len; break; case CK_STATUS_PACKET: return (set_packet_value(gametype, value, "status", &gametype->status_packet, &gametype->status_len)); case CK_STATUS2_PACKET: return (set_packet_value(gametype, value, "status2", &gametype->rule_packet, &gametype->rule_len)); case CK_PLAYER_PACKET: return (set_packet_value(gametype, value, "player", &gametype->player_packet, &gametype->player_len)); case CK_RULE_PACKET: return (set_packet_value(gametype, value, "rule", &gametype->rule_packet, &gametype->rule_len)); case CK_PORT_OFFSET: { short port; if (sscanf(value, "%hd", &port) != 1) { REPORT_ERROR((stderr, "Syntax error on port. Should be a number between 32767 and -32768.")); return (-1); } gametype->port_offset = port; break; } } return (0); } STATIC int set_packet_value(server_type *gametype, char const *value, char const *packet_name, char **packet, int *len) { if (gametype->master) { REPORT_ERROR((stderr, "Cannot set info packet on master game type")); return (-1); } if ((value == NULL) || (value[0] == '\0')) { REPORT_ERROR((stderr, "Empty %s packet", packet_name)); return (-1); } // Removed as this doesn't seem to be any reason why we can't do this and it works just fine for warsow //if ( *packet == NULL) { // REPORT_ERROR((stderr, "Cannot set %s packet; extend game type does not define a %s packet", packet_name, packet_name)); // return -1; //} *packet = memdup(value, value_len); *len = value_len; return (0); } STATIC int modify_game_type_value(server_type *gametype, int key, char *value) { switch (key) { case CK_FLAGS: { int flags = parse_server_flags(value); if (flags == -1) { return (-1); } gametype->flags = flags; } break; case CK_MASTER_PROTOCOL: if (!gametype->master) { REPORT_ERROR((stderr, "Cannot set master protocol on non-master game type")); return (-1); } if (value) { gametype->master_protocol = strdup(value); } else { gametype->master_protocol = NULL; } break; case CK_MASTER_QUERY: if (!gametype->master) { REPORT_ERROR((stderr, "Cannot set master query on non-master game type")); return (-1); } if (value) { gametype->master_query = strdup(value); } else { gametype->master_query = NULL; } break; case CK_MASTER_PACKET: if (!gametype->master) { REPORT_ERROR((stderr, "Cannot set master packet on non-master game type")); return (-1); } if ((value == NULL) || (value[0] == '\0')) { REPORT_ERROR((stderr, "Empty master packet")); return (-1); } gametype->master_packet = memdup(value, value_len); gametype->master_len = value_len; break; case CK_MASTER_TYPE: { server_type *type; if (!gametype->master) { REPORT_ERROR((stderr, "Cannot set master type on non-master game type\n")); return (-1); } force_lower_case(value); type = get_server_type(value); if (type == NULL) { REPORT_ERROR((stderr, "Unknown server type \"%s\"\n", value)); return (-1); } gametype->master = type->id; } break; } return (0); } STATIC server_type * get_server_type(char *game_type) { server_type *result; result = get_builtin_type(game_type); if (result != NULL) { return (result); } return (get_config_type(game_type)); } STATIC server_type * get_builtin_type(char *type_string) { server_type *type = &builtin_types[0]; for ( ; type->id != Q_UNKNOWN_TYPE; type++) { if (strcmp(type->type_string, type_string) == 0) { return (type); } } return (NULL); } STATIC void add_config_type(server_type *gametype) { if (gametype->id == 0) { if (next_gametype_id >= MASTER_SERVER) { REPORT_ERROR((stderr, "Exceeded game type limit, ignoring \"%s\"", gametype->type_string)); return; } gametype->id = next_gametype_id; if (gametype->master) { gametype->id |= MASTER_SERVER; } next_gametype_id++; } if (max_config_types == 0) { max_config_types = 4; config_types = (server_type **)malloc(sizeof(server_type *) * (max_config_types + 1)); } else if (n_config_types >= max_config_types) { max_config_types *= 2; config_types = (server_type **)realloc(config_types, sizeof(server_type *) * (max_config_types + 1)); } config_types[n_config_types] = gametype; n_config_types++; } STATIC server_type * get_config_type(char *game_type) { int i; for (i = 0; i < n_config_types; i++) { if (strcmp(config_types[i]->type_string, game_type) == 0) { return (config_types[i]); } } return (NULL); } STATIC void copy_server_type(server_type *dest, server_type *source) { *dest = *source; } STATIC void * memdup(const void *mem, unsigned int len) { void *result = malloc(len); if (NULL == result) { REPORT_ERROR((stderr, "Failed to malloc %d bytes of memory", len)); return (NULL); } memcpy(result, mem, len); return (result); } /* Parsing primitives */ STATIC char * first_token(char *text) { parse_text = text; parse_end = parse_text + strlen(parse_text); lex = text; token_end = text; if (*lex == '#') { return (NULL); } return (get_token()); } STATIC char * next_token() { lex = token_end; return (get_token()); } STATIC char * next_token_dup() { return (strdup(next_token())); } STATIC char * get_token() { char *token = &token_buf[0]; while (isspace((unsigned char)*token_end)) { token_end++; } if (token_end == parse_end) { return (NULL); } while ((isalnum((unsigned char)*token_end) || *token_end == '.' || *token_end == '_' || *token_end == '-') && token < &token_buf[0] + sizeof(token_buf)) { *token++ = *token_end++; } if (token == &token_buf[0]) { *token++ = *token_end++; } *token = '\0'; return (&token_buf[0]); } STATIC char * next_value() { char *token = &token_buf[0]; while (isspace((unsigned char)*token_end)) { token_end++; } if (token_end == parse_end) { return (NULL); } while (token_end < parse_end && token < &token_buf[0] + sizeof(token_buf)) { *token++ = *token_end++; } *token-- = '\0'; if (strchr(token_buf, '\\')) { return (parse_value(token_buf, (token - token_buf) + 1)); } while (isspace((unsigned char)*token)) { *token-- = '\0'; } value_len = token - token_buf + 1; return (&token_buf[0]); } STATIC char * parse_value(char *source, int len) { char *value, *v, *end; int error = 0; value = (char *)malloc(len + 1); end = v = value; /* * printf( "parse_value <%.*s>\n", len, source); */ for ( ; len; len--, source++) { if (*source != '\\') { *v++ = *source; if (*source != ' ') { end = v; } continue; } source++; len--; if (len == 0) { break; } if (*source == '\\') { *v++ = *source; } else if (*source == 'n') { *v++ = '\n'; } else if (*source == 'r') { *v++ = '\r'; } else if (*source == ' ') { *v++ = ' '; } else if (*source == 'x') { source++; len--; if (len < 2) { break; } *v++ = parse_hex(source, 2, &error); if (error) { break; } source++; len--; } else if (isdigit((unsigned char)*source)) { if (len < 3) { break; } *v++ = parse_oct(source, 3, &error); if (error) { break; } source++; len--; source++; len--; } else { error = 1; REPORT_ERROR((stderr, "Invalid character escape \"%.*s\"", 2, source - 1)); break; } end = v; } if (error) { free(value); return (NULL); } value_len = end - value; memcpy(token_buf, value, value_len); token_buf[value_len] = '\0'; free(value); return (&token_buf[0]); } STATIC unsigned int parse_hex(char *hex, int len, int *error) { char *save_hex = hex; int save_len = len; unsigned int result = 0; *error = 0; for ( ; len; len--, hex++) { result <<= 4; if (!isxdigit((unsigned char)*hex)) { *error = 1; REPORT_ERROR((stderr, "Invalid hex \"%.*s\"", save_len + 2, save_hex - 2)); return (0); } if ((*hex >= '0') && (*hex <= '9')) { result |= *hex - '0'; } else if ((*hex >= 'A') && (*hex <= 'F')) { result |= ((*hex - 'A') + 10); } else if ((*hex >= 'a') && (*hex <= 'f')) { result |= ((*hex - 'a') + 10); } } return (result); } STATIC unsigned int parse_oct(char *oct, int len, int *error) { char *save_oct = oct; int save_len = len; unsigned int result = 0; *error = 0; for ( ; len; len--, oct++) { result <<= 3; if ((*oct >= '0') && (*oct <= '7')) { result |= *oct - '0'; } else { *error = 1; REPORT_ERROR((stderr, "Invalid octal \"%.*s\"", save_len + 1, save_oct - 1)); return (0); } } return (result); } STATIC char * force_lower_case(char *string) { char *s = string; for ( ; *s; s++) { *s = tolower(*s); } return (string); } STATIC char * force_upper_case(char *string) { char *s = string; for ( ; *s; s++) { *s = toupper(*s); } return (string); } qstat-2.17/config.h000066400000000000000000000004651412457473700142400ustar00rootroot00000000000000/* * config.h * by Steve Jankowski * steve@qstat.org * http://www.qstat.org * * Copyright 1996,1997,1998,1999 by Steve Jankowski */ #include "qstat.h" int qsc_load_default_config_files(); int qsc_load_config_file(char const *filename); server_type **qsc_get_config_server_types(int *n_config_types); qstat-2.17/configure.ac000066400000000000000000000050721412457473700151070ustar00rootroot00000000000000AC_INIT([qstat], m4_esyscmd([./scripts/version.sh | tr -d '\n']),[https://github.com/multiplay/qstat/issues]) AC_CONFIG_SRCDIR([qstat.c]) AM_CONFIG_HEADER([gnuconfig.h]) AC_PREREQ(2.53) AC_CANONICAL_HOST AM_INIT_AUTOMAKE([1.6 foreign]) dnl Checks for programs. AC_PROG_CC dnl Checks for header files. AC_HEADER_STDC AC_CHECK_HEADER(sys/mman.h, [have_mman_h=yes]) case $host in *mingw32*) AC_MSG_NOTICE([compiling for $host, adding -lwsock32]) LIBS="$LIBS -lwsock32" ;; esac AC_CHECK_FUNCS([strndup], [HAVE_STRNDUP=yes], [HAVE_STRNDUP=no]) AM_CONDITIONAL(NEED_STRNDUP, [test x$HAVE_STRNDUP = xno]) dnl Check for strnstr including broken one on MacOSX 10.4 which crashes dnl AC_CACHE_CHECK(for strnstr, ac_cv_func_strnstr, AC_TRY_RUN([ #include #include #include // we expect this to succeed, or crash on over-run. // if it passes otherwise we may need a better check. int main(int argc, char **argv) { int size = 20; char *str = malloc(size); memset(str, 'x', size); strnstr(str, "fubar", size); return 0; } ],ac_cv_func_strnstr="yes",ac_cv_func_strnstr="no") ) if test "$ac_cv_func_strnstr" = "yes" ; then AC_DEFINE(HAVE_STRNSTR,1,[Working strnstr]) else AC_DEFINE(HAVE_STRNSTR,0,[No or broken strnstr]) fi dnl check if user wants debug AC_MSG_CHECKING([whether to enable optimization]) AC_ARG_ENABLE(optimize,[ --disable-optimize turn off optimization]) if test x$enable_optimize != xno; then AC_MSG_RESULT([yes]) else CPPFLAGS="" CFLAGS="-ggdb" AC_MSG_RESULT([no]) fi dnl check if user wants debug AC_MSG_CHECKING([whether to enable debug output]) AC_ARG_ENABLE(debug,[ --disable-debug turn off debugging code]) if test x$enable_debug != xno; then CPPFLAGS="$CPPFLAGS -DDEBUG" AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no]) fi AC_MSG_CHECKING([whether to enable packet dumps]) AC_ARG_ENABLE(dump,[ --enable-dump enable packet dumps]) if test x$enable_dump != xno; then if test x$have_mman_h = xyes; then CPPFLAGS="$CPPFLAGS -DENABLE_DUMP" AC_MSG_RESULT([yes]) else AC_MSG_RESULT([no, sys/mman.h missing]) fi else AC_MSG_RESULT([no]) fi AC_ARG_WITH(efence, [ --with-efence= Use electric fence for malloc debugging.], if test x$withval != xyes ; then LDFLAGS="${LDFLAGS} -L$withval" fi AC_CHECK_LIB(efence,malloc) ) dnl Use -Wall if we have gcc. changequote(,)dnl if test "x$GCC" = "xyes"; then case " $CFLAGS " in *[\ \ ]-Wall[\ \ ]*) ;; *) CFLAGS="$CFLAGS -Wall" ;; esac fi changequote([,])dnl AC_CONFIG_FILES([ Makefile template/Makefile info/Makefile ]) AC_OUTPUT qstat-2.17/contrib.cfg000066400000000000000000000022011412457473700147310ustar00rootroot00000000000000# QStat config file # # The following game types were contributed by QStat users. # To use, copy them into your qstat.cfg or add "-f contrib.cfg" to you # qstat command line. gametype aps new extend gps name = Alien vs Predator 2 default port = 27888 template var = AVP2 game rule = gamename end gametype uts new extend uns name = Unreal Tournament default port = 7777 template var = UNREALTOURNAMENT game rule = gametype end gametype sms new extend gps name = Serious Sam default port = 25601 template var = SERIOUSSAM game rule = gametype end gametype gos new extend gps name = Global Operations default port = 28672 template var = GLOBALOPS game rule = gamedir end gametype j2s new extend q3s name = Jedi Knight 2 default port = 28070 template var = JEDIKNIGHT2 game rule = gamename end gametype dks new extend gps name = Daikatana default port = 27992 template var = DAIKATANA game rule = gamedir end gametype vcs new extend gps name = V8 Supercar Challange default port = 16700 template var = V8SUPERCAR game rule = gamedir end qstat-2.17/crysis.c000066400000000000000000000132411412457473700142760ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * Crysis query protocol * Copyright 2012 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms * */ #include #ifndef _WIN32 #include #include #include #else #include #endif #include #include #include #include "debug.h" #include "utils.h" #include "qstat.h" #include "md5.h" #include "packet_manip.h" char * decode_crysis_val(char *val) { // Very basic html conversion val = str_replace(val, """, "\""); return (str_replace(val, "&", "&")); } query_status_t send_crysis_request_packet(struct qserver *server) { char cmd[256], buf[1024], *password, *md5; debug(2, "challenge: %ld", server->challenge); switch (server->challenge) { case 0: // Not seen a challenge yet, request it server->challenge++; sprintf(cmd, "challenge"); break; case 1: server->challenge++; password = get_param_value(server, "password", ""); sprintf(cmd, "%s:%s", server->challenge_string, password); md5 = md5_hex(cmd, strlen(cmd)); sprintf(cmd, "authenticate %s", md5); free(md5); break; case 2: // NOTE: we currently don't support player info server->challenge++; server->flags |= TF_STATUS_QUERY; server->n_servers = 3; sprintf(cmd, "status"); break; case 3: return (DONE_FORCE); } server->saved_data.pkt_max = -1; sprintf(buf, "POST /RPC2 HTTP/1.1\015\012Keep-Alive: 300\015\012User-Agent: qstat %s\015\012Content-Length: %d\015\012Content-Type: text/xml\015\012\015\012%s", QSTAT_VERSION, (int)(98 + strlen(cmd)), cmd); return (send_packet(server, buf, strlen(buf))); } query_status_t valid_crysis_response(struct qserver *server, char *rawpkt, int pktlen) { char *s; int len; int cnt = packet_count(server); if ((0 == cnt) && (0 != strncmp("HTTP/1.1 200 OK", rawpkt, 15))) { // not valid response return (REQ_ERROR); } s = strnstr(rawpkt, "Content-Length: ", pktlen); if (NULL == s) { // not valid response return (INPROGRESS); } s += 16; if (1 != sscanf(s, "%d", &len)) { return (INPROGRESS); } s = strnstr(rawpkt, "\015\012\015\012", pktlen); if (NULL == s) { return (INPROGRESS); } s += 4; if (pktlen != (s - rawpkt + len)) { return (INPROGRESS); } return (DONE_FORCE); } char * crysis_response(struct qserver *server, char *rawpkt, int pktlen) { char *s, *e; int len = pktlen; s = strnstr(rawpkt, "", len); if (NULL == s) { // not valid response return (NULL); } s += 46; len += rawpkt - s; e = strnstr(s, "", len); if (NULL == e) { // not valid response return (NULL); } *e = '\0'; return (strdup(s)); } query_status_t deal_with_crysis_packet(struct qserver *server, char *rawpkt, int pktlen) { char *s, *val, *line; query_status_t state = INPROGRESS; debug(2, "processing..."); if (!server->combined) { state = valid_crysis_response(server, rawpkt, pktlen); server->retry1 = n_retries; if (0 == server->n_requests) { server->ping_total = time_delta(&packet_recv_time, &server->packet_time1); server->n_requests++; } switch (state) { case INPROGRESS: { // response fragment recieved int pkt_id; int pkt_max; // We're expecting more to come debug(5, "fragment recieved..."); pkt_id = packet_count(server); pkt_max = pkt_id++; if (!add_packet(server, 0, pkt_id, pkt_max, pktlen, rawpkt, 1)) { // fatal error e.g. out of memory return (MEM_ERROR); } // combine_packets will call us recursively return (combine_packets(server)); } case DONE_FORCE: break; // single packet response fall through default: return (state); } } if (DONE_FORCE != state) { state = valid_crysis_response(server, rawpkt, pktlen); switch (state) { case DONE_FORCE: break; // actually process default: return (state); } } debug(3, "packet: challenge = %ld", server->challenge); s = NULL; switch (server->challenge) { case 1: s = crysis_response(server, rawpkt, pktlen); if (NULL != s) { server->challenge_string = s; return (send_crysis_request_packet(server)); } return (REQ_ERROR); case 2: s = crysis_response(server, rawpkt, pktlen); if (NULL == s) { return (REQ_ERROR); } if (0 != strncmp(s, "authorized", 10)) { free(s); return (REQ_ERROR); } free(s); return (send_crysis_request_packet(server)); case 3: s = crysis_response(server, rawpkt, pktlen); if (NULL == s) { return (REQ_ERROR); } } // Correct ping // Not quite right but gives a good estimate server->ping_total = (server->ping_total * server->n_requests) / 2; debug(3, "processing response..."); s = decode_crysis_val(s); line = strtok(s, "\012"); // NOTE: id=XXX and msg=XXX will be processed by the mod following the one they where the response of while (NULL != line) { debug(4, "LINE: %s\n", line); val = strstr(line, ":"); if (NULL != val) { *val = '\0'; val += 2; debug(4, "var: %s, val: %s", line, val); if (0 == strcmp("name", line)) { server->server_name = strdup(val); } else if (0 == strcmp("level", line)) { server->map_name = strdup(val); } else if (0 == strcmp("players", line)) { if (2 == sscanf(val, "%d/%d", &server->num_players, &server->max_players)) { } } else if ( (0 == strcmp("version", line)) || (0 == strcmp("gamerules", line)) || (0 == strcmp("time remaining", line)) ) { add_rule(server, line, val, NO_FLAGS); } } line = strtok(NULL, "\012"); } gettimeofday(&server->packet_time1, NULL); return (DONE_FORCE); } qstat-2.17/crysis.h000066400000000000000000000006571412457473700143120ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * Crysis protocol * Copyright 2012 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_CRYSIS_H #define QSTAT_CRYSIS_H #include "qserver.h" // Packet processing methods query_status_t deal_with_crysis_packet(struct qserver *server, char *pkt, int pktlen); query_status_t send_crysis_request_packet(struct qserver *server); #endif qstat-2.17/cube2.c000066400000000000000000000101561412457473700137640ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * Cube 2 / Sauerbraten protocol * Copyright 2011 NoisyB * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #include #include #include #include "debug.h" #include "qstat.h" #include "packet_manip.h" struct offset { unsigned char *d; int pos; int len; }; //#define SB_MASTER_SERVER "http://sauerbraten.org/masterserver/retrieve.do?item=list" #define SB_PROTOCOL 258 #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define MAX_ATTR 255 #define MAX_STRING 1024 static int getint(struct offset *d) { int val = 0; if (d->pos >= d->len) { return (0); } val = d->d[d->pos++] & 0xff;// 8 bit value // except... if ((val == 0x80) && (d->pos < d->len - 2)) { // 16 bit value val = (d->d[d->pos++] & 0xff); val |= (d->d[d->pos++] & 0xff) << 8; } else if ((val == 0x81) && (d->pos < d->len - 4)) { // 32 bit value val = (d->d[d->pos++] & 0xff); val |= (d->d[d->pos++] & 0xff) << 8; val |= (d->d[d->pos++] & 0xff) << 16; val |= (d->d[d->pos++] & 0xff) << 24; } return (val); } static char * getstr(char *dest, int dest_len, struct offset *d) { int len = 0; if (d->pos >= d->len) { return (NULL); } len = MIN(dest_len, d->len - d->pos); strncpy(dest, (const char *)d->d + d->pos, len)[len - 1] = 0; d->pos += strlen(dest) + 1; return (dest); } static char * sb_getversion_s(int n) { static char *version_s[] = { "Justice", "CTF", "Assassin", "Summer", "Spring", "Gui", "Water", "Normalmap", "Sp", "Occlusion", "Shader", "Physics", "Mp", "", "Agc", "Quakecon", "Independence" }; n = SB_PROTOCOL - n; if ((n >= 0) && ((size_t)n < sizeof(version_s) / sizeof(version_s[0]))) { return (version_s[n]); } return ("unknown"); } static char * sb_getmode_s(int n) { static char *mode_s[] = { "slowmo SP", "slowmo DMSP", "demo", "SP", "DMSP", "ffa/default", "coopedit", "ffa/duel", "teamplay", "instagib", "instagib team", "efficiency", "efficiency team", "insta arena", "insta clan arena", "tactics arena", "tactics clan arena", "capture", "insta capture", "regen capture", "assassin", "insta assassin", "ctf", "insta ctf" }; n += 6; if ((n >= 0) && ((size_t)n < sizeof(mode_s) / sizeof(mode_s[0]))) { return (mode_s[n]); } return ("unknown"); } query_status_t send_cube2_request_packet(struct qserver *server) { return (send_packet(server, server->type->status_packet, server->type->status_len)); } query_status_t deal_with_cube2_packet(struct qserver *server, char *rawpkt, int pktlen) { // skip unimplemented ack, crc, etc int i; int numattr; int attr[MAX_ATTR]; char buf[MAX_STRING]; enum { MM_OPEN = 0, MM_VETO, MM_LOCKED, MM_PRIVATE }; struct offset d; d.d = (unsigned char *)rawpkt; d.pos = 0; d.len = pktlen; server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); getint(&d); // we have the ping already server->num_players = getint(&d); numattr = getint(&d); for (i = 0; i < numattr && i < MAX_ATTR; i++) { attr[i] = getint(&d); } server->protocol_version = attr[0]; sprintf(buf, "%d %s", attr[0], sb_getversion_s(attr[0])); add_rule(server, "version", buf, NO_FLAGS); sprintf(buf, "%d %s", attr[1], sb_getmode_s(attr[1])); add_rule(server, "mode", buf, NO_FLAGS); sprintf(buf, "%d", attr[2]); add_rule(server, "seconds_left", buf, NO_FLAGS); server->max_players = attr[3]; switch (attr[5]) { case MM_OPEN: sprintf(buf, "%d open", attr[5]); break; case MM_VETO: sprintf(buf, "%d veto", attr[5]); break; case MM_LOCKED: sprintf(buf, "%d locked", attr[5]); break; case MM_PRIVATE: sprintf(buf, "%d private", attr[5]); break; default: sprintf(buf, "%d unknown", attr[5]); } add_rule(server, "mm", buf, NO_FLAGS); for (i = 0; i < numattr && i < MAX_ATTR; i++) { char buf2[MAX_STRING]; sprintf(buf, "attr%d", i); sprintf(buf2, "%d", attr[i]); add_rule(server, buf, buf2, NO_FLAGS); } getstr(buf, MAX_STRING, &d); server->map_name = strdup(buf); getstr(buf, MAX_STRING, &d); server->server_name = strdup(buf); return (DONE_FORCE); } qstat-2.17/cube2.h000066400000000000000000000007021412457473700137650ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * Cube 2 / Sauerbraten protocol * Copyright 2011 NoisyB * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_CUBE2_H #define QSTAT_CUBE2_H #include "qserver.h" // Packet processing methods query_status_t deal_with_cube2_packet(struct qserver *server, char *pkt, int pktlen); query_status_t send_cube2_request_packet(struct qserver *server); #endif // QSTAT_CUBE2_H qstat-2.17/debug.c000066400000000000000000000065651412457473700140630ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * debug helper functions * Copyright 2004 Ludwig Nussel * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #include #include #ifndef _WIN32 #include #include #else #include #endif #include #include #include #include #include #include #include #define QSTAT_DEBUG_C #include "debug.h" #ifdef ENABLE_DUMP #ifndef _WIN32 #include #else #include #endif #endif #ifdef DEBUG void _debug(const char *file, int line, const char *function, int level, const char *fmt, ...) { va_list ap; char buf[9]; time_t now = time(NULL); strftime(buf, 9, "%H:%M:%S", localtime(&now)); fprintf(stderr, "debug(%d) %s %s:%d %s() - ", level, buf, file, line, function); va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); if ('\n' != fmt[strlen(fmt) - 1]) { fputs("\n", stderr); } } #endif static int debug_level = 0; void set_debug_level(int level) { debug_level = level; } int get_debug_level(void) { return (debug_level); } void malformed_packet(const struct qserver *server, const char *fmt, ...) { va_list ap; if (!show_errors) { return; } fputs("malformed packet", stderr); if (server) { fprintf(stderr, " from %d.%d.%d.%d:%hu", server->ipaddr & 0xff, (server->ipaddr >> 8) & 0xff, (server->ipaddr >> 16) & 0xff, (server->ipaddr >> 24) & 0xff, server->port); } fputs(": ", stderr); va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); if (*fmt && (fmt[strlen(fmt) - 1] != '\n')) { fputc('\n', stderr); } } #ifdef ENABLE_DUMP static unsigned count = 0; static void _dump_packet(const char *tag, const char *buf, int buflen) { char fn[PATH_MAX] = { 0 }; int fd; sprintf(fn, "%03u_%s.pkt", count++, tag); fprintf(stderr, "dumping to %s\n", fn); fd = open(fn, O_WRONLY | O_CREAT | O_EXCL, 0644); if (fd == -1) { perror("open"); return; } if (write(fd, buf, buflen) == -1) { perror("write"); } close(fd); } int do_dump; ssize_t send_dump(int s, const void *buf, size_t len, int flags) { if (do_dump) { _dump_packet("send", buf, len); } return (send(s, buf, len, flags)); } #endif void dump_packet(const char *buf, int buflen) { #ifdef ENABLE_DUMP _dump_packet("recv", buf, buflen); #endif } void print_packet(struct qserver *server, const char *buf, int buflen) { output_packet(server, buf, buflen, 0); } void output_packet(struct qserver *server, const char *buf, int buflen, int to) { static char *hex = "0123456789abcdef"; unsigned char *p = (unsigned char *)buf; int i, h, a, b, astart, offset = 0; char line[256]; if (server != NULL) { fprintf(stderr, "%s %s len %d\n", (to) ? "TO" : "FROM", server->arg, buflen); } for (i = buflen; i; offset += 16) { memset(line, ' ', 256); h = 0; h += sprintf(line, "%5d:", offset); a = astart = h + 16 * 2 + 16 / 4 + 2; for (b = 16; b && i; b--, i--, p++) { if ((b & 3) == 0) { line[h++] = ' '; } line[h++] = hex[*p >> 4]; line[h++] = hex[*p & 0xf]; if (isprint(*p)) { line[a++] = *p; } else { line[a++] = '.'; } if ((a - astart) == 8) { line[a++] = ' '; } } line[a] = '\0'; fputs(line, stderr); fputs("\n", stderr); } fputs("\n", stderr); } qstat-2.17/debug.h000066400000000000000000000033121412457473700140530ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * debug helper functions * Copyright 2004 Ludwig Nussel * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_DEBUG_H #define QSTAT_DEBUG_H #include #include "qstat.h" #ifdef DEBUG #include #ifdef _WIN32 void _debug(const char *file, int line, const char *function, int level, const char *fmt, ...); #define debug(level, fmt, ...) \ if (level <= get_debug_level()) \ _debug(__FILE__, __LINE__, __FUNCTION__, level, fmt, __VA_ARGS__) #else void _debug(const char *file, int line, const char *function, int level, const char *fmt, ...) GCC_FORMAT_PRINTF(5, 6); #define debug(level, fmt, rem ...) \ if (level <= get_debug_level()) \ _debug(__FILE__, __LINE__, __FUNCTION__, level, fmt, ## rem) #endif // _WIN32 #else #define debug(...) #endif // DEBUG void dump_packet(const char *buf, int buflen); #ifdef ENABLE_DUMP #ifndef _WIN32 #include #include #else #ifdef _MSC_VER #define ssize_t SSIZE_T #define uint32_t UINT32 #endif #endif #include #include extern int do_dump; ssize_t send_dump(int s, const void *buf, size_t len, int flags); #ifndef QSTAT_DEBUG_C #define send(s, buf, len, flags) send_dump(s, buf, len, flags) #endif #endif /** report a packet decoding error to stderr */ void malformed_packet(const struct qserver *server, const char *fmt, ...) GCC_FORMAT_PRINTF(2, 3); int get_debug_level(void); void set_debug_level(int level); void print_packet(struct qserver *server, const char *buf, int buflen); void output_packet(struct qserver *server, const char *buf, int buflen, int to); #endif qstat-2.17/dirtybomb.c000066400000000000000000000204551412457473700147620ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * DirtyBomb query protocol * Copyright 2012 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms * */ #include #ifndef _WIN32 #include #include #include #else #include #endif #include #include #include #include "debug.h" #include "utils.h" #include "qstat.h" #include "md5.h" #include "packet_manip.h" int unpack_msgpack(struct qserver *server, unsigned char *, unsigned char *); query_status_t send_dirtybomb_request_packet(struct qserver *server) { char buf[1024], *password, chunks, len; password = get_param_value(server, "password", ""); len = strlen(password); chunks = 0x01; if (server->flags & TF_RULES_QUERY) { chunks |= 0x02; } if (server->flags & TF_PLAYER_QUERY) { chunks |= 0x04; // Player chunks |= 0x08; // Team - Currently not supported } sprintf(buf, "%c%c%s%c", 0x01, len, password, chunks); server->saved_data.pkt_max = -1; return (send_packet(server, buf, len + 3)); } query_status_t deal_with_dirtybomb_packet(struct qserver *server, char *rawpkt, int pktlen) { unsigned char *s, *l, pkt_id, pkt_max; debug(2, "processing..."); if (8 > pktlen) { // not valid response malformed_packet(server, "packet too small"); return (PKT_ERROR); } if (rawpkt[4] != 0x01) { // not a known response malformed_packet(server, "unknown packet type 0x%02hhx", rawpkt[4]); return (PKT_ERROR); } // Response ID - long // Response Type - byte // Current Packet Number - byte pkt_id = rawpkt[5]; // Last Packet Number - byte pkt_max = rawpkt[6]; // Payload Blob - msgpack map s = (unsigned char *)rawpkt + 7; if (!server->combined) { int pkt_cnt = packet_count(server); server->retry1 = n_retries; if (0 == server->n_requests) { server->ping_total = time_delta(&packet_recv_time, &server->packet_time1); server->n_requests++; } if (pkt_cnt < pkt_max) { // We're expecting more to come debug(5, "fragment recieved..."); if (!add_packet(server, 0, pkt_id, pkt_max, pktlen, rawpkt, 1)) { // fatal error e.g. out of memory return (MEM_ERROR); } // combine_packets will call us recursively return (combine_packets(server)); } } // Correct ping // Not quite right but gives a good estimate server->ping_total = (server->ping_total * server->n_requests) / 2; debug(3, "processing response..."); gettimeofday(&server->packet_time1, NULL); l = (unsigned char *)rawpkt + pktlen - 1; if (!unpack_msgpack(server, s, l)) { return (PKT_ERROR); } return (DONE_FORCE); } int unpack_msgpack_pos_fixnum(struct qserver *server, uint8_t *val, unsigned char **datap, unsigned char *last, int raw) { if (!raw) { if (*datap > last) { malformed_packet(server, "packet too small"); return (0); } if ((**datap & 0x80) != 0) { malformed_packet(server, "invalid positive fixnum type 0x%02hhx", **datap); return (0); } } *val = **datap; (*datap)++; return (1); } int unpack_msgpack_uint16(struct qserver *server, uint16_t *val, unsigned char **datap, unsigned char *last, int raw) { if (!raw) { if (*datap > last) { malformed_packet(server, "packet too small"); return (0); } if (**datap != 0xcd) { malformed_packet(server, "invalid uint16 type 0x%02hhx", **datap); return (0); } (*datap)++; } if (*datap + 2 > last) { malformed_packet(server, "packet too small"); return (0); } *val = (uint16_t)(*datap)[1] | ((uint16_t)(*datap)[0] << 8); *datap += 2; return (1); } int unpack_msgpack_uint32(struct qserver *server, uint32_t *val, unsigned char **datap, unsigned char *last, int raw) { if (!raw) { if (*datap > last) { malformed_packet(server, "packet too small"); return (0); } if (**datap != 0xce) { malformed_packet(server, "invalid uint32 type 0x%02hhx", **datap); return (0); } (*datap)++; } if (*datap + 4 > last) { malformed_packet(server, "packet too small"); return (0); } *val = (uint32_t)(*datap)[3] | ((uint32_t)(*datap)[2] << 8) | ((uint32_t)(*datap)[1] << 16) | ((uint32_t)(*datap)[0] << 24); *datap += 2; return (1); } int unpack_msgpack_raw_len(struct qserver *server, char **valp, unsigned char **datap, unsigned char *last, uint32_t len) { char *data; debug(4, "raw_len: 0x%lu\n", (unsigned long)len); if (*datap + len > last) { malformed_packet(server, "packet too small"); return (0); } if (NULL == (data = malloc(len + 1))) { malformed_packet(server, "out of memory"); return (0); } memcpy(data, *datap, len); data[len] = '\0'; *valp = data; (*datap) += len; return (1); } int unpack_msgpack_raw(struct qserver *server, char **valp, unsigned char **datap, unsigned char *last) { unsigned char type, len; if (*datap + 1 > last) { malformed_packet(server, "packet too small"); return (0); } type = (**datap & 0xa0); len = (**datap & 0x1f); if (0xa0 != type) { malformed_packet(server, "invalid raw type 0x%02hhx", **datap); return (0); } (*datap)++; return (unpack_msgpack_raw_len(server, valp, datap, last, (uint32_t)len)); } int unpack_msgpack_raw16(struct qserver *server, char **valp, unsigned char **datap, unsigned char *last) { uint16_t len; if (*datap + 3 > last) { malformed_packet(server, "packet too small"); return (0); } (*datap)++; if (!unpack_msgpack_uint16(server, &len, datap, last, 1)) { return (0); } return (unpack_msgpack_raw_len(server, valp, datap, last, (uint32_t)len)); } int unpack_msgpack_raw32(struct qserver *server, char **valp, unsigned char **datap, unsigned char *last) { uint32_t len; if (*datap + 5 > last) { malformed_packet(server, "packet too small"); return (0); } (*datap)++; if (!unpack_msgpack_uint32(server, &len, datap, last, 1)) { return (0); } return (unpack_msgpack_raw_len(server, valp, datap, last, len)); } int unpack_msgpack_string(struct qserver *server, char **valp, unsigned char **datap, unsigned char *last) { unsigned char type = **datap; debug(4, "string: 0x%02hhx\n", type); if (0xa0 == (type & 0xa0)) { return (unpack_msgpack_raw(server, valp, datap, last)); } else if (type == 0xda) { return (unpack_msgpack_raw16(server, valp, datap, last)); } else if (type == 0xdb) { return (unpack_msgpack_raw32(server, valp, datap, last)); } else { malformed_packet(server, "invalid string type 0x%02hhx", type); } return (0); } int unpack_msgpack(struct qserver *server, unsigned char *s, unsigned char *l) { uint16_t elements; unsigned char type, type_len; char *var, *str_val; uint8_t uint8_val; type_len = *s; s++; debug(3, "type/len: 0x%02hhx\n", type_len); if (0x80 == (type_len & 0x80)) { type = (type_len & 0x80); elements = (type_len & 0x0f); debug(3, "map type: 0x%02hhx, elements: %d\n", type, elements); } else if (type_len == 0xde) { // map 16 if (!unpack_msgpack_uint16(server, &elements, &s, l, 1)) { return (0); } } else { // There is a map 32 but we don't support it malformed_packet(server, "invalid map type 0x%02hhx", type_len); return (0); } while (elements) { type = *s; if (!unpack_msgpack_string(server, &var, &s, l)) { return (0); } debug(4, "Map[%s]\n", var); if (0 == strcmp(var, "SN")) { // Server Name if (!unpack_msgpack_string(server, &str_val, &s, l)) { return (0); } server->server_name = str_val; } else if (0 == strcmp(var, "MAP")) { // Map if (!unpack_msgpack_string(server, &str_val, &s, l)) { return (0); } server->map_name = str_val; } else if (0 == strcmp(var, "MP")) { // Max Players if (!unpack_msgpack_pos_fixnum(server, &uint8_val, &s, l, 0)) { return (0); } server->max_players = uint8_val; } else if (0 == strcmp(var, "NP")) { // Number of Players if (!unpack_msgpack_pos_fixnum(server, &uint8_val, &s, l, 0)) { return (0); } server->num_players = uint8_val; } else if (0 == strcmp(var, "RL")) { // Gametype if (!unpack_msgpack_string(server, &str_val, &s, l)) { return (0); } server->game = str_val; add_rule(server, "gametype", str_val, NO_FLAGS); } else if (0 == strcmp(var, "SFT")) { // Current Frametime in ms if (!unpack_msgpack_pos_fixnum(server, &uint8_val, &s, l, 0)) { return (0); } } else { debug(4, "Unknown setting '%s'\n", var); s++; } free(var); elements--; } return (1); } qstat-2.17/dirtybomb.h000066400000000000000000000006761412457473700147720ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * DirtyBomb protocol * Copyright 2012 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_DIRTYBOMB_H #define QSTAT_DIRTYBOMB_H #include "qserver.h" // Packet processing methods query_status_t deal_with_dirtybomb_packet(struct qserver *server, char *pkt, int pktlen); query_status_t send_dirtybomb_request_packet(struct qserver *server); #endif qstat-2.17/display_json.c000066400000000000000000000746231412457473700154730ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * steve@qstat.org * http://www.qstat.org * * Inspired by QuakePing by Len Norton * * JSON output * Contributed by Steve Teuber * * Copyright 2013 by Steve Teuber * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #include #include #include #include "qstat.h" #include "xform.h" #include "display_json.h" extern FILE *OF;/* output file */ int json_display = 0; int json_printed = 0; void json_protocols() { int i; server_type *type; server_type **sorted_types; sorted_types = (server_type **)malloc(sizeof(server_type *) * n_server_types); type = &types[0]; for (i = 0; type->id != Q_UNKNOWN_TYPE && i < n_server_types; type++, i++) { sorted_types[i] = type; } quicksort((void **)sorted_types, 0, n_server_types - 1, (int (*)(void *, void *))type_option_compare); printf("{\n"); for (i = 0; i < n_server_types; i++) { type = sorted_types[i]; if (i) { printf(",\n"); } printf("\t\"%s\": \"%s\"", type->type_string, type->game_name); } printf("\n}\n"); exit(0); } void json_version() { printf("{\n\t\"version\": \"%s\"\n}\n", QSTAT_VERSION); exit(0); } void json_display_server(struct qserver *server) { char *prefix; prefix = server->type->type_string; if (server->server_name == DOWN) { if (!up_servers_only) { xform_printf(OF, (json_printed) ? ",\n\t{\n" : "\t{\n"); xform_printf(OF, "\t\t\"protocol\": \"%s\",\n", json_escape(prefix)); xform_printf(OF, "\t\t\"address\": \"%s\",\n", json_escape(server->arg)); xform_printf(OF, "\t\t\"status\": \"%s\",\n", json_escape("offline")); xform_printf(OF, "\t\t\"hostname\": \"%s\"\n", json_escape((hostname_lookup) ? server->host_name : server->arg)); xform_printf(OF, "\t}"); json_printed = 1; } return; } if (server->server_name == TIMEOUT) { if (server->flags & FLAG_BROADCAST && server->n_servers) { xform_printf(OF, (json_printed) ? ",\n\t{\n" : "\t{\n"); xform_printf(OF, "\t\t\"protocol\": \"%s\",\n", json_escape(prefix)); xform_printf(OF, "\t\t\"address\": \"%s\",\n", json_escape(server->arg)); xform_printf(OF, "\t\t\"status\": \"%s\",\n", json_escape("timeout")); xform_printf(OF, "\t\t\"servers\": %d\n", server->n_servers); xform_printf(OF, "\t}"); json_printed = 1; } else if (!up_servers_only) { xform_printf(OF, (json_printed) ? ",\n\t{\n" : "\t{\n"); xform_printf(OF, "\t\t\"protocol\": \"%s\",\n", json_escape(prefix)); xform_printf(OF, "\t\t\"address\": \"%s\",\n", json_escape(server->arg)); xform_printf(OF, "\t\t\"status\": \"%s\",\n", json_escape("timeout")); xform_printf(OF, "\t\t\"hostname\": \"%s\"\n", json_escape((hostname_lookup) ? server->host_name : server->arg)); xform_printf(OF, "\t}"); json_printed = 1; } return; } if (server->error != NULL) { xform_printf(OF, (json_printed) ? ",\n\t{\n" : "\t{\n"); xform_printf(OF, "\t\t\"protocol\": \"%s\",\n", json_escape(prefix)); xform_printf(OF, "\t\t\"address\": \"%s\",\n", json_escape(server->arg)); xform_printf(OF, "\t\t\"status\": \"%s\",\n", json_escape("error")); xform_printf(OF, "\t\t\"hostname\": \"%s\",\n", json_escape((hostname_lookup) ? server->host_name : server->arg)); xform_printf(OF, "\t\t\"error\": \"%s\"\n", json_escape(server->error)); xform_printf(OF, "\t}"); json_printed = 1; } else if (server->type->master) { xform_printf(OF, (json_printed) ? ",\n\t{\n" : "\t{\n"); xform_printf(OF, "\t\t\"protocol\": \"%s\",\n", json_escape(prefix)); xform_printf(OF, "\t\t\"address\": \"%s\",\n", json_escape(server->arg)); xform_printf(OF, "\t\t\"status\": \"%s\",\n", json_escape("online")); xform_printf(OF, "\t\t\"servers\": %d\n", server->n_servers); xform_printf(OF, "\t}"); json_printed = 1; } else { xform_printf(OF, (json_printed) ? ",\n\t{\n" : "\t{\n"); xform_printf(OF, "\t\t\"protocol\": \"%s\",\n", json_escape(prefix)); xform_printf(OF, "\t\t\"address\": \"%s\",\n", json_escape(server->arg)); xform_printf(OF, "\t\t\"status\": \"%s\",\n", json_escape("online")); xform_printf(OF, "\t\t\"hostname\": \"%s\",\n", json_escape((hostname_lookup) ? server->host_name : server->arg)); xform_printf(OF, "\t\t\"name\": \"%s\",\n", json_escape(xform_name(server->server_name, server))); xform_printf(OF, "\t\t\"gametype\": \"%s\",\n", json_escape(get_qw_game(server))); xform_printf(OF, "\t\t\"map\": \"%s\",\n", json_escape(xform_name(server->map_name, server))); xform_printf(OF, "\t\t\"numplayers\": %d,\n", server->num_players); xform_printf(OF, "\t\t\"maxplayers\": %d,\n", server->max_players); xform_printf(OF, "\t\t\"numspectators\": %d,\n", server->num_spectators); xform_printf(OF, "\t\t\"maxspectators\": %d", server->max_spectators); if (!(server->type->flags & TF_RAW_STYLE_TRIBES)) { xform_printf(OF, ",\n\t\t\"ping\": %d,\n", server->n_requests ? server->ping_total / server->n_requests : 999); xform_printf(OF, "\t\t\"retries\": %d", server->n_retries); } if (server->type->flags & TF_RAW_STYLE_QUAKE) { xform_printf(OF, ",\n\t\t\"address\": %s,\n", json_escape(server->address)); xform_printf(OF, "\t\t\"protocolversion\": %d", server->protocol_version); } if (get_server_rules && (NULL != server->type->display_json_rule_func)) { server->type->display_json_rule_func(server); } if (get_player_info && (NULL != server->type->display_json_player_func)) { server->type->display_json_player_func(server); } xform_printf(OF, "\n\t}"); json_printed = 1; } } void json_header() { xform_printf(OF, "[\n"); } void json_footer() { xform_printf(OF, "\n]\n"); } void json_display_server_rules(struct qserver *server) { struct rule *rule; int printed = 0; rule = server->rules; xform_printf(OF, ",\n\t\t\"rules\": {\n"); for ( ; rule != NULL; rule = rule->next) { if (printed) { xform_printf(OF, ",\n"); } xform_printf(OF, "\t\t\t\"%s\": \"%s\"", json_escape(rule->name), json_escape(rule->value)); printed = 1; } xform_printf(OF, "\n\t\t}"); } void json_display_q_player_info(struct qserver *server) { struct player *player; int printed = 0; xform_printf(OF, ",\n\t\t\"players\": [\n"); player = server->players; for ( ; player != NULL; player = player->next) { if (printed) { xform_printf(OF, ",\n"); } xform_printf(OF, "\t\t\t{\n"); xform_printf(OF, "\t\t\t\"number\": %d,\n", player->number); xform_printf(OF, "\t\t\t\t\"name\": \"%s\",\n", json_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t\"address\": \"%s\",\n", json_escape(player->address)); xform_printf(OF, "\t\t\t\t\"score\": %d,\n", player->frags); xform_printf(OF, "\t\t\t\t\"time\": \"%s\"\n", json_escape(play_time(player->connect_time, 2))); xform_printf(OF, "\t\t\t}"); printed = 1; } xform_printf(OF, "\n\t\t]"); } void json_display_qw_player_info(struct qserver *server) { struct player *player; int printed = 0; xform_printf(OF, ",\n\t\t\"players\": [\n"); player = server->players; for ( ; player != NULL; player = player->next) { if (printed) { xform_printf(OF, ",\n"); } xform_printf(OF, "\t\t\t{\n"); xform_printf(OF, "\t\t\t\t\"number\": %d,\n", player->number); xform_printf(OF, "\t\t\t\t\"name\": \"%s\",\n", json_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t\"score\": %d,\n", player->frags); xform_printf(OF, "\t\t\t\t\"time\": \"%s\",\n", json_escape(play_time(player->connect_time, 2))); xform_printf(OF, "\t\t\t\t\"ping\": %d,\n", player->ping); xform_printf(OF, "\t\t\t\t\"skin\": \"%s\",\n", player->skin ? json_escape(player->skin) : ""); xform_printf(OF, "\t\t\t\t\"team\": \"%s\"\n", player->team_name ? json_escape(player->team_name) : ""); xform_printf(OF, "\t\t\t}"); printed = 1; } xform_printf(OF, "\n\t\t]"); } void json_display_q2_player_info(struct qserver *server) { struct player *player; int printed = 0; xform_printf(OF, ",\n\t\t\"players\": [\n"); player = server->players; for ( ; player != NULL; player = player->next) { if (printed) { xform_printf(OF, ",\n"); } xform_printf(OF, "\t\t\t{\n"); xform_printf(OF, "\t\t\t\t\"name\": \"%s\",\n", json_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t\"score\": %d,\n", player->frags); if (server->flags & FLAG_PLAYER_TEAMS) { xform_printf(OF, "\t\t\t\t\"team\": %d,\n", player->team); } xform_printf(OF, "\t\t\t\t\"ping\": %d\n", player->ping); xform_printf(OF, "\t\t\t}"); printed = 1; } xform_printf(OF, "\n\t\t]"); } void json_display_player_info_info(struct player *player) { struct info *info; for (info = player->info; info; info = info->next) { if (info->name) { char *name = json_escape(info->name); char *value = json_escape(info->value); xform_printf(OF, "\t\t\t\t\"%s\": \"%s\",\n", name, value); } } } void json_display_unreal_player_info(struct qserver *server) { struct player *player; int printed = 0; xform_printf(OF, ",\n\t\t\"players\": [\n"); player = server->players; for ( ; player != NULL; player = player->next) { if (printed) { xform_printf(OF, ",\n"); } xform_printf(OF, "\t\t\t{\n"); xform_printf(OF, "\t\t\t\t\"name\": \"%s\",\n", json_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t\"score\": %d,\n", player->frags); if (-999 != player->deaths) { xform_printf(OF, "\t\t\t\t\"deaths\": %d,\n", player->deaths); } if (player->team_name != NULL) { xform_printf(OF, "\t\t\t\t\"team\": \"%s\",\n", json_escape(player->team_name)); } else if (-1 != player->team) { xform_printf(OF, "\t\t\t\t\"team\": %d,\n", player->team); } if (player->skin != NULL) { xform_printf(OF, "\t\t\t\t\"skin\": \"%s\",\n", player->skin ? json_escape(player->skin) : ""); } if (player->mesh != NULL) { xform_printf(OF, "\t\t\t\t\"mesh\": \"%s\",\n", player->mesh ? json_escape(player->mesh) : ""); } if (player->face != NULL) { xform_printf(OF, "\t\t\t\t\"face\": \"%s\",\n", player->face ? json_escape(player->face) : ""); } json_display_player_info_info(player); xform_printf(OF, "\t\t\t\t\"ping\": %d\n", player->ping); xform_printf(OF, "\t\t\t}"); printed = 1; } xform_printf(OF, "\n\t\t]"); } void json_display_halflife_player_info(struct qserver *server) { struct player *player; int printed = 0; xform_printf(OF, ",\n\t\t\"players\": [\n"); player = server->players; for ( ; player != NULL; player = player->next) { if (printed) { xform_printf(OF, ",\n"); } xform_printf(OF, "\t\t\t{\n"); xform_printf(OF, "\t\t\t\t\"name\": \"%s\",\n", json_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t\"score\": %d,\n", player->frags); xform_printf(OF, "\t\t\t\t\"time\": \"%s\"\n", json_escape(play_time(player->connect_time, 2))); xform_printf(OF, "\t\t\t}"); printed = 1; } xform_printf(OF, "\n\t\t]"); } void json_display_fl_player_info(struct qserver *server) { struct player *player; int printed = 0; xform_printf(OF, ",\n\t\t\"players\": [\n"); player = server->players; for ( ; player != NULL; player = player->next) { if (printed) { xform_printf(OF, ",\n"); } xform_printf(OF, "\t\t\t{\n"); xform_printf(OF, "\t\t\t\t\"name\": \"%s\",\n", json_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t\"score\": %d,\n", player->frags); xform_printf(OF, "\t\t\t\t\"ping\": %d,\n", player->ping); xform_printf(OF, "\t\t\t\t\"team\": %d,\n", player->team); xform_printf(OF, "\t\t\t\t\"time\": \"%s\"\n", json_escape(play_time(player->connect_time, 2))); xform_printf(OF, "\t\t\t}"); printed = 1; } xform_printf(OF, "\n\t\t]"); } void json_display_tribes_player_info(struct qserver *server) { struct player *player; int printed = 0; xform_printf(OF, ",\n\t\t\"players\": [\n"); player = server->players; for ( ; player != NULL; player = player->next) { if (printed) { xform_printf(OF, ",\n"); } xform_printf(OF, "\t\t\t{\n"); xform_printf(OF, "\t\t\t\t\"name\": \"%s\",\n", json_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t\"score\": %d,\n", player->frags); xform_printf(OF, "\t\t\t\t\"team\": %d,\n", player->team); xform_printf(OF, "\t\t\t\t\"ping\": %d,\n", player->ping); xform_printf(OF, "\t\t\t\t\"packetloss\": %d\n", player->packet_loss); xform_printf(OF, "\t\t\t}"); printed = 1; } xform_printf(OF, "\n\t\t]"); } void json_display_tribes2_player_info(struct qserver *server) { struct player *player; int printed = 0; char *type; // Build Teams into a seperate object xform_printf(OF, ",\n\t\t\"teams\": [\n"); player = server->players; for ( ; player != NULL; player = player->next) { if (player->number == TRIBES_TEAM) { if (printed) { xform_printf(OF, ",\n"); } printed = 1; xform_printf(OF, "\t\t\t{\n"); xform_printf(OF, "\t\t\t\t\"team\": \"%s\",\n", json_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t\"score\": %d\n", player->frags); xform_printf(OF, "\t\t\t}"); } } xform_printf(OF, "\n\t\t]"); xform_printf(OF, ",\n\t\t\"players\": [\n"); printed = 0; player = server->players; for ( ; player != NULL; player = player->next) { if (player->team_name != NULL) { switch (player->type_flag) { case PLAYER_TYPE_BOT: type = "Bot"; break; case PLAYER_TYPE_ALIAS: type = "Alias"; break; default: type = ""; break; } if (printed) { xform_printf(OF, ",\n"); } xform_printf(OF, "\t\t\t{\n"); xform_printf(OF, "\t\t\t\t\"name\": \"%s\",\n", json_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t\"score\": %d,\n", player->frags); xform_printf(OF, "\t\t\t\t\"team\": \"%s\",\n", json_escape(player->team_name)); xform_printf(OF, "\t\t\t\t\"type\": \"%s\",\n", json_escape(type)); xform_printf(OF, "\t\t\t\t\"clan\": \"%s\"\n", player->tribe_tag ? json_escape(xform_name(player->tribe_tag, server)) : ""); xform_printf(OF, "\t\t\t}"); printed = 1; } } xform_printf(OF, "\n\t\t]"); } void json_display_bfris_player_info(struct qserver *server) { struct player *player; int printed = 0; xform_printf(OF, ",\n\t\t\"players\": [\n"); player = server->players; for ( ; player != NULL; player = player->next) { if (printed) { xform_printf(OF, ",\n"); } xform_printf(OF, "\t\t\t{\n"); xform_printf(OF, "\t\t\t\t\"number\": %d,\n", player->number); xform_printf(OF, "\t\t\t\t\"name\": \"%s\",\n", json_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t\"score\": %d,\n", player->score); xform_printf(OF, "\t\t\t\t\"frags\": %d,\n", player->frags); xform_printf(OF, "\t\t\t\t\"team\": \"%s\",\n", json_escape(player->team_name)); xform_printf(OF, "\t\t\t\t\"ping\": %d,\n", player->ping); xform_printf(OF, "\t\t\t\t\"ship\": %d\n", player->ship); xform_printf(OF, "\t\t\t}"); printed = 1; } xform_printf(OF, "\n\t\t]"); } void json_display_descent3_player_info(struct qserver *server) { struct player *player; int printed = 0; xform_printf(OF, ",\n\t\t\"players\": [\n"); player = server->players; for ( ; player != NULL; player = player->next) { if (printed) { xform_printf(OF, ",\n"); } xform_printf(OF, "\t\t\t{\n"); xform_printf(OF, "\t\t\t\t\"name\": \"%s\",\n", json_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t\"score\": %d,\n", player->frags); xform_printf(OF, "\t\t\t\t\"deaths\": %d,\n", player->deaths); xform_printf(OF, "\t\t\t\t\"ping\": %d,\n", player->ping); xform_printf(OF, "\t\t\t\t\"team\": %d\n", player->team); xform_printf(OF, "\t\t\t}"); printed = 1; } xform_printf(OF, "\n\t\t]"); } void json_display_ravenshield_player_info(struct qserver *server) { struct player *player; int printed = 0; xform_printf(OF, ",\n\t\t\"players\": [\n"); player = server->players; for ( ; player != NULL; player = player->next) { if (printed) { xform_printf(OF, ",\n"); } xform_printf(OF, "\t\t\t{\n"); xform_printf(OF, "\t\t\t\t\"name\": \"%s\",\n", json_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t\"score\": %d,\n", player->frags); xform_printf(OF, "\t\t\t\t\"time\": \"%s\"\n", json_escape(play_time(player->connect_time, 2))); xform_printf(OF, "\t\t\t}"); printed = 1; } xform_printf(OF, "\n\t\t]"); } void json_display_ghostrecon_player_info(struct qserver *server) { struct player *player; int printed = 0; xform_printf(OF, ",\n\t\t\"players\": [\n"); player = server->players; for ( ; player != NULL; player = player->next) { if (printed) { xform_printf(OF, ",\n"); } xform_printf(OF, "\t\t\t{\n"); xform_printf(OF, "\t\t\t\t\"name\": \"%s\",\n", json_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t\"deaths\": %d,\n", player->deaths); xform_printf(OF, "\t\t\t\t\"team\": %d\n", player->team); xform_printf(OF, "\t\t\t}"); printed = 1; } xform_printf(OF, "\n\t\t]"); } void json_display_eye_player_info(struct qserver *server) { struct player *player; int printed = 0; xform_printf(OF, ",\n\t\t\"players\": [\n"); player = server->players; for ( ; player != NULL; player = player->next) { if (printed) { xform_printf(OF, ",\n"); } xform_printf(OF, "\t\t\t{\n"); xform_printf(OF, "\t\t\t\t\"name\": \"%s\",\n", json_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t\"score\": %d,\n", player->score); if (player->team_name != NULL) { xform_printf(OF, "\t\t\t\t\"team\": \"%s\",\n", json_escape(player->team_name)); } else { xform_printf(OF, "\t\t\t\t\"team\": %d,\n", player->team); } if (player->skin != NULL) { xform_printf(OF, "\t\t\t\t\"skin\": \"%s\",\n", json_escape(player->skin)); } if (player->connect_time != 0) { xform_printf(OF, "\t\t\t\t\"time\": \"%s\",\n", json_escape(play_time(player->connect_time, 1))); } xform_printf(OF, "\t\t\t\t\"ping\": %d\n", player->ping); xform_printf(OF, "\t\t\t}"); printed = 1; } xform_printf(OF, "\n\t\t]"); } void json_display_doom3_player_info(struct qserver *server) { struct player *player; int printed = 0; xform_printf(OF, ",\n\t\t\"players\": [\n"); player = server->players; for ( ; player != NULL; player = player->next) { if (printed) { xform_printf(OF, ",\n"); } xform_printf(OF, "\t\t\t{\n"); xform_printf(OF, "\t\t\t\t\"number\": %d,\n", player->number); xform_printf(OF, "\t\t\t\t\"name\": \"%s\",\n", json_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t\"score\": %d,\n", player->score); if (player->tribe_tag != NULL) { xform_printf(OF, "\t\t\t\t\"clan\": \"%s\",\n", player->tribe_tag ? json_escape(xform_name(player->tribe_tag, server)) : ""); } else { xform_printf(OF, "\t\t\t\t\"team\": %d,\n", player->team); } if (player->skin != NULL) { xform_printf(OF, "\t\t\t\t\"skin\": \"%s\",\n", json_escape(player->skin)); } if (player->type_flag != 0) { xform_printf(OF, "\t\t\t\t\"type\": \"%s\",\n", "bot"); } else { xform_printf(OF, "\t\t\t\t\"type\": \"%s\",\n", "player"); } if (player->connect_time != 0) { xform_printf(OF, "\t\t\t\t\"time\": \"%s\",\n", json_escape(play_time(player->connect_time, 2))); } json_display_player_info_info(player); xform_printf(OF, "\t\t\t\t\"ping\": %d\n", player->ping); xform_printf(OF, "\t\t\t}"); printed = 1; } xform_printf(OF, "\n\t\t]"); } void json_display_player_info(struct qserver *server) { struct player *player; int printed = 0; xform_printf(OF, ",\n\t\t\"players\": [\n"); player = server->players; for ( ; player != NULL; player = player->next) { if (printed) { xform_printf(OF, ",\n"); } xform_printf(OF, "\t\t\t{\n"); if (NA_INT != player->ping) { xform_printf(OF, "\t\t\t\t\"ping\": %d,\n", player->ping); } if (NA_INT != player->score) { xform_printf(OF, "\t\t\t\t\"score\": %d,\n", player->score); } if (NA_INT != player->deaths) { xform_printf(OF, "\t\t\t\t\"deaths\": %d,\n", player->deaths); } if (NA_INT != player->frags) { xform_printf(OF, "\t\t\t\t\"frags\": %d,\n", player->frags); } if (player->team_name != NULL) { xform_printf(OF, "\t\t\t\t\"team\": \"%s\",\n", json_escape(player->team_name)); } else if (NA_INT != player->team) { xform_printf(OF, "\t\t\t\t\"team\": %d,\n", player->team); } if (player->skin != NULL) { xform_printf(OF, "\t\t\t\t\"skin\": \"%s\",\n", json_escape(player->skin)); } if (player->connect_time != 0) { xform_printf(OF, "\t\t\t\t\"time\": \"%s\",\n", json_escape(play_time(player->connect_time, 1))); } if (player->address != NULL) { xform_printf(OF, "\t\t\t\t\"address\": \"%s\",\n", json_escape(player->address)); } json_display_player_info_info(player); xform_printf(OF, "\t\t\t\t\"name\": \"%s\"\n", json_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t}"); printed = 1; } xform_printf(OF, "\n\t\t]"); } void json_display_ts2_player_info(struct qserver *server) { struct player *player; int printed = 0; xform_printf(OF, ",\n\t\t\"players\": [\n"); player = server->players; for ( ; player != NULL; player = player->next) { if (printed) { xform_printf(OF, ",\n"); } xform_printf(OF, "\t\t\t{\n"); xform_printf(OF, "\t\t\t\t\"name\": \"%s\",\n", json_escape(xform_name(player->name, server))); if (player->connect_time != 0) { xform_printf(OF, "\t\t\t\t\"time\": \"%s\",\n", json_escape(play_time(player->connect_time, 2))); } json_display_player_info_info(player); xform_printf(OF, "\t\t\t\t\"ping\": %d\n", player->ping); xform_printf(OF, "\t\t\t}"); printed = 1; } xform_printf(OF, "\n\t\t]"); } void json_display_ts3_player_info(struct qserver *server) { struct player *player; int printed = 0; xform_printf(OF, ",\n\t\t\"players\": [\n"); player = server->players; for ( ; player != NULL; player = player->next) { if (printed) { xform_printf(OF, ",\n"); } xform_printf(OF, "\t\t\t{\n"); if (player->connect_time != 0) { xform_printf(OF, "\t\t\t\t\"time\": \"%s\",\n", json_escape(play_time(player->connect_time, 2))); } json_display_player_info_info(player); xform_printf(OF, "\t\t\t\t\"name\": \"%s\"\n", json_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t}"); printed = 1; } xform_printf(OF, "\n\t\t]"); } void json_display_bfbc2_player_info(struct qserver *server) { struct player *player; int printed = 0; xform_printf(OF, ",\n\t\t\"players\": [\n"); player = server->players; for ( ; player != NULL; player = player->next) { if (printed) { xform_printf(OF, ",\n"); } xform_printf(OF, "\t\t\t{\n"); if (player->connect_time != 0) { xform_printf(OF, "\t\t\t\t\"time\": \"%s\",\n", json_escape(play_time(player->connect_time, 2))); } json_display_player_info_info(player); xform_printf(OF, "\t\t\t\t\"name\": \"%s\"\n", json_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t}"); printed = 1; } xform_printf(OF, "\n\t\t]"); } void json_display_wic_player_info(struct qserver *server) { struct player *player; int printed = 0; xform_printf(OF, ",\n\t\t\"players\": [\n"); player = server->players; for ( ; player != NULL; player = player->next) { if (printed) { xform_printf(OF, ",\n"); } xform_printf(OF, "\t\t\t{\n"); xform_printf(OF, "\t\t\t\t\"name\": \"%s\",\n", json_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t\"score\": %d,\n", player->score); xform_printf(OF, "\t\t\t\t\"team\": \"%s\",\n", json_escape(player->team_name)); if (player->tribe_tag != NULL) { xform_printf(OF, "\t\t\t\t\"role\": \"%s\",\n", json_escape(player->tribe_tag)); } json_display_player_info_info(player); xform_printf(OF, "\t\t\t\t\"bot\": %d\n", player->type_flag); xform_printf(OF, "\t\t\t}"); printed = 1; } xform_printf(OF, "\n\t\t]"); } void json_display_ventrilo_player_info(struct qserver *server) { struct player *player; int printed = 0; xform_printf(OF, ",\n\t\t\"players\": [\n"); player = server->players; for ( ; player != NULL; player = player->next) { if (printed) { xform_printf(OF, ",\n"); } xform_printf(OF, "\t\t\t{\n"); xform_printf(OF, "\t\t\t\t\"name\": \"%s\",\n", json_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t\"ping\": %d,\n", player->ping); xform_printf(OF, "\t\t\t\t\"team\": \"%s\",\n", json_escape(player->team_name)); xform_printf(OF, "\t\t\t\t\"time\": \"%s\"\n", json_escape(play_time(player->connect_time, 2))); xform_printf(OF, "\t\t\t}"); printed = 1; } xform_printf(OF, "\n\t\t]"); } void json_display_tm_player_info(struct qserver *server) { struct player *player; int printed = 0; xform_printf(OF, ",\n\t\t\"players\": [\n"); player = server->players; for ( ; player != NULL; player = player->next) { if (printed) { xform_printf(OF, ",\n"); } xform_printf(OF, "\t\t\t{\n"); xform_printf(OF, "\t\t\t\t\"name\": \"%s\",\n", json_escape(xform_name(player->name, server))); if (player->connect_time != 0) { xform_printf(OF, "\t\t\t\t\"time\": \"%s\",\n", json_escape(play_time(player->connect_time, 2))); } json_display_player_info_info(player); xform_printf(OF, "\t\t\t\t\"ping\": %d\n", player->ping); xform_printf(OF, "\t\t\t}"); printed = 1; } xform_printf(OF, "\n\t\t]"); } void json_display_savage_player_info(struct qserver *server) { struct player *player; int printed = 0; xform_printf(OF, ",\n\t\t\"players\": [\n"); player = server->players; for ( ; player != NULL; player = player->next) { if (printed) { xform_printf(OF, ",\n"); } xform_printf(OF, "\t\t\t{\n"); xform_printf(OF, "\t\t\t\t\"name\": \"%s\",\n", json_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t\"score\": %d,\n", player->frags); xform_printf(OF, "\t\t\t\t\"time\": \"%s\"\n", json_escape(play_time(player->connect_time, 2))); xform_printf(OF, "\t\t\t}"); printed = 1; } xform_printf(OF, "\n\t\t]"); } void json_display_farcry_player_info(struct qserver *server) { struct player *player; int printed = 0; xform_printf(OF, ",\n\t\t\"players\": [\n"); player = server->players; for ( ; player != NULL; player = player->next) { if (printed) { xform_printf(OF, ",\n"); } xform_printf(OF, "\t\t\t{\n"); xform_printf(OF, "\t\t\t\t\"name\": \"%s\",\n", json_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t\"score\": %d,\n", player->frags); xform_printf(OF, "\t\t\t\t\"time\": \"%s\"\n", json_escape(play_time(player->connect_time, 2))); xform_printf(OF, "\t\t\t}"); printed = 1; } xform_printf(OF, "\n\t\t]"); } void json_display_tee_player_info(struct qserver *server) { struct player *player; int printed = 0; xform_printf(OF, ",\n\t\t\"players\": [\n"); player = server->players; for ( ; player != NULL; player = player->next) { if (printed) { xform_printf(OF, ",\n"); } xform_printf(OF, "\t\t\t{\n"); xform_printf(OF, "\t\t\t\t\"name\": \"%s\",\n", json_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t\"score\": %d\n", player->score); xform_printf(OF, "\t\t\t}"); printed = 1; } xform_printf(OF, "\n\t\t]"); } void json_display_starmade_player_info(struct qserver *server) { struct player *player; int printed = 0; xform_printf(OF, ",\n\t\t\"players\": [\n"); player = server->players; for ( ; player != NULL; player = player->next) { if (printed) { xform_printf(OF, ",\n"); } xform_printf(OF, "\t\t\t{\n"); xform_printf(OF, "\t\t\t\t\"name\": \"%s\",\n", json_escape(xform_name(player->name, server))); if (player->connect_time != 0) { xform_printf(OF, "\t\t\t\t\"time\": \"%s\",\n", json_escape(play_time(player->connect_time, 2))); } json_display_player_info_info(player); xform_printf(OF, "\t\t\t\t\"ping\": %d\n", player->ping); xform_printf(OF, "\t\t\t}"); printed = 1; } xform_printf(OF, "\n\t\t]"); } char * json_escape(char *string) { static unsigned char _buf[4][MAXSTRLEN + 8]; static int _buf_index = 0; unsigned char *result, *b, *end; unsigned int c; if (string == NULL) { return (""); } result = &_buf[_buf_index][0]; _buf_index = (_buf_index + 1) % 4; end = &result[MAXSTRLEN]; b = result; for ( ; *string && b < end; string++) { c = *string; switch (c) { case '"': *b++ = '\\'; *b++ = '"'; continue; case '\\': *b++ = '\\'; *b++ = '\\'; continue; default: break; } // Validate character // http://www.w3.org/TR/2000/REC-xml-20001006#charsets if (! ( (0x09 == c) || (0xA == c) || (0xD == c) || ((0x20 <= c) && (0xD7FF >= c)) || ((0xE000 <= c) && (0xFFFD >= c)) || ((0x10000 <= c) && (0x10FFFF >= c)) ) ) { if (show_errors) { fprintf(stderr, "Encoding error (%d) for U+%x, D+%d\n", 1, c, c); } } // JSON is always unicode-encoded, see RFC 4627 // https://www.ietf.org/rfc/rfc4627.txt else { unsigned char tempbuf[10] = { 0 }; unsigned char *buf = &tempbuf[0]; int bytes = 0; int error = 1; // Valid character ranges if ( (0x09 == c) || (0xA == c) || (0xD == c) || ((0x20 <= c) && (0xD7FF >= c)) || ((0xE000 <= c) && (0xFFFD >= c)) || ((0x10000 <= c) && (0x10FFFF >= c)) ) { error = 0; } if (c < 0x80) { /* 0XXX XXXX one byte */ buf[0] = c; bytes = 1; } else if (c < 0x0800) { /* 110X XXXX two bytes */ buf[0] = 0xC0 | (0x03 & (c >> 6)); buf[1] = 0x80 | (0x3F & c); bytes = 2; } else if (c < 0x10000) { /* 1110 XXXX three bytes */ buf[0] = 0xE0 | (c >> 12); buf[1] = 0x80 | ((c >> 6) & 0x3F); buf[2] = 0x80 | (c & 0x3F); bytes = 3; if ((c == UTF8BYTESWAPNOTACHAR) || (c == UTF8NOTACHAR)) { error = 3; } } else if (c < 0x10FFFF) { /* 1111 0XXX four bytes */ buf[0] = 0xF0 | (c >> 18); buf[1] = 0x80 | ((c >> 12) & 0x3F); buf[2] = 0x80 | ((c >> 6) & 0x3F); buf[3] = 0x80 | (c & 0x3F); bytes = 4; if (c > UTF8MAXFROMUCS4) { error = 4; } } else if (c < 0x4000000) { /* 1111 10XX five bytes */ buf[0] = 0xF8 | (c >> 24); buf[1] = 0x80 | (c >> 18); buf[2] = 0x80 | ((c >> 12) & 0x3F); buf[3] = 0x80 | ((c >> 6) & 0x3F); buf[4] = 0x80 | (c & 0x3F); bytes = 5; error = 5; } else if (c < 0x80000000) { /* 1111 110X six bytes */ buf[0] = 0xFC | (c >> 30); buf[1] = 0x80 | ((c >> 24) & 0x3F); buf[2] = 0x80 | ((c >> 18) & 0x3F); buf[3] = 0x80 | ((c >> 12) & 0x3F); buf[4] = 0x80 | ((c >> 6) & 0x3F); buf[5] = 0x80 | (c & 0x3F); bytes = 6; error = 6; } else { error = 7; } if (error) { int i; fprintf(stderr, "UTF-8 encoding error (%d) for U+%x, D+%d : ", error, c, c); for (i = 0; i < bytes; i++) { fprintf(stderr, "0x%02x ", buf[i]); } fprintf(stderr, "\n"); } else { int i; for (i = 0; i < bytes; ++i) { *b++ = buf[i]; } } } } *b = '\0'; return ((char *)result); } qstat-2.17/display_json.h000066400000000000000000000043751412457473700154750ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * steve@qstat.org * http://www.qstat.org * * Inspired by QuakePing by Len Norton * * JSON output * Contributed by Steve Teuber * * Copyright 2013 by Steve Teuber * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_DISPLAY_JSON_H #define QSTAT_DISPLAY_JSON_H #include "qstat.h" #include "qserver.h" extern int json_display; extern int json_encoding; extern int json_printed; void json_footer(); void json_header(); void json_protocols(); void json_version(); char *json_escape(char *string); void json_display_server(struct qserver *server); void json_display_server_rules(struct qserver *server); void json_display_bfbc2_player_info(struct qserver *server); void json_display_bfris_player_info(struct qserver *server); void json_display_descent3_player_info(struct qserver *server); void json_display_doom3_player_info(struct qserver *server); void json_display_eye_player_info(struct qserver *server); void json_display_farcry_player_info(struct qserver *server); void json_display_fl_player_info(struct qserver *server); void json_display_ghostrecon_player_info(struct qserver *server); void json_display_halflife_player_info(struct qserver *server); void json_display_player_info_info(struct player *player); void json_display_player_info(struct qserver *server); void json_display_q2_player_info(struct qserver *server); void json_display_q_player_info(struct qserver *server); void json_display_qw_player_info(struct qserver *server); void json_display_ravenshield_player_info(struct qserver *server); void json_display_savage_player_info(struct qserver *server); void json_display_starmade_player_info(struct qserver *server); void json_display_tee_player_info(struct qserver *server); void json_display_tm_player_info(struct qserver *server); void json_display_tribes2_player_info(struct qserver *server); void json_display_tribes_player_info(struct qserver *server); void json_display_ts2_player_info(struct qserver *server); void json_display_ts3_player_info(struct qserver *server); void json_display_unreal_player_info(struct qserver *server); void json_display_ventrilo_player_info(struct qserver *server); void json_display_wic_player_info(struct qserver *server); #endif qstat-2.17/doom3.c000066400000000000000000000374511412457473700140140ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * Doom3 / Quake4 protocol * Copyright 2005 Ludwig Nussel * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #include #ifndef _WIN32 #include #endif #include #include #include "qstat.h" #include "debug.h" #include "assert.h" static const char doom3_master_query[] = "\xFF\xFFgetServers\x00\x00\x00\x00\x00\x00"; // version ^^^^^^^^^^^^^^^^ // null terminated mod string ^^^^ // filterbyte ^^^^ static const char quake4_master_query[] = "\xFF\xFFgetServers\x00\x00\x00\x00\x00\x00\x00\x00"; // version ^^^^^^^^^^^^^^^^ // null terminated mod string ^^^^ // null terminated player string ^^^^ // null terminated clan string ^^^^ // filterbyte ^^^^ static unsigned put_param_string(struct qserver *server, const char *paramname, char *buf, unsigned buflen, unsigned off) { char *val = get_param_value(server, paramname, NULL); if (val && (strlen(val) < buflen - off - 2)) { strcpy(buf + off, val); off += strlen(val) + 1; } else { buf[off++] = '\0'; } return (off); } static char * build_doom3_masterfilter(struct qserver *server, char *buf, unsigned *buflen, int q4) { int flen = 0; char *pkt, *r, *sep = ""; unsigned char b = 0; char *proto = server->query_arg; unsigned ver; unsigned off = 13; if (!proto) { if (q4) { *buflen = sizeof(quake4_master_query); return ((char *)quake4_master_query); } else { *buflen = sizeof(doom3_master_query); return ((char *)doom3_master_query); } } ver = (atoi(proto) & 0xFFFF) << 16; proto = strchr(proto, '.'); if (proto && *++proto) { ver |= (atoi(proto) & 0xFFFF); } if (q4) { ver |= 1 << 31; // third party flag } memcpy(buf, doom3_master_query, sizeof(doom3_master_query)); put_long_little(ver, buf + off); off += 4; off = put_param_string(server, "game", buf, *buflen, off); if (q4) { off = put_param_string(server, "player", buf, *buflen, off); off = put_param_string(server, "clan", buf, *buflen, off); } pkt = get_param_value(server, "status", NULL); r = pkt; while (pkt && sep) { sep = strchr(r, ':'); if (sep) { flen = sep - r; } else { flen = strlen(r); } if (strncmp(r, "password", flen) == 0) { b |= 0x01; } else if (strncmp(r, "nopassword", flen) == 0) { b |= 0x02; } else if (strncmp(r, "notfull", flen) == 0) { b |= 0x04; } else if (strncmp(r, "notfullnotempty", flen) == 0) { b |= 0x08; } r = sep + 1; } pkt = get_param_value(server, "gametype", NULL); if (pkt) { if (strncmp(pkt, "dm", flen) == 0) { b |= 0x10; } else if (strncmp(pkt, "tourney", flen) == 0) { b |= 0x20; } else if (strncmp(pkt, "tdm", flen) == 0) { b |= 0x30; } } buf[off++] = (char)b; *buflen = off; return (buf); } query_status_t send_doom3master_request_packet(struct qserver *server) { int rc = 0; int packet_len = -1; char *packet = NULL; char query_buf[4096] = { 0 }; server->next_player_info = NO_PLAYER_INFO; packet_len = sizeof(query_buf); packet = build_doom3_masterfilter(server, query_buf, (unsigned *)&packet_len, 0); rc = send(server->fd, packet, packet_len, 0); if (rc == SOCKET_ERROR) { return (send_error(server, rc)); } if (server->retry1 == n_retries) { gettimeofday(&server->packet_time1, NULL); server->n_requests++; } else { server->n_retries++; } server->retry1--; server->n_packets++; return (INPROGRESS); } query_status_t send_quake4master_request_packet(struct qserver *server) { int rc = 0; int packet_len = -1; char *packet = NULL; char query_buf[4096] = { 0 }; server->next_player_info = NO_PLAYER_INFO; packet_len = sizeof(query_buf); packet = build_doom3_masterfilter(server, query_buf, (unsigned *)&packet_len, 1); rc = send(server->fd, packet, packet_len, 0); if (rc == SOCKET_ERROR) { return (send_error(server, rc)); } if (server->retry1 == n_retries) { gettimeofday(&server->packet_time1, NULL); server->n_requests++; } else { server->n_retries++; } server->retry1--; server->n_packets++; return (INPROGRESS); } static const char doom3_masterresponse[] = "\xFF\xFFservers"; query_status_t deal_with_doom3master_packet(struct qserver *server, char *rawpkt, int pktlen) { char *pkt, *dest; int len; server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); if ((pktlen < sizeof(doom3_masterresponse) + 6) || // at least one server (pktlen - sizeof(doom3_masterresponse)) % 6 || (memcmp(doom3_masterresponse, rawpkt, sizeof(doom3_masterresponse)) != 0)) { server->server_name = SERVERERROR; server->master_pkt_len = 0; malformed_packet(server, "too short or invalid response"); return (PKT_ERROR); } server->retry1 = 0; // received at least one packet so no need to retry pkt = rawpkt + sizeof(doom3_masterresponse); len = pktlen - sizeof(doom3_masterresponse); server->master_pkt = (char *)realloc(server->master_pkt, server->master_pkt_len + len); dest = server->master_pkt + server->master_pkt_len; server->master_pkt_len += len; while (len > 0) { memcpy(dest, pkt, 4); dest[4] = pkt[5]; dest[5] = pkt[4]; dest += 6; pkt += 6; len -= 6; } assert(len == 0); server->n_servers = server->master_pkt_len / 6; debug(2, "%d servers added", server->n_servers); return (INPROGRESS); } static const char doom3_inforesponse[] = "\xFF\xFFinfoResponse"; static unsigned MAX_DOOM3_ASYNC_CLIENTS = 32; static unsigned MAX_WOLF_ASYNC_CLIENTS = 16; static query_status_t _deal_with_doom3_packet(struct qserver *server, char *rawpkt, int pktlen, unsigned version) { char *ptr = rawpkt; char *end = rawpkt + pktlen; int type = 0; int size = 0; int tail_size = 4; int viewers = 0; int tv = 0; unsigned num_players = 0; unsigned challenge = 0; unsigned protocolver = 0; char tmp[32]; server->n_servers++; if (server->server_name == NULL) { server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); } else { gettimeofday(&server->packet_time1, NULL); } // Check if correct reply if ((pktlen < sizeof(doom3_inforesponse) + 4 + 4 + 1) || (memcmp(doom3_inforesponse, ptr, sizeof(doom3_inforesponse)) != 0)) { malformed_packet(server, "too short or invalid response"); return (PKT_ERROR); } ptr += sizeof(doom3_inforesponse); if (5 == version) { // TaskID ptr += 4; // osmask + ranked tail_size++; } challenge = swap_long_from_little(ptr); ptr += 4; protocolver = swap_long_from_little(ptr); ptr += 4; snprintf(tmp, sizeof(tmp), "%u.%u", protocolver >> 16, protocolver & 0xFFFF); debug(2, "challenge: 0x%08X, protocol: %s (0x%X)", challenge, tmp, protocolver); server->protocol_version = protocolver; add_rule(server, "protocol", tmp, NO_FLAGS); if (5 == version) { // Size Long size = swap_long_from_little(ptr); debug(2, "Size = %d", size); ptr += 4; } // Commented out until we have a better way to specify the expected version // This is due to prey demo requiring version 4 yet prey retail version 3 /* * if( protocolver >> 16 != version ) * { * malformed_packet(server, "protocol version %u, expected %u", protocolver >> 16, version ); * return PKT_ERROR; * } */ while (ptr < end) { // server info: // name value pairs null seperated // empty name && value signifies the end of section char *key, *val; unsigned keylen, vallen; key = ptr; ptr = memchr(ptr, '\0', end - ptr); if (!ptr) { malformed_packet(server, "no rule key"); return (PKT_ERROR); } keylen = ptr - key; val = ++ptr; ptr = memchr(ptr, '\0', end - ptr); if (!ptr) { malformed_packet(server, "no rule value"); return (PKT_ERROR); } vallen = ptr - val; ++ptr; if ((keylen == 0) && (vallen == 0)) { type = 1; break; // end } debug(2, "key:%s=%s:", key, val); // Lets see what we've got if (0 == strcasecmp(key, "si_name")) { server->server_name = strdup(val); } else if (0 == strcasecmp(key, "fs_game")) { server->game = strdup(val); } #if 0 else if (0 == strcasecmp(key, "si_version")) { // format: x // DOOM 1.0.1262.win-x86 Jul 8 2004 16:46:37 server->protocol_version = atoi(val + 1); } #endif else if (0 == strcasecmp(key, "si_map")) { if ((5 == version) || (6 == version)) { // ET:QW reports maps/.entities // so we strip that here if it exists char *tmpp = strstr(val, ".entities"); if (NULL != tmpp) { *tmpp = '\0'; } if (0 == strncmp(val, "maps/", 5)) { val += 5; } } server->map_name = strdup(val); } else if (0 == strcasecmp(key, "si_maxplayers")) { server->max_players = atoi(val); } else if (0 == strcasecmp(key, "ri_maxViewers")) { char max[20]; sprintf(max, "%d", server->max_players); add_rule(server, "si_maxplayers", max, NO_FLAGS); server->max_players = atoi(val); } else if (0 == strcasecmp(key, "ri_numViewers")) { viewers = atoi(val); tv = 1; } add_rule(server, key, val, NO_FLAGS); } if (type != 1) { // no more info should be player headers here as we // requested it malformed_packet(server, "player info missing"); return (PKT_ERROR); } // now each player details while (ptr < end - tail_size) { struct player *player; char *val; unsigned char player_id = *ptr++; short ping = 0; unsigned rate = 0; if (((6 == version) && (MAX_WOLF_ASYNC_CLIENTS == player_id)) || (MAX_DOOM3_ASYNC_CLIENTS == player_id)) { break; } debug(2, "ID = %d\n", player_id); // Note: id's are not steady if (ptr + 7 > end) {// 2 ping + 4 rate + empty player name ('\0') // run off the end and shouldnt have malformed_packet(server, "player info too short"); return (PKT_ERROR); } /* * if ( 6 == version ) * { * // Playerid is broken in wolf its always 0 * player_id = num_players; * } */ player = add_player(server, player_id); if (!player) { malformed_packet(server, "duplicate player id"); return (PKT_ERROR); } // doesnt support score so set a sensible default player->score = 0; player->frags = 0; // Ping ping = swap_short_from_little(ptr); player->ping = ping; ptr += 2; if ((5 != version) || (0xa0013 >= protocolver)) { // No Rate in ETQW since v1.4 ( protocol v10.19 ) // Rate rate = swap_long_from_little(ptr); { char buf[16]; snprintf(buf, sizeof(buf), "%u", rate); player_add_info(player, "rate", buf, NO_FLAGS); } ptr += 4; } if ((5 == version) && (((0xd0009 == protocolver) || (0xd000a == protocolver)) && (0 != num_players))) { // v13.9 or v13.10 // Fix the packet offset due to the single bit used for bot // which realigns at the byte boundary for the player name ptr++; } // Name val = ptr; ptr = memchr(ptr, '\0', end - ptr); if (!ptr) { malformed_packet(server, "player name not null terminated"); return (PKT_ERROR); } player->name = strdup(val); ptr++; switch (version) { case 2: // Quake 4 val = ptr; ptr = memchr(ptr, '\0', end - ptr); if (!ptr) { malformed_packet(server, "player clan not null terminated"); return (PKT_ERROR); } player->tribe_tag = strdup(val); ptr++; debug(2, "Player[%d] = %s, ping %hu, rate %u, id %hhu, clan %s", num_players, player->name, ping, rate, player_id, player->tribe_tag); break; case 5: // ETQW case 6: // Wolfenstien if (0xa0011 <= protocolver) { // clan tag since 10.17 // clantag position ptr++; // clantag val = ptr; ptr = memchr(ptr, '\0', end - ptr); if (!ptr) { malformed_packet(server, "player clan not null terminated"); return (PKT_ERROR); } player->tribe_tag = strdup(val); ptr++; } // Bot flag if ((0xd0009 == protocolver) || (0xd000a == protocolver)) { // v13.9 or v13.10 // Bot flag is a single bit so need to realign everything from here on in :( int i; unsigned char *tp = (unsigned char *)ptr; player->type_flag = (*tp) << 7; // alignment is reset at the end for (i = 0; i < 8 && tp < (unsigned char *)end; i++) { *tp = (*tp) >> 1 | *(tp + 1) << 7; tp++; } } else { player->type_flag = *ptr++; } if (0xa0011 <= protocolver) { // clan tag since 10.17 debug(2, "Player[%d] = %s, ping %hu, rate %u, id %hhu, bot %hu, clan %s", num_players, player->name, ping, rate, player_id, player->type_flag, player->tribe_tag); } else { debug(2, "Player[%d] = %s, ping %hu, rate %u, id %hhu, bot %hu", num_players, player->name, ping, rate, player_id, player->type_flag); } break; default: debug(2, "Player[%d] = %s, ping %hu, rate %u, id %hhu", num_players, player->name, ping, rate, player_id); } ++num_players; } if (end - ptr >= 4) { // OS Mask snprintf(tmp, sizeof(tmp), "0x%X", swap_long_from_little(ptr)); add_rule(server, "osmask", tmp, NO_FLAGS); debug(2, "osmask %s", tmp); ptr += 4; if ((5 == version) && (end - ptr >= 1)) { // Ranked flag snprintf(tmp, sizeof(tmp), "%hhu", *ptr++); add_rule(server, "ranked", tmp, NO_FLAGS); if (end - ptr >= 5) { // Time Left snprintf(tmp, sizeof(tmp), "%d", swap_long_from_little(ptr)); add_rule(server, "timeleft", tmp, NO_FLAGS); ptr += 4; // Game State snprintf(tmp, sizeof(tmp), "%hhu", *ptr++); add_rule(server, "gamestate", tmp, NO_FLAGS); if (end - ptr >= 1) { // Server Type unsigned char servertype = *ptr++; snprintf(tmp, sizeof(tmp), "%hhu", servertype); add_rule(server, "servertype", tmp, NO_FLAGS); switch (servertype) { case 0: // Regular Server // Interested Clients snprintf(tmp, sizeof(tmp), "%hhu", *ptr++); add_rule(server, "interested_clients", tmp, NO_FLAGS); break; case 1: // TV Server // Connected Clients snprintf(tmp, sizeof(tmp), "%d", swap_long_from_little(ptr)); ptr += 4; add_rule(server, "interested_clients", tmp, NO_FLAGS); // Max Clients snprintf(tmp, sizeof(tmp), "%d", swap_long_from_little(ptr)); ptr += 4; add_rule(server, "interested_clients", tmp, NO_FLAGS); break; default: // Unknown if (show_errors) { fprintf(stderr, "Unknown server type %d\n", servertype); } } } } } } else { malformed_packet(server, "osmask missing"); } #if 0 if (end - ptr) { malformed_packet(server, "%ld byes left", end - ptr); } #endif if (0 == tv) { debug(2, "Num players = %d", num_players); server->num_players = num_players; } else { server->num_players = viewers; } return (DONE_FORCE); } query_status_t deal_with_doom3_packet(struct qserver *server, char *rawpkt, int pktlen) { return (_deal_with_doom3_packet(server, rawpkt, pktlen, 1)); } query_status_t deal_with_quake4_packet(struct qserver *server, char *rawpkt, int pktlen) { return (_deal_with_doom3_packet(server, rawpkt, pktlen, 2)); } query_status_t deal_with_prey_demo_packet(struct qserver *server, char *rawpkt, int pktlen) { return (_deal_with_doom3_packet(server, rawpkt, pktlen, 4)); } query_status_t deal_with_prey_packet(struct qserver *server, char *rawpkt, int pktlen) { return (_deal_with_doom3_packet(server, rawpkt, pktlen, 3)); } query_status_t deal_with_etqw_packet(struct qserver *server, char *rawpkt, int pktlen) { return (_deal_with_doom3_packet(server, rawpkt, pktlen, 5)); } query_status_t deal_with_wolf_packet(struct qserver *server, char *rawpkt, int pktlen) { return (_deal_with_doom3_packet(server, rawpkt, pktlen, 6)); } qstat-2.17/doom3.h000066400000000000000000000022731412457473700140130ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * Doom3 / Quake4 protocol * Copyright 2005 Ludwig Nussel * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_DOOM3_H #define QSTAT_DOOM3_H #define DOOM3_DEFAULT_PORT 27666 #define DOOM3_MASTER_DEFAULT_PORT 27650 #define QUAKE4_DEFAULT_PORT 28004 #define QUAKE4_MASTER_DEFAULT_PORT 27650 #define PREY_DEFAULT_PORT 27719 #define PREY_MASTER_DEFAULT_PORT 27655 #define ETQW_DEFAULT_PORT 27733 #define WOLF_DEFAULT_PORT 27758 query_status_t send_doom3master_request_packet(struct qserver *server); query_status_t deal_with_doom3master_packet(struct qserver *server, char *rawpkt, int pktlen); query_status_t deal_with_doom3_packet(struct qserver *server, char *rawpkt, int pktlen); query_status_t send_quake4master_request_packet(struct qserver *server); query_status_t deal_with_quake4_packet(struct qserver *server, char *rawpkt, int pktlen); query_status_t deal_with_prey_packet(struct qserver *server, char *rawpkt, int pktlen); query_status_t deal_with_etqw_packet(struct qserver *server, char *rawpkt, int pktlen); query_status_t deal_with_wolf_packet(struct qserver *server, char *rawpkt, int pktlen); #endif qstat-2.17/farmsim.c000066400000000000000000000122471412457473700144250ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * Crysis query protocol * Copyright 2012 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms * */ #include #ifndef _WIN32 #include #include #include #else #include #endif #include #include #include #include "debug.h" #include "utils.h" #include "qstat.h" #include "md5.h" #include "packet_manip.h" char * decode_farmsim_val(char *val) { // Very basic html conversion val = str_replace(val, """, "\""); return (str_replace(val, "&", "&")); } query_status_t send_farmsim_request_packet(struct qserver *server) { char buf[256], *code; server->saved_data.pkt_max = -1; code = get_param_value(server, "code", ""); sprintf(buf, "GET /feed/dedicated-server-stats.xml?code=%s HTTP/1.1\015\012User-Agent: qstat\015\012\015\012", code); return (send_packet(server, buf, strlen(buf))); } query_status_t valid_farmsim_response(struct qserver *server, char *rawpkt, int pktlen) { char *s; int len; int cnt = packet_count(server); if ((0 == cnt) && (0 != strncmp("HTTP/1.1 200 OK", rawpkt, 15))) { // not valid response debug(2, "Invalid"); return (REQ_ERROR); } s = strnstr(rawpkt, "Content-Length: ", pktlen); if (s == NULL) { // not valid response debug(2, "Invalid (no content-length)"); return (INPROGRESS); } s += 16; // TODO: remove this bug work around if (*s == ':') { s += 2; } if (sscanf(s, "%d", &len) != 1) { debug(2, "Invalid (no length)"); return (INPROGRESS); } s = strnstr(rawpkt, "\015\012\015\012", pktlen); if (s == NULL) { debug(2, "Invalid (no end of header"); return (INPROGRESS); } s += 4; if (pktlen != (s - rawpkt + len)) { debug(2, "Outstanding data"); return (INPROGRESS); } debug(2, "Valid data"); return (DONE_FORCE); } char * farmsim_xml_attrib(char *line, char *name) { char *q, *p, *val; p = strstr(line, name); if (p == NULL) { return (NULL); } p += strlen(name); if (strlen(p) < 4) { return (NULL); } p += 2; q = strchr(p, '"'); if (q == NULL) { return (NULL); } *q = '\0'; val = strdup(p); *q = '"'; debug(4, "%s = %s", name, val); return (val); } query_status_t deal_with_farmsim_packet(struct qserver *server, char *rawpkt, int pktlen) { char *s, *val, *line; query_status_t state = INPROGRESS; debug(2, "processing..."); if (!server->combined) { state = valid_farmsim_response(server, rawpkt, pktlen); server->retry1 = n_retries; if (server->n_requests == 0) { server->ping_total = time_delta(&packet_recv_time, &server->packet_time1); server->n_requests++; } switch (state) { case INPROGRESS: { // response fragment recieved int pkt_id; int pkt_max; // We're expecting more to come debug(5, "fragment recieved..."); pkt_id = packet_count(server); pkt_max = pkt_id + 1; if (!add_packet(server, 0, pkt_id, pkt_max, pktlen, rawpkt, 1)) { // fatal error e.g. out of memory return (MEM_ERROR); } // combine_packets will call us recursively return (combine_packets(server)); } case DONE_FORCE: break; // single packet response fall through default: return (state); } } if (state != DONE_FORCE) { state = valid_farmsim_response(server, rawpkt, pktlen); switch (state) { case DONE_FORCE: break; // actually process default: return (state); } } // Correct ping // Not quite right but gives a good estimate server->ping_total = (server->ping_total * server->n_requests) / 2; debug(3, "processing response..."); s = rawpkt; // Ensure we're null terminated (will only loose the last \x0a) s[pktlen - 1] = '\0'; s = decode_farmsim_val(s); line = strtok(s, "\012"); // NOTE: id=XXX and msg=XXX will be processed by the mod following the one they where the response of while (line != NULL) { debug(4, "LINE: %s\n", line); if (strstr(line, " // Server Name val = farmsim_xml_attrib(line, "name"); if (val != NULL) { server->server_name = val; } else { server->server_name = strdup("Unknown"); } // Map Name val = farmsim_xml_attrib(line, "mapName"); if (val != NULL) { server->map_name = val; } else { server->map_name = strdup("Default"); } } else if (strstr(line, " debug(1, "max_players = atoi(val); free(val); } else { server->max_players = get_param_ui_value(server, "maxplayers", 1); } // Num Players val = farmsim_xml_attrib(line, "numUsed"); if (val != NULL) { server->num_players = atoi(val); free(val); } else { server->num_players = 0; } } line = strtok(NULL, "\012"); } gettimeofday(&server->packet_time1, NULL); return (DONE_FORCE); } qstat-2.17/farmsim.h000066400000000000000000000006631412457473700144310ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * Crysis protocol * Copyright 2012 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_FARMSIM_H #define QSTAT_FARMSIM_H #include "qserver.h" // Packet processing methods query_status_t deal_with_farmsim_packet(struct qserver *server, char *pkt, int pktlen); query_status_t send_farmsim_request_packet(struct qserver *server); #endif qstat-2.17/fl.c000066400000000000000000000252571412457473700133750ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * Frontlines-Fuel of War protocol * Copyright 2008 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms * */ #include #ifndef _WIN32 #include #include #include #else #include #endif #include #include #include "debug.h" #include "qstat.h" #include "packet_manip.h" #define FL_GETCHALLENGE "\xFF\xFF\xFF\xFF\x57" #define FL_CHALLENGERESPONSE 0x41 #define FL_INFO "\xFF\xFF\xFF\xFF\x46LSQ" #define FL_INFORESPONSE 0x49 #define FL_PLAYER "\xFF\xFF\xFF\xFF\x55" #define FL_PLAYERRESPONSE 0x44 #define FL_RULES "\xFF\xFF\xFF\xFF\x56" #define FL_RULESRESPONSE 0x45 struct fl_status { unsigned sent_challenge : 1; unsigned have_challenge : 1; unsigned sent_info : 1; unsigned have_info : 1; unsigned sent_player : 1; unsigned have_player : 1; unsigned sent_rules : 1; unsigned have_rules : 1; unsigned challenge; unsigned char type; }; query_status_t send_fl_request_packet(struct qserver *server) { struct fl_status *status = (struct fl_status *)server->master_query_tag; if (SOCKET_ERROR == qserver_send_initial(server, FL_INFO, sizeof(FL_INFO) - 1)) { return (SOCKET_ERROR); } status->sent_info = 1; status->type = 0; return (INPROGRESS); } query_status_t send_fl_rule_request_packet(struct qserver *server) { struct fl_status *status = (struct fl_status *)server->master_query_tag; if (1 >= status->type) { // Not supported return (PKT_ERROR); } if (!get_server_rules && !get_player_info) { return (DONE_FORCE); } do { if (!status->have_challenge) { debug(3, "sending challenge"); if (SOCKET_ERROR == qserver_send_initial(server, FL_GETCHALLENGE, sizeof(FL_GETCHALLENGE) - 1)) { return (SOCKET_ERROR); } status->sent_challenge = 1; break; } else if (get_server_rules && !status->have_rules) { char buf[sizeof(FL_RULES) - 1 + 4] = FL_RULES; memcpy(buf + sizeof(FL_RULES) - 1, &status->challenge, 4); debug(3, "sending rule query"); if (SOCKET_ERROR == qserver_send_initial(server, buf, sizeof(buf))) { return (SOCKET_ERROR); } status->sent_rules = 1; break; } else if (get_player_info && !status->have_player) { char buf[sizeof(FL_PLAYER) - 1 + 4] = FL_PLAYER; memcpy(buf + sizeof(FL_PLAYER) - 1, &status->challenge, 4); debug(3, "sending player query"); if (SOCKET_ERROR == qserver_send_initial(server, buf, sizeof(buf))) { return (SOCKET_ERROR); } status->sent_player = 1; break; } else { debug(3, "timeout"); // we are probably called due to timeout, restart. status->have_challenge = 0; status->have_rules = 0; } } while (1); return (INPROGRESS); } query_status_t deal_with_fl_packet(struct qserver *server, char *rawpkt, int pktlen) { struct fl_status *status = (struct fl_status *)server->master_query_tag; char *pkt = rawpkt; char buf[16]; char *str; unsigned cnt; unsigned short tmp_short; if (server->server_name == NULL) { server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); server->n_requests++; } if (pktlen < 5) { goto out_too_short; } if (0 == memcmp(pkt, "\xFF\xFF\xFF\xFE", 4)) { // fragmented packet unsigned char pkt_index, pkt_max; unsigned int pkt_id; SavedData *sdata; if (pktlen < 9) { goto out_too_short; } // format: // int Header // int RequestId // byte PacketNumber // byte NumPackets // Short SizeOfPacketSplits // Header pkt += 4; // RequestId pkt_id = ntohl(*(long *)pkt); debug(3, "RequestID: %d", pkt_id); pkt += 4; // The next two bytes are: // 1. the max packets sent ( byte ) // 2. the index of this packet starting from 0 ( byte ) // 3. Size of the split ( short ) if (pktlen < 10) { goto out_too_short; } // PacketNumber pkt_index = ((unsigned char)*pkt); // NumPackates pkt_max = ((unsigned char)*(pkt + 1)); // SizeOfPacketSplits debug(3, "packetid[2]: 0x%hhx => idx: %hhu, max: %hhu", *pkt, pkt_index, pkt_max); pkt += 4; pktlen -= 12; // pkt_max is the total number of packets expected // pkt_index is a bit mask of the packets received. if (server->saved_data.data == NULL) { sdata = &server->saved_data; } else { sdata = (SavedData *)calloc(1, sizeof(SavedData)); sdata->next = server->saved_data.next; server->saved_data.next = sdata; } sdata->pkt_index = pkt_index; sdata->pkt_max = pkt_max; sdata->pkt_id = pkt_id; sdata->datalen = pktlen; sdata->data = (char *)malloc(pktlen); if (NULL == sdata->data) { malformed_packet(server, "Out of memory"); return (MEM_ERROR); } memcpy(sdata->data, pkt, sdata->datalen); // combine_packets will call us recursively return (combine_packets(server)); } else if (0 != memcmp(pkt, "\xFF\xFF\xFF\xFF", 4)) { malformed_packet(server, "invalid packet header"); return (PKT_ERROR); } pkt += 4; pktlen -= 4; pktlen -= 1; debug(2, "FL type = 0x%x", *pkt); switch (*pkt++) { case FL_CHALLENGERESPONSE: if (pktlen < 4) { goto out_too_short; } memcpy(&status->challenge, pkt, 4); // do not count challenge as retry if (!status->have_challenge && (server->retry1 != n_retries)) { ++server->retry1; if (server->n_retries) { --server->n_retries; } } status->have_challenge = 1; debug(3, "challenge %x", status->challenge); break; case FL_INFORESPONSE: if (pktlen < 1) { goto out_too_short; } status->type = *pkt; if ((*pkt > 1) && (get_server_rules || get_player_info)) { server->next_rule = ""; // trigger calling send_fl_rule_request_packet } snprintf(buf, sizeof(buf), "%hhX", *pkt); add_rule(server, "protocol", buf, 0); pktlen--; pkt++; // ServerName str = memchr(pkt, '\0', pktlen); if (!str) { goto out_too_short; } server->server_name = strdup(pkt); pktlen -= str - pkt + 1; pkt += str - pkt + 1; // MapName str = memchr(pkt, '\0', pktlen); if (!str) { goto out_too_short; } server->map_name = strdup(pkt); pktlen -= str - pkt + 1; pkt += str - pkt + 1; // ModName str = memchr(pkt, '\0', pktlen); if (!str) { goto out_too_short; } server->game = strdup(pkt); add_rule(server, "modname", pkt, 0); pktlen -= str - pkt + 1; pkt += str - pkt + 1; // GameMode str = memchr(pkt, '\0', pktlen); if (!str) { goto out_too_short; } add_rule(server, "gamemode", pkt, 0); pktlen -= str - pkt + 1; pkt += str - pkt + 1; // GameDescription str = memchr(pkt, '\0', pktlen); if (!str) { goto out_too_short; } add_rule(server, "gamedescription", pkt, 0); pktlen -= str - pkt + 1; pkt += str - pkt + 1; // GameVersion str = memchr(pkt, '\0', pktlen); if (!str) { goto out_too_short; } add_rule(server, "gameversion", pkt, 0); pktlen -= str - pkt + 1; pkt += str - pkt + 1; if (pktlen < 13) { goto out_too_short; } // GamePort tmp_short = ((unsigned short)pkt[0] << 8) | ((unsigned short)pkt[1]); change_server_port(server, tmp_short, 0); pkt += 2; // Num Players server->num_players = (unsigned char)*pkt++; // Max Players server->max_players = (unsigned char)*pkt++; // Dedicated add_rule(server, "dedicated", ('d' == *pkt++) ? "1" : "0", 0); // OS switch (*pkt) { case 'l': add_rule(server, "sv_os", "linux", 0); break; case 'w': add_rule(server, "sv_os", "windows", 0); break; default: buf[0] = *pkt; buf[1] = '\0'; add_rule(server, "sv_os", buf, 0); break; } pkt++; // Passworded add_rule(server, "passworded", (*pkt++) ? "1" : "0", 0); // Anticheat add_rule(server, "passworded", (*pkt++) ? "1" : "0", 0); // FrameTime sprintf(buf, "%hhu", *pkt++); add_rule(server, "frametime", buf, 0); // Round sprintf(buf, "%hhu", *pkt++); add_rule(server, "round", buf, 0); // RoundMax sprintf(buf, "%hhu", *pkt++); add_rule(server, "roundmax", buf, 0); // RoundSeconds tmp_short = ((unsigned short)pkt[0] << 8) | ((unsigned short)pkt[1]); sprintf(buf, "%hu", tmp_short); add_rule(server, "roundseconds", buf, 0); pkt += 2; status->have_info = 1; server->retry1 = n_retries; server->next_player_info = server->num_players; break; case FL_RULESRESPONSE: if (pktlen < 2) { goto out_too_short; } cnt = ((unsigned char)pkt[0] << 8) + ((unsigned char)pkt[1]); pktlen -= 2; pkt += 2; debug(3, "num_rules: %d", cnt); for ( ; cnt && pktlen > 0; --cnt) { char *key, *value; str = memchr(pkt, '\0', pktlen); if (!str) { break; } key = pkt; pktlen -= str - pkt + 1; pkt += str - pkt + 1; str = memchr(pkt, '\0', pktlen); if (!str) { break; } value = pkt; pktlen -= str - pkt + 1; pkt += str - pkt + 1; add_rule(server, key, value, NO_FLAGS); } if (cnt) { malformed_packet(server, "packet contains too few rules, missing %d", cnt); server->missing_rules = 1; } if (pktlen) { malformed_packet(server, "garbage at end of rules, %d bytes left", pktlen); } status->have_rules = 1; server->retry1 = n_retries; break; case FL_PLAYERRESPONSE: if (pktlen < 1) { goto out_too_short; } cnt = (unsigned char)pkt[0]; pktlen -= 1; pkt += 1; debug(3, "num_players: %d", cnt); for ( ; cnt && pktlen > 0; --cnt) { unsigned idx; const char *name; struct player *p; // Index idx = *pkt++; --pktlen; // PlayerName str = memchr(pkt, '\0', pktlen); if (!str) { break; } name = pkt; pktlen -= str - pkt + 1; pkt += str - pkt + 1; if (pktlen < 8) { goto out_too_short; } debug(3, "player index %d", idx); p = add_player(server, server->n_player_info); if (p) { union { int i; float fl; } temp; p->name = strdup(name); // Score p->frags = ntohl(*(unsigned int *)pkt); // TimeConnected temp.i = ntohl(*(unsigned int *)(pkt + 4)); p->connect_time = temp.fl; // Ping p->ping = 0; p->ping = ntohs(*(unsigned int *)(pkt + 8)); //((unsigned char*)&p->ping)[0] = pkt[9]; //((unsigned char*)&p->ping)[1] = pkt[8]; // ProfileId //p->profileid = ntohl( *(unsigned int *)pkt+10 ); // Team p->team = *(pkt + 14); } pktlen -= 15; pkt += 15; } if (pktlen) { malformed_packet(server, "garbage at end of player info, %d bytes left", pktlen); } status->have_player = 1; server->retry1 = n_retries; break; default: malformed_packet(server, "invalid packet id %hhx", *--pkt); return (PKT_ERROR); } if ( (!get_player_info || (get_player_info && status->have_player)) && (!get_server_rules || (get_server_rules && status->have_rules)) ) { server->next_rule = NULL; } return (DONE_AUTO); out_too_short: malformed_packet(server, "packet too short"); return (PKT_ERROR); } // vim: sw=4 ts=4 noet qstat-2.17/fl.h000066400000000000000000000007311412457473700133700ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * Frontlines-Fuel of War protocol * Copyright 2008 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_FL_H #define QSTAT_FL_H #include "qserver.h" query_status_t send_fl_request_packet(struct qserver *server); query_status_t send_fl_rule_request_packet(struct qserver *server); query_status_t deal_with_fl_packet(struct qserver *server, char *rawpkt, int pktlen); #endif qstat-2.17/gps.c000066400000000000000000000234761412457473700135660ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * Gamespy query protocol * Copyright 2005 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms * */ #include #ifndef _WIN32 #include #endif #include #include #include #include "debug.h" #include "qstat.h" #include "packet_manip.h" int gps_max_players(struct qserver *server) { struct player *player; int no_players = 0; if (0 == server->num_players) { return (0); } for (player = server->players; player; player = player->next) { no_players++; } return ((no_players < server->num_players) ? 1 : 0); } int gps_player_info_key(char *s, char *end) { static char *keys[] = { "frags_", "team_", "ping_", "species_", "race_", "deaths_", "score_", "enemy_", "player_", "keyhash_", "teamname_", "playername_", "keyhash_", "kills_", "queryid" }; int i; for (i = 0; i < sizeof(keys) / sizeof(char *); i++) { int len = strlen(keys[i]); if ((s + len < end) && (strncmp(s, keys[i], len) == 0)) { return (len); } } return (0); } query_status_t send_gps_request_packet(struct qserver *server) { return (send_packet(server, server->type->status_packet, server->type->status_len)); } query_status_t deal_with_gps_packet(struct qserver *server, char *rawpkt, int pktlen) { char *s, *key, *value, *end; struct player *player = NULL; int id_major = 0, id_minor = 0, final = 0, player_num; char tmp[256]; debug(2, "processing..."); server->n_servers++; if (server->server_name == NULL) { server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); } else { gettimeofday(&server->packet_time1, NULL); } /* * // We're using the saved_data a bit differently here to track received * // packets. * // pkt_id is the id_major from the \queryid\ * // pkt_max is the total number of packets expected * // pkt_index is a bit mask of the packets received. The id_minor of * // \queryid\ provides packet numbers (1 through pkt_max). */ if (server->saved_data.pkt_index == -1) { server->saved_data.pkt_index = 0; } rawpkt[pktlen] = '\0'; end = &rawpkt[pktlen]; s = rawpkt; while (*s) { // find the '\' while (*s && *s == '\\') { s++; } if (!*s) { // out of packet break; } // Start of key key = s; // while we still have data and its not a '\' while (*s && *s != '\\') { s++; } if (!*s) { // out of packet break; } // Terminate the key *s++ = '\0'; // Now for the value value = s; // while we still have data and its not a '\' while (*s && *s != '\\') { s++; } if (s[0] && s[1]) { //fprintf( stderr, "%s = %s\n", key, value ); if (!isalpha((unsigned char)s[1])) { // escape char? s++; // while we still have data and its not a '\' while (*s && *s != '\\') { s++; } } else if ( isalpha((unsigned char)s[1]) && (0 == strncmp(key, "player_", 7)) && (0 != strcmp(key, "player_flags")) ) { // possible '\' in player name if (!gps_player_info_key(s + 1, end)) { // yep there was an escape in the player name s++; // while we still have data and its not a '\' while (*s && *s != '\\') { s++; } } } } if (*s) { *s++ = '\0'; } //fprintf( stderr, "%s = %s\n", key, value ); if (*value == '\0') { if (strcmp(key, "final") == 0) { final = 1; if (id_minor > server->saved_data.pkt_max) { server->saved_data.pkt_max = id_minor; } continue; } } /* This must be done before looking for player info because * "queryid" is a valid according to gps_player_info_key(). */ if (strcmp(key, "queryid") == 0) { sscanf(value, "%d.%d", &id_major, &id_minor); if (server->saved_data.pkt_id == 0) { server->saved_data.pkt_id = id_major; } if (id_major == server->saved_data.pkt_id) { if (id_minor > 0) { // pkt_index is bitmask of packets recieved server->saved_data.pkt_index |= 1 << (id_minor - 1); } if (final && (id_minor > server->saved_data.pkt_max)) { server->saved_data.pkt_max = id_minor; } } continue; } if (player == NULL) { int len = gps_player_info_key(key, end); if (len) { // We have player info int player_number = atoi(key + len); player = get_player_by_number(server, player_number); // && gps_max_players( server ) due to bf1942 issue // where the actual no players is correct but more player // details are returned if ((player == NULL) && gps_max_players(server)) { player = add_player(server, player_number); if (player) { // init to -1 so we can tell if // we have team info player->team = -1; player->deaths = -999; } } } } if ((strcmp(key, "mapname") == 0) && !server->map_name) { server->map_name = strdup(value); } else if ((strcmp(key, "hostname") == 0) && !server->server_name) { server->server_name = strdup(value); } else if (strcmp(key, "hostport") == 0) { change_server_port(server, atoi(value), 0); } else if (strcmp(key, "maxplayers") == 0) { server->max_players = atoi(value); } else if (strcmp(key, "numplayers") == 0) { server->num_players = atoi(value); } else if ((strcmp(key, server->type->game_rule) == 0) && !server->game) { server->game = strdup(value); add_rule(server, key, value, NO_FLAGS); } else if (strcmp(key, "final") == 0) { final = 1; if (id_minor > server->saved_data.pkt_max) { server->saved_data.pkt_max = id_minor; } continue; } else if ((strncmp(key, "player_", 7) == 0) || (strncmp(key, "playername_", 11) == 0)) { int no; if (strncmp(key, "player_", 7) == 0) { no = atoi(key + 7); } else { no = atoi(key + 11); } if (player && (player->number == no)) { player->name = strdup(value); player = NULL; } else if (NULL != (player = get_player_by_number(server, no))) { player->name = strdup(value); player = NULL; } else if (gps_max_players(server)) { // gps_max_players( server ) due to bf1942 issue // where the actual no players is correct but more player // details are returned player = add_player(server, no); if (player) { player->name = strdup(value); // init to -1 so we can tell if // we have team info player->team = -1; player->deaths = -999; } } } else if (strncmp(key, "teamname_", 9) == 0) { // Yes plus 1 BF1942 is a silly players_set_teamname(server, atoi(key + 9) + 1, value); } else if (strncmp(key, "team_t", 6) == 0) { players_set_teamname(server, atoi(key + 6), value); } else if (strncmp(key, "frags_", 6) == 0) { player = get_player_by_number(server, atoi(key + 6)); if (NULL != player) { player->frags = atoi(value); } } else if (strncmp(key, "kills_", 6) == 0) { player = get_player_by_number(server, atoi(key + 6)); if (NULL != player) { player->frags = atoi(value); } } else if (strncmp(key, "team_", 5) == 0) { player = get_player_by_number(server, atoi(key + 5)); if (NULL != player) { if (!isdigit((unsigned char)*value)) { player->team_name = strdup(value); } else { player->team = atoi(value); } server->flags |= FLAG_PLAYER_TEAMS; } } else if (strncmp(key, "skin_", 5) == 0) { player = get_player_by_number(server, atoi(key + 5)); if (NULL != player) { player->skin = strdup(value); } } else if (strncmp(key, "mesh_", 5) == 0) { player = get_player_by_number(server, atoi(key + 5)); if (NULL != player) { player->mesh = strdup(value); } } else if (strncmp(key, "ping_", 5) == 0) { player = get_player_by_number(server, atoi(key + 5)); if (NULL != player) { player->ping = atoi(value); } } else if (strncmp(key, "face_", 5) == 0) { player = get_player_by_number(server, atoi(key + 5)); if (NULL != player) { player->face = strdup(value); } } else if (strncmp(key, "deaths_", 7) == 0) { player = get_player_by_number(server, atoi(key + 7)); if (NULL != player) { player->deaths = atoi(value); } } // isnum( key[6] ) as halo uses score_tX for team scores else if ((strncmp(key, "score_", 6) == 0) && isdigit((unsigned char)key[6])) { player = get_player_by_number(server, atoi(key + 6)); if (NULL != player) { player->score = atoi(value); } } else if (player && (strncmp(key, "playertype", 10) == 0)) { player->team_name = strdup(value); } else if (player && (strncmp(key, "charactername", 13) == 0)) { player->face = strdup(value); } else if (player && (strncmp(key, "characterlevel", 14) == 0)) { player->ship = atoi(value); } else if (strncmp(key, "keyhash_", 8) == 0) { // Ensure these dont make it into the rules } else if (2 == sscanf(key, "%255[^_]_%d", tmp, &player_num)) { // arbitary player info player = get_player_by_number(server, player_num); if (NULL != player) { player_add_info(player, tmp, value, NO_FLAGS); } else if (gps_max_players(server)) { // gps_max_players( server ) due to bf1942 issue // where the actual no players is correct but more player // details are returned player = add_player(server, player_num); if (player) { player->name = NULL; // init to -1 so we can tell if // we have team info player->team = -1; player->deaths = -999; } player_add_info(player, tmp, value, NO_FLAGS); } } else { player = NULL; add_rule(server, key, value, NO_FLAGS); } } debug(2, "final %d\n", final); debug(2, "pkt_id %d\n", server->saved_data.pkt_id); debug(2, "pkt_max %d\n", server->saved_data.pkt_max); debug(2, "pkt_index %x\n", server->saved_data.pkt_index); if ( (final && (server->saved_data.pkt_id == 0)) || (server->saved_data.pkt_max && (server->saved_data.pkt_index >= ((1 << (server->saved_data.pkt_max)) - 1))) || ((server->num_players < 0) && (id_minor >= 3)) ) { return (DONE_FORCE); } return (INPROGRESS); } qstat-2.17/gps.h000066400000000000000000000006521412457473700135620ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * Gamespy query protocol * Copyright 2005 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_GPS_H #define QSTAT_GPS_H #include "qserver.h" // Packet processing methods query_status_t deal_with_gps_packet(struct qserver *server, char *pkt, int pktlen); query_status_t send_gps_request_packet(struct qserver *server); #endif qstat-2.17/gs2.c000066400000000000000000000170701412457473700134610ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * New Gamespy v2 query protocol * Copyright 2005 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms * */ #include #ifndef _WIN32 #include #endif #include #include #include "debug.h" #include "qstat.h" #include "packet_manip.h" query_status_t send_gs2_request_packet(struct qserver *server) { // The below should work but seems to make no difference to what some // servers send if (get_player_info) { server->type->status_packet[8] = 0xff; server->type->status_packet[9] = 0xff; } else { server->type->status_packet[8] = 0x00; server->type->status_packet[9] = 0x00; } return (send_packet(server, server->type->status_packet, server->type->status_len)); } // See the following for protocol details: // http://dev.kquery.com/index.php?article=42 query_status_t deal_with_gs2_packet(struct qserver *server, char *rawpkt, int pktlen) { char *ptr = rawpkt; char *end = rawpkt + pktlen; unsigned char type = 0; unsigned char no_players = 0; unsigned char total_players = 0; unsigned char no_teams = 0; unsigned char total_teams = 0; unsigned char no_headers = 0; char **headers = NULL; debug(2, "processing packet..."); if (pktlen < 15) { // invalid packet? return (PKT_ERROR); } server->n_servers++; if (server->server_name == NULL) { server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); } else { gettimeofday(&server->packet_time1, NULL); } // Could check the header here should // match the 4 byte id sent ptr += 5; while (0 == type && ptr < end) { // server info: // name value pairs null seperated // empty name && value signifies the end of section char *var, *val; int var_len, val_len; var = ptr; var_len = strlen(var); if (ptr + var_len + 2 > end) { if (0 != var_len) { malformed_packet(server, "no rule value"); } else if (get_player_info) { malformed_packet(server, "no player headers"); } return (PKT_ERROR); } ptr += var_len + 1; val = ptr; val_len = strlen(val); ptr += val_len + 1; debug(2, "var:%s (%d)=%s (%d)\n", var, var_len, val, val_len); // Lets see what we've got if (0 == strcmp(var, "hostname")) { server->server_name = strdup(val); } else if (0 == strcmp(var, "game_id")) { server->game = strdup(val); } else if (0 == strcmp(var, "gamever")) { // format: // v1.0 server->protocol_version = atoi(val + 1); add_rule(server, var, val, NO_FLAGS); } else if (0 == strcmp(var, "mapname")) { server->map_name = strdup(val); } else if (0 == strcmp(var, "map")) { // For BF2MC compatibility server->map_name = strdup(val); } else if (0 == strcmp(var, "maxplayers")) { server->max_players = atoi(val); } else if (0 == strcmp(var, "numplayers")) { server->num_players = no_players = atoi(val); } else if (0 == strcmp(var, "hostport")) { change_server_port(server, atoi(val), 0); } else if (0 == var_len) { // check for end of section type = 1; } else { add_rule(server, var, val, NO_FLAGS); } } if (1 != type) { // no more info should be player headers here as we // requested it malformed_packet(server, "no player headers"); return (PKT_ERROR); } // player info header // format: // first byte = player count // followed by null seperated header no_players = (unsigned char)*ptr; debug(2, "No Players:%d\n", no_players); ptr++; if (ptr >= end) { malformed_packet(server, "no player headers"); return (PKT_ERROR); } while (1 == type && ptr < end) { // first we have the headers null seperated char **tmpp; char *head = ptr; int head_len = strlen(head); no_headers++; tmpp = (char **)realloc(headers, no_headers * sizeof(char *)); if (NULL == tmpp) { debug(0, "Failed to realloc memory for headers\n"); if (NULL != headers) { free(headers); } return (MEM_ERROR); } headers = tmpp; headers[no_headers - 1] = head; ptr += head_len + 1; // end of headers check if (0x00 == *ptr) { type = 2; ptr++; } debug(2, "player header[%d] = '%s'", no_headers - 1, head); } if (2 != type) { // no more info should be player info here as we // requested it malformed_packet(server, "no players"); return (PKT_ERROR); } while (2 == type && ptr < end) { // now each player details // add the player if (0x00 == *ptr) { // no players if (0 != no_players) { malformed_packet(server, "no players"); return (PKT_ERROR); } } else { struct player *player = add_player(server, total_players); int i; for (i = 0; i < no_headers; i++) { char *header = headers[i]; char *val; int val_len; if (ptr >= end) { malformed_packet(server, "short player detail"); return (PKT_ERROR); } val = ptr; val_len = strlen(val); ptr += val_len + 1; // lets see what we got if (0 == strcmp(header, "player_")) { player->name = strdup(val); } else if (0 == strcmp(header, "score_")) { player->score = atoi(val); } else if (0 == strcmp(header, "deaths_")) { player->deaths = atoi(val); } else if (0 == strcmp(header, "ping_")) { player->ping = atoi(val); } else if (0 == strcmp(header, "kills_")) { player->frags = atoi(val); } else if (0 == strcmp(header, "team_")) { player->team = atoi(val); } else { int len = strlen(header); if ('_' == header[len - 1]) { header[len - 1] = '\0'; } player_add_info(player, header, val, NO_FLAGS); } debug(2, "Player[%d][%s]=%s\n", total_players, headers[i], val); } total_players++; } if (total_players > no_players) { malformed_packet(server, "to many players %d > %d", total_players, no_players); return (PKT_ERROR); } // check for end of player info if (0x00 == *ptr) { if (total_players != no_players) { malformed_packet(server, "bad number of players %d != %d", total_players, no_players); return (PKT_ERROR); } type = 3; ptr++; } } if (3 != type) { // no more info should be team info here as we // requested it malformed_packet(server, "no teams"); return (PKT_ERROR); } no_teams = (unsigned char)*ptr; ptr++; debug(2, "No teams:%d\n", no_teams); no_headers = 0; while (3 == type && ptr < end) { // first we have the headers null seperated char **tmpp; char *head = ptr; int head_len = strlen(head); no_headers++; tmpp = (char **)realloc(headers, no_headers * sizeof(char *)); if (NULL == tmpp) { debug(0, "Failed to realloc memory for headers\n"); if (NULL != headers) { free(headers); } return (MEM_ERROR); } headers = tmpp; headers[no_headers - 1] = head; ptr += head_len + 1; // end of headers check if (0x00 == *ptr) { type = 4; ptr++; } } if (4 != type) { // no more info should be team info here as we // requested it malformed_packet(server, "no teams"); return (PKT_ERROR); } while (4 == type && ptr < end) { // now each teams details int i; for (i = 0; i < no_headers; i++) { char *val; int val_len; if (ptr >= end) { malformed_packet(server, "short team detail"); return (PKT_ERROR); } val = ptr; val_len = strlen(val); ptr += val_len + 1; // lets see what we got if (0 == strcmp(headers[i], "team_t")) { // BF being stupid again teams 1 based instead of 0 players_set_teamname(server, total_teams + 1, val); } debug(2, "Team[%d][%s]=%s\n", total_teams, headers[i], val); } total_teams++; if (total_teams > no_teams) { malformed_packet(server, "to many teams"); return (PKT_ERROR); } } return (DONE_FORCE); } qstat-2.17/gs2.h000066400000000000000000000006611412457473700134640ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * New Gamespy v2 query protocol * Copyright 2005 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_GS2_H #define QSTAT_GS2_H #include "qserver.h" // Packet processing methods query_status_t deal_with_gs2_packet(struct qserver *server, char *pkt, int pktlen); query_status_t send_gs2_request_packet(struct qserver *server); #endif qstat-2.17/gs3.c000066400000000000000000000451601412457473700134630ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * New Gamespy v3 query protocol * Copyright 2005 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms * */ #include #ifndef _WIN32 #include #include #endif #include #include #include "debug.h" #include "qstat.h" #include "packet_manip.h" int process_gs3_packet(struct qserver *server); // Player headers #define PLAYER_NAME_HEADER 1 #define PLAYER_SCORE_HEADER 2 #define PLAYER_DEATHS_HEADER 3 #define PLAYER_PING_HEADER 4 #define PLAYER_KILLS_HEADER 5 #define PLAYER_TEAM_HEADER 6 #define PLAYER_OTHER_HEADER 7 // Team headers #define TEAM_NAME_HEADER 1 #define TEAM_OTHER_HEADER 2 // Challenge response algorithum // Before sending a qr2 query (type 0x00) the client must first send a // challenge request (type 0x09). The host will respond with the same // packet type containing a string signed integer. // // Once the challenge is received the client should convert the string to a // network byte order integer and embed it in the keys query. // // Example: // // challenge request: [0xFE][0xFD][0x09][0x.. 4-byte-instance] // challenge response: [0x09][0x.. 4-byte-instance]["-1287574694"] // query: [0xFE][0xFD][0x00][0x.. 4-byte-instance][0xb3412b5a "-1287574694"] // query_status_t deal_with_gs3_packet(struct qserver *server, char *rawpkt, int pktlen) { char *ptr = rawpkt; unsigned int pkt_id; int pkt_index; unsigned char flag; unsigned int pkti, final; debug(2, "packet n_requests %d, retry1 %d, n_retries %d, delta %d", server->n_requests, server->retry1, n_retries, time_delta(&packet_recv_time, &server->packet_time1)); server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); if ((7 == pktlen) && (0x09 == *ptr)) { // gs4 query sent to a gs3 server // switch protocols and try again // Correct the stats due to two phase protocol int i; server_type *gs3_type = &builtin_types[50]; debug(3, "Attempting gs3 fallback from gs4"); server->retry1++; if (GAMESPY3_PROTOCOL_SERVER != gs3_type->id) { // Static coding failure do it the hard way debug(1, "gs3 static lookup failure, using dynamic lookup"); for (i = 0; i < GAMESPY3_PROTOCOL_SERVER; i++) { if (GAMESPY3_PROTOCOL_SERVER == builtin_types[i].id) { // found it gs3_type = &builtin_types[i]; i = GAMESPY3_PROTOCOL_SERVER; } } if (GAMESPY3_PROTOCOL_SERVER != gs3_type->id) { malformed_packet(server, "GS3 protocol not found"); return (PKT_ERROR); } } server->type = gs3_type; return (send_gs3_request_packet(server)); } if (pktlen < 12) { // invalid packet? malformed_packet(server, "too short"); return (PKT_ERROR); } if (0x09 == *ptr) { // challenge response // Could check the header here should // match the 4 byte id sent ptr++; memcpy(&pkt_id, ptr, 4); ptr += 4; server->challenge = atoi(ptr); debug(3, "Challenge: %ld", server->challenge); server->retry1++; return (send_gs3_request_packet(server)); } if (0x00 != *ptr) { malformed_packet(server, "bad initial byte '%hhx'", *ptr); return (PKT_ERROR); } ptr++; server->n_servers++; // Could check the header here should // match the 4 byte id sent memcpy(&pkt_id, ptr, 4); ptr += 4; // Next we have the splitnum details if (0 != strncmp(ptr, "splitnum", 8)) { if (server->flags & TF_STATUS_QUERY) { // we have the status response return (deal_with_gs3_status(server, ptr, pktlen - (ptr - rawpkt))); } else { malformed_packet(server, "missing splitnum"); return (PKT_ERROR); } } ptr += 9; pkt_index = ((unsigned char)*ptr) & 127; final = ((unsigned char)*ptr) >> 7; flag = *ptr++; pkti = *ptr++; debug(1, "splitnum: flag = 0x%hhx, index = %d, final = %d, %d", flag, pkt_index, final, pkti); if (0xFF != flag) { // not a single packet response or a callback int pkt_max = pkt_index + 1; if (!final) { // Guess that we have more to come pkt_max++; debug(2, "more to come 0x%hhx 0x%hhx 0x%hhx", rawpkt[pktlen - 3], rawpkt[pktlen - 2], rawpkt[pktlen - 1]); } debug(2, "pkt_max %d", pkt_max); if (0 == pkt_index) { // to prevent reprocessing when we get the call back // override the packet flag so it looks like a single // packet response rawpkt[14] = 0xFF; } // add the packet recalcing maxes if (!add_packet(server, pkt_id, pkt_index, pkt_max, pktlen, rawpkt, 1)) { // fatal error e.g. out of memory return (MEM_ERROR); } // combine_packets will call us recursively return (combine_packets(server)); } // if we get here we have what should be a full packet return (process_gs3_packet(server)); } query_status_t deal_with_gs3_status(struct qserver *server, char *rawpkt, int pktlen) { char *pkt = rawpkt; debug(1, "status packet"); // Server name server->server_name = strdup(pkt); pkt += strlen(pkt) + 1; // gametype add_rule(server, "gametype", pkt, NO_FLAGS); pkt += strlen(pkt) + 1; // map // UT3 retail compatibility if (0 == strncmp(pkt, "OwningPlayerId", 13)) { char *end = pkt + strlen(pkt) + 1; char *var = pkt; while (NULL != var) { char *next; char *val = strstr(var, "="); *val = '\0'; val++; next = strstr(val, ","); if (NULL != next) { *next = '\0'; next++; } if (0 == strcmp(var, "mapname")) { if (server->map_name) { free(server->map_name); } server->map_name = strdup(val); } else if (0 == strcmp(var, "p1073741825")) { if (server->map_name) { free(server->map_name); } server->map_name = strdup(val); } else if (0 == strcmp(var, "p1073741826")) { add_rule(server, "gametype", val, OVERWITE_DUPLICATES); } else if (0 == strcmp(var, "p268435705")) { add_rule(server, "timelimit", val, OVERWITE_DUPLICATES); } else if (0 == strcmp(var, "p1073741827")) { add_rule(server, "description", val, OVERWITE_DUPLICATES); #ifndef UT3_PATCHED if (0 != strlen(val)) { if (server->server_name) { char *name = (char *)realloc(server->server_name, strlen(server->server_name) + strlen(val) + 3); if (name) { strcat(name, ": "); strcat(name, val); server->server_name = name; } } server->server_name = strdup(val); } #endif } else if (0 == strcmp(var, "p268435704")) { add_rule(server, "goalscore", val, OVERWITE_DUPLICATES); } else if (0 == strcmp(var, "s32779")) { add_rule(server, "gamemode", val, OVERWITE_DUPLICATES); } else { add_rule(server, var, val, OVERWITE_DUPLICATES); } var = next; } pkt = end; } else { server->map_name = strdup(pkt); pkt += strlen(pkt) + 1; } // num players server->num_players = atoi(pkt); pkt += strlen(pkt) + 1; // max_players server->max_players = atoi(pkt); pkt += strlen(pkt) + 1; // hostport change_server_port(server, atoi(pkt), 0); pkt += strlen(pkt) + 1; return (DONE_FORCE); } int process_gs3_packet(struct qserver *server) { unsigned char state = 0; unsigned char no_players = 0; unsigned char total_players = 0; unsigned char no_teams = 0; unsigned char total_teams = 0; int pkt_index = 0; SavedData *fragment; debug(2, "processing packet..."); while (NULL != (fragment = get_packet_fragment(pkt_index++))) { int pktlen = fragment->datalen; char *ptr = fragment->data; char *end = ptr + pktlen; debug(2, "processing fragment[%d]...", fragment->pkt_index); if (6 <= get_debug_level()) { print_packet(server, ptr, pktlen); } // check we have a full header if (pktlen < 16) { // invalid packet? malformed_packet(server, "too short"); return (PKT_ERROR); } // skip over the header ptr += 16; while (0 == state && ptr < end) { // server info: // name value pairs null seperated // empty name && value signifies the end of section char *var, *val; int var_len, val_len; if (ptr + 1 >= end) { debug(4, "state = %d, %hhx, bytes left = %d", state, ptr[0], (int)(end - ptr)); } else { debug(4, "state = %d, %hhx, %hhx, bytes left = %d", state, ptr[0], ptr[1], (int)(end - ptr)); } if ((0x00 == ptr[0]) && ((ptr + 1 >= end) || (0x01 >= ptr[1]))) { // not quite sure of the significance of these bytes // but we use them as a check for end of section state = 1; ptr += 2; break; } var = ptr; var_len = strlen(var); ptr += var_len + 1; if (ptr + 1 > end) { malformed_packet(server, "no rule value"); return (PKT_ERROR); } val = ptr; val_len = strlen(val); ptr += val_len + 1; debug(2, "var:%s (%d)=%s (%d)\n", var, var_len, val, val_len); // Lets see what we've got if (0 == strcmp(var, "hostname")) { server->server_name = strdup(val); } else if (0 == strcmp(var, "game_id")) { server->game = strdup(val); add_rule(server, var, val, NO_FLAGS); } else if (0 == strcmp(var, "gamever")) { // format: // v1.0 server->protocol_version = atoi(val + 1); add_rule(server, var, val, NO_FLAGS); } else if (0 == strcmp(var, "mapname")) { // UT3 retail compatibility if (0 == strncmp(val, "OwningPlayerId", 13)) { var = val; while (NULL != var) { char *next; char *val = strstr(var, "="); *val = '\0'; val++; next = strstr(val, ","); if (NULL != next) { *next = '\0'; next++; } if (0 == strcmp(var, "mapname")) { if (server->map_name) { free(server->map_name); } server->map_name = strdup(val); } else if (0 == strcmp(var, "p1073741825")) { if (server->map_name) { free(server->map_name); } server->map_name = strdup(val); } else if (0 == strcmp(var, "p1073741826")) { add_rule(server, "gametype", val, OVERWITE_DUPLICATES); } else if (0 == strcmp(var, "p268435705")) { add_rule(server, "timelimit", val, OVERWITE_DUPLICATES); } else if (0 == strcmp(var, "p1073741827")) { #ifndef UT3_PATCHED if (0 != strlen(val)) { if (server->server_name) { char *name = (char *)realloc(server->server_name, strlen(server->server_name) + strlen(val) + 3); if (name) { strcat(name, ": "); strcat(name, val); server->server_name = name; } } server->server_name = strdup(val); } #endif } else if (0 == strcmp(var, "p268435704")) { add_rule(server, "goalscore", val, OVERWITE_DUPLICATES); } else if (0 == strcmp(var, "s32779")) { add_rule(server, "gamemode", val, OVERWITE_DUPLICATES); } else { add_rule(server, var, val, OVERWITE_DUPLICATES); } var = next; } } else { server->map_name = strdup(val); } } else if (0 == strcmp(var, "map")) { // BF2MC compatibility server->map_name = strdup(val); } else if (0 == strcmp(var, "maxplayers")) { server->max_players = atoi(val); } else if (0 == strcmp(var, "numplayers")) { server->num_players = no_players = atoi(val); } else if (0 == strcmp(var, "hostport")) { change_server_port(server, atoi(val), 0); } else if (0 == strcmp(var, "p1073741825")) { // UT3 demo compatibility if (server->map_name) { free(server->map_name); } server->map_name = strdup(val); } else if (0 == strcmp(var, "p1073741826")) { // UT3 demo compatibility add_rule(server, "gametype", val, OVERWITE_DUPLICATES); } else if (0 == strcmp(var, "p268435705")) { // UT3 demo compatibility add_rule(server, "timelimit", val, OVERWITE_DUPLICATES); } else if (0 == strcmp(var, "p1073741827")) { // UT3 demo compatibility add_rule(server, "description", val, OVERWITE_DUPLICATES); #ifndef UT3_PATCHED if (0 != strlen(val)) { if (server->server_name) { char *name = (char *)realloc(server->server_name, strlen(server->server_name) + strlen(val) + 3); if (name) { strcat(name, ": "); strcat(name, val); server->server_name = name; } } server->server_name = strdup(val); } #endif } else if (0 == strcmp(var, "p268435704")) { // UT3 demo compatibility add_rule(server, "goalscore", val, OVERWITE_DUPLICATES); } else if (0 == strcmp(var, "s32779")) { // UT3 demo compatibility add_rule(server, "gamemode", val, OVERWITE_DUPLICATES); } else { add_rule(server, var, val, OVERWITE_DUPLICATES); } } while (1 == state && ptr < end) { // first we have the header char *header = ptr; int head_len = strlen(header); int header_type; debug(4, "state = %d, bytes left = %d, head_len = %d", state, (int)(end - ptr), head_len); if (0 == head_len) { // no more info debug(3, "All done"); return (DONE_FORCE); } ptr += head_len + 1; if (ptr >= end) { // partial header, should be restarted in the next fragment debug(5, "partial header '%s'", header); // ensure gt than ptr++; break; } debug(2, "player header '%s'", header); // the next byte is the starting number total_players = *ptr++; if ((0 == strcmp(header, "player_")) || (0 == strcmp(header, "name_"))) { header_type = PLAYER_NAME_HEADER; } else if (0 == strcmp(header, "score_")) { header_type = PLAYER_SCORE_HEADER; } else if (0 == strcmp(header, "deaths_")) { header_type = PLAYER_DEATHS_HEADER; } else if (0 == strcmp(header, "ping_")) { header_type = PLAYER_PING_HEADER; } else if (0 == strcmp(header, "kills_")) { header_type = PLAYER_KILLS_HEADER; } else if (0 == strcmp(header, "team_")) { header_type = PLAYER_TEAM_HEADER; } else { header_type = PLAYER_OTHER_HEADER; } while (ptr < end) { // now each player details // add the player struct player *player; char *val; int val_len; // check for end of this headers player info if (0x00 == *ptr) { debug(3, "end of '%s' detail, %d bytes left", header, (int)(end - ptr)); ptr++; // Note: can't check ( total_players != no_players ) here as we may have more packets if ((ptr < end) && (0x00 == *ptr)) { debug(3, "end of players"); // end of all player headers / detail state = 2; ptr++; } break; } player = get_player_by_number(server, total_players); if (NULL == player) { player = add_player(server, total_players); } if (ptr >= end) { malformed_packet(server, "short player detail"); return (PKT_ERROR); } val = ptr; val_len = strlen(val); ptr += val_len + 1; debug(2, "Player[%d][%s]=%s\n", total_players, header, val); // lets see what we got switch (header_type) { case PLAYER_NAME_HEADER: player->name = strdup(val); break; case PLAYER_SCORE_HEADER: player->score = atoi(val); break; case PLAYER_DEATHS_HEADER: player->deaths = atoi(val); break; case PLAYER_PING_HEADER: player->ping = atoi(val); break; case PLAYER_KILLS_HEADER: player->frags = atoi(val); break; case PLAYER_TEAM_HEADER: player->team = atoi(val); break; case PLAYER_OTHER_HEADER: default: if ('_' == header[head_len - 1]) { header[head_len - 1] = '\0'; player_add_info(player, header, val, NO_FLAGS); header[head_len - 1] = '_'; } else { player_add_info(player, header, val, NO_FLAGS); } break; } total_players++; if (total_players > no_players) { malformed_packet(server, "to many players %d > %d", total_players, no_players); return (PKT_ERROR); } } } debug(4, "state = %d, bytes left = %d", state, (int)(end - ptr)); if ((2 == state) && (ptr < end)) { no_teams = (unsigned char)*ptr; ptr++; debug(2, "No teams:%d\n", no_teams); state = 3; } while (3 == state && ptr < end) { // first we have the header char *header = ptr; int head_len = strlen(header); int header_type; ptr += head_len + 1; if (0 == head_len) { // no more info debug(3, "All done"); return (DONE_FORCE); } if (ptr >= end) { // partial header, should be restarted in the next fragment debug(5, "partial header '%s'", header); break; } debug(2, "team header '%s'", header); if (0 == strcmp(header, "team_t")) { header_type = TEAM_NAME_HEADER; } else { header_type = TEAM_OTHER_HEADER; } // the next byte is the starting number total_teams = *ptr++; while (ptr < end) { // now each teams details char *val; int val_len; char rule[512]; if (ptr >= end) { malformed_packet(server, "short team detail"); return (PKT_ERROR); } val = ptr; val_len = strlen(val); ptr += val_len + 1; debug(2, "Team[%d][%s]=%s\n", total_teams, header, val); // lets see what we got switch (header_type) { case TEAM_NAME_HEADER: // BF being stupid again teams 1 based instead of 0 players_set_teamname(server, total_teams + 1, val); // N.B. yes no break case TEAM_OTHER_HEADER: default: // add as a server rule sprintf(rule, "%s%d", header, total_teams); add_rule(server, rule, val, NO_FLAGS); break; } total_teams++; if (0x00 == *ptr) { // end of this headers teams ptr++; break; } } } } return (DONE_FORCE); } query_status_t send_gs3_request_packet(struct qserver *server) { char *packet; char query_buf[128]; int len; // In the old v3 protocol the doesnt seems to make a difference // to what the servers sends but in the challenge version it definitely does if (get_player_info || get_server_rules) { server->flags |= TF_PLAYER_QUERY | TF_RULES_QUERY; if (server->challenge) { // we've recieved a challenge response, send the query + challenge id len = sprintf( query_buf, "\xfe\xfd%c\x10\x20\x30\x40%c%c%c%c\xff\xff\xff\x01", 0x00, (unsigned char)(server->challenge >> 24), (unsigned char)(server->challenge >> 16), (unsigned char)(server->challenge >> 8), (unsigned char)(server->challenge >> 0) ); packet = query_buf; } else { // Either basic v3 protocol or challenge request packet = server->type->player_packet; len = server->type->player_len; } } else { server->flags |= TF_STATUS_QUERY; if (server->challenge) { // we've recieved a challenge response, send the query + challenge id len = sprintf( query_buf, "\xfe\xfd%c\x10\x20\x30\x40%c%c%c%c\x06\x01\x06\x05\x08\x0a\x04%c%c", 0x00, (unsigned char)(server->challenge >> 24), (unsigned char)(server->challenge >> 16), (unsigned char)(server->challenge >> 8), (unsigned char)(server->challenge >> 0), 0x00, 0x00 ); packet = query_buf; } else { // Either basic v3 protocol or challenge request packet = server->type->status_packet; len = server->type->status_len; } } return (send_packet(server, packet, len)); } qstat-2.17/gs3.h000066400000000000000000000010101412457473700134520ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * New Gamespy v3 query protocol * Copyright 2005 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_GS3_H #define QSTAT_GS3_H #include "qserver.h" // Packet processing methods query_status_t deal_with_gs3_packet(struct qserver *server, char *pkt, int pktlen); query_status_t deal_with_gs3_status(struct qserver *server, char *rawpkt, int pktlen); query_status_t send_gs3_request_packet(struct qserver *server); #endif qstat-2.17/haze.c000066400000000000000000000306471412457473700137220ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * New Haze query protocol * Copyright 2005 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms * */ #include #ifndef _WIN32 #include #include #endif #include #include #include "debug.h" #include "qstat.h" #include "packet_manip.h" // Format: // 1 - 8: Challenge Request / Response char haze_challenge[] = { 'f', 'r', 'd', 'c', '_', '_', '_', '_' }; int process_haze_packet(struct qserver *server); // Player headers #define PLAYER_NAME_HEADER 1 #define PLAYER_SCORE_HEADER 2 #define PLAYER_DEATHS_HEADER 3 #define PLAYER_PING_HEADER 4 #define PLAYER_KILLS_HEADER 5 #define PLAYER_TEAM_HEADER 6 #define PLAYER_OTHER_HEADER 7 // Team headers #define TEAM_NAME_HEADER 1 #define TEAM_OTHER_HEADER 2 // Challenge response algorithum // Before sending a qr2 query (type 0x00) the client must first send a // challenge request (type 0x09). The host will respond with the same // packet type containing a string signed integer. // // Once the challenge is received the client should convert the string to a // network byte order integer and embed it in the keys query. // // Example: // // challenge request: [0xFE][0xFD][0x09][0x.. 4-byte-instance] // challenge response: [0x09][0x.. 4-byte-instance]["-1287574694"] // query: [0xFE][0xFD][0x00][0x.. 4-byte-instance][0xb3412b5a "-1287574694"] // query_status_t deal_with_haze_packet(struct qserver *server, char *rawpkt, int pktlen) { char *ptr = rawpkt; unsigned int pkt_id; unsigned short len; unsigned char pkt_max, pkt_index; debug(2, "packet..."); if (pktlen < 8) { // invalid packet malformed_packet(server, "too short"); return (PKT_ERROR); } if (0 == strncmp(ptr, "frdcr", 5)) { // challenge response ptr += 8; server->challenge = 1; // Correct the stats due to two phase protocol server->retry1++; server->n_packets--; if ((server->retry1 == n_retries) || server->flags & FLAG_BROADCAST) { //server->n_requests--; } else { server->n_retries--; } return (send_haze_request_packet(server)); } if (pktlen < 12) { // invalid packet malformed_packet(server, "too short"); return (PKT_ERROR); } server->n_servers++; if (server->server_name == NULL) { server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); } else { gettimeofday(&server->packet_time1, NULL); } // Query version ID ptr += 4; // Could check the header here should // match the 4 byte id sent memcpy(&pkt_id, ptr, 4); ptr += 4; // Max plackets pkt_max = ((unsigned char)*ptr); ptr++; // Packet ID pkt_index = ((unsigned char)*ptr); ptr++; // Query Length debug(1, "%04hx, %04hx", (unsigned short)ptr[0], (unsigned short)(ptr[1] << 8)); // TODO: fix this crap memcpy(&len, ptr + 1, 1); ptr += 2; debug(1, "pkt_index = %d, pkt_max = %d, len = %d", pkt_index, pkt_max, len); if (0 != pkt_max) { // not a single packet response or callback debug(2, "pkt_max %d", pkt_max); if (0 == pkt_index) { // to prevent reprocessing when we get the call back // override the packet flag so it looks like a single // packet response rawpkt[8] = '\0'; } // add the packet recalcing maxes if (!add_packet(server, pkt_id, pkt_index, pkt_max, pktlen, rawpkt, 1)) { // fatal error e.g. out of memory return (MEM_ERROR); } // combine_packets will call us recursively return (combine_packets(server)); } // if we get here we have what should be a full packet return (process_haze_packet(server)); } query_status_t deal_with_haze_status(struct qserver *server, char *rawpkt, int pktlen) { char *pkt = rawpkt; int len; debug(1, "status packet"); // Server name server->server_name = strdup(pkt); pkt += strlen(pkt) + 1; // gametype add_rule(server, "gametype", pkt, NO_FLAGS); pkt += strlen(pkt) + 1; // map len = strlen(pkt); // remove .res from map names if (0 == strncmp(pkt + len - 4, ".res", 4)) { *(pkt + len - 4) = '\0'; } server->map_name = strdup(pkt); pkt += len + 1; // num players server->num_players = atoi(pkt); pkt += strlen(pkt) + 1; // max_players server->max_players = atoi(pkt); pkt += strlen(pkt) + 1; // hostport change_server_port(server, atoi(pkt), 0); pkt += strlen(pkt) + 1; return (DONE_FORCE); } int process_haze_packet(struct qserver *server) { unsigned char state = 0; unsigned char no_players = 0; unsigned char total_players = 0; unsigned char no_teams = 0; unsigned char total_teams = 0; int pkt_index = 0; SavedData *fragment; debug(2, "processing packet..."); while (NULL != (fragment = get_packet_fragment(pkt_index++))) { int pktlen = fragment->datalen; char *ptr = fragment->data; char *end = ptr + pktlen; debug(2, "processing fragment[%d]...", fragment->pkt_index); // check we have a full header if (pktlen < 12) { // invalid packet malformed_packet(server, "too short"); return (PKT_ERROR); } // skip over the header //server->protocol_version = atoi( val+1 ); ptr += 12; // 4 * null's signifies the end of a section // Basic Info while (0 == state && ptr < end) { // name value pairs null seperated char *var, *val; int var_len, val_len; if ((ptr + 4 <= end) && (0x00 == ptr[0]) && (0x00 == ptr[1]) && (0x00 == ptr[2]) && (0x00 == ptr[3])) { // end of rules state++; ptr += 4; break; } var = ptr; var_len = strlen(var); ptr += var_len + 1; if (ptr + 1 > end) { malformed_packet(server, "no basic value"); return (PKT_ERROR); } val = ptr; val_len = strlen(val); ptr += val_len + 1; debug(2, "var:%s (%d)=%s (%d)\n", var, var_len, val, val_len); // Lets see what we've got if (0 == strcmp(var, "serverName")) { server->server_name = strdup(val); } else if (0 == strcmp(var, "map")) { // remove .res from map names if (0 == strncmp(val + val_len - 4, ".res", 4)) { *(val + val_len - 4) = '\0'; } server->map_name = strdup(val); } else if (0 == strcmp(var, "maxPlayers")) { server->max_players = atoi(val); } else if (0 == strcmp(var, "currentPlayers")) { server->num_players = no_players = atoi(val); } else { add_rule(server, var, val, NO_FLAGS); } } // rules while (1 == state && ptr < end) { // name value pairs null seperated char *var, *val; int var_len, val_len; if ((ptr + 4 <= end) && (0x00 == ptr[0]) && (0x00 == ptr[1]) && (0x00 == ptr[2]) && (0x00 == ptr[3])) { // end of basic state++; ptr += 4; break; } var = ptr; var_len = strlen(var); ptr += var_len + 1; if (ptr + 1 > end) { malformed_packet(server, "no basic value"); return (PKT_ERROR); } val = ptr; val_len = strlen(val); ptr += val_len + 1; debug(2, "var:%s (%d)=%s (%d)\n", var, var_len, val, val_len); // add the rule add_rule(server, var, val, NO_FLAGS); } // players while (2 == state && ptr < end) { // first we have the header char *header = ptr; int head_len = strlen(header); ptr += head_len + 1; if ((ptr + 2 <= end) && (0x00 == ptr[0]) && (0x00 == ptr[1])) { // end of player headers state++; ptr += 2; break; } if (0 == head_len) { // no more info debug(3, "All done"); return (DONE_FORCE); } debug(2, "player header '%s'", header); if (ptr > end) { malformed_packet(server, "no details for header '%s'", header); return (PKT_ERROR); } } while (3 == state && ptr < end) { char *header = ptr; int head_len = strlen(header); int header_type; // the next byte is the starting number total_players = *ptr++; if ((0 == strcmp(header, "player_")) || (0 == strcmp(header, "name_"))) { header_type = PLAYER_NAME_HEADER; } else if (0 == strcmp(header, "score_")) { header_type = PLAYER_SCORE_HEADER; } else if (0 == strcmp(header, "deaths_")) { header_type = PLAYER_DEATHS_HEADER; } else if (0 == strcmp(header, "ping_")) { header_type = PLAYER_PING_HEADER; } else if (0 == strcmp(header, "kills_")) { header_type = PLAYER_KILLS_HEADER; } else if (0 == strcmp(header, "team_")) { header_type = PLAYER_TEAM_HEADER; } else { header_type = PLAYER_OTHER_HEADER; } while (ptr < end) { // now each player details // add the player struct player *player; char *val; int val_len; // check for end of this headers player info if (0x00 == *ptr) { debug(3, "end of '%s' detail", header); ptr++; // Note: can't check ( total_players != no_players ) here as we may have more packets if ((ptr < end) && (0x00 == *ptr)) { debug(3, "end of players"); // end of all player headers / detail state = 2; ptr++; } break; } player = get_player_by_number(server, total_players); if (NULL == player) { player = add_player(server, total_players); } if (ptr >= end) { malformed_packet(server, "short player detail"); return (PKT_ERROR); } val = ptr; val_len = strlen(val); ptr += val_len + 1; debug(2, "Player[%d][%s]=%s\n", total_players, header, val); // lets see what we got switch (header_type) { case PLAYER_NAME_HEADER: player->name = strdup(val); break; case PLAYER_SCORE_HEADER: player->score = atoi(val); break; case PLAYER_DEATHS_HEADER: player->deaths = atoi(val); break; case PLAYER_PING_HEADER: player->ping = atoi(val); break; case PLAYER_KILLS_HEADER: player->frags = atoi(val); break; case PLAYER_TEAM_HEADER: player->team = atoi(val); break; case PLAYER_OTHER_HEADER: default: if ('_' == header[head_len - 1]) { header[head_len - 1] = '\0'; player_add_info(player, header, val, NO_FLAGS); header[head_len - 1] = '_'; } else { player_add_info(player, header, val, NO_FLAGS); } break; } total_players++; if (total_players > no_players) { malformed_packet(server, "to many players %d > %d", total_players, no_players); return (PKT_ERROR); } } } if (3 == state) { no_teams = (unsigned char)*ptr; ptr++; debug(2, "No teams:%d\n", no_teams); state = 3; } while (4 == state && ptr < end) { // first we have the header char *header = ptr; int head_len = strlen(header); int header_type; ptr += head_len + 1; if (0 == head_len) { // no more info debug(3, "All done"); return (DONE_FORCE); } debug(2, "team header '%s'", header); if (0 == strcmp(header, "team_t")) { header_type = TEAM_NAME_HEADER; } else { header_type = TEAM_OTHER_HEADER; } // the next byte is the starting number total_teams = *ptr++; while (ptr < end) { // now each teams details char *val; int val_len; char rule[512]; if (ptr >= end) { malformed_packet(server, "short team detail"); return (PKT_ERROR); } val = ptr; val_len = strlen(val); ptr += val_len + 1; debug(2, "Team[%d][%s]=%s\n", total_teams, header, val); // lets see what we got switch (header_type) { case TEAM_NAME_HEADER: // BF being stupid again teams 1 based instead of 0 players_set_teamname(server, total_teams + 1, val); // N.B. yes no break case TEAM_OTHER_HEADER: default: // add as a server rule sprintf(rule, "%s%d", header, total_teams); add_rule(server, rule, val, NO_FLAGS); break; } total_teams++; if (0x00 == *ptr) { // end of this headers teams ptr++; break; } } } } return (DONE_FORCE); } query_status_t send_haze_request_packet(struct qserver *server) { char *packet; char query_buf[128]; size_t len; unsigned char required = HAZE_BASIC_INFO; if (get_server_rules) { required |= HAZE_GAME_RULES; server->flags |= TF_PLAYER_QUERY; } if (get_player_info) { required |= HAZE_PLAYER_INFO; required |= HAZE_TEAM_INFO; server->flags |= TF_RULES_QUERY; } server->flags |= TF_STATUS_QUERY; if (server->challenge) { // we've recieved a challenge response, send the query + challenge id len = sprintf( query_buf, "frdquery%c%c%c%c%c", (unsigned char)(server->challenge >> 24), (unsigned char)(server->challenge >> 16), (unsigned char)(server->challenge >> 8), (unsigned char)(server->challenge >> 0), required ); packet = query_buf; } else { // Either basic v3 protocol or challenge request packet = haze_challenge; len = sizeof(haze_challenge); } return (send_packet(server, packet, len)); } qstat-2.17/haze.h000066400000000000000000000011771412457473700137230ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * New Haze query protocol * Copyright 2007 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_HAZE_H #define QSTAT_HAZE_H #include "qserver.h" #define HAZE_BASIC_INFO 0x01 #define HAZE_GAME_RULES 0x02 #define HAZE_PLAYER_INFO 0x04 #define HAZE_TEAM_INFO 0x08 // Packet processing methods query_status_t deal_with_haze_packet(struct qserver *server, char *pkt, int pktlen); query_status_t deal_with_haze_status(struct qserver *server, char *rawpkt, int pktlen); query_status_t send_haze_request_packet(struct qserver *server); #endif qstat-2.17/hcache.c000066400000000000000000000236641412457473700142070ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * steve@activesw.com * http://www.activesw.com/people/steve/qstat.html * * Thanks to Per Hammer for the OS/2 patches (per@mindbend.demon.co.uk) * Thanks to John Ross Hunt for the OpenVMS Alpha patches (bigboote@ais.net) * Thanks to Scott MacFiggen for the quicksort code (smf@activesw.com) * * Inspired by QuakePing by Len Norton * * Copyright 1996,1997,1998,1999 by Steve Jankowski * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #include #include #include #include #include #include "qstat.h" #include "debug.h" #ifndef _WIN32 #include #include #include #include #include #endif #ifdef _WIN32 #include #endif #ifdef __hpux #define STATIC static #else #define STATIC #endif #ifndef INADDR_NONE #define INADDR_NONE ~0 #endif typedef struct _cache_entry { unsigned long ipaddr; char *hostname[5]; } cache_entry; static cache_entry *hcache; static int n_entry; static int max_entry; static char *last_filename; static int n_changes; static void write_file(FILE *file); static cache_entry *init_entry(unsigned long ipaddr, char *hostname, cache_entry *known); static cache_entry *find_entry(unsigned long ipaddr); static void free_entry(cache_entry *entry); static cache_entry *validate_entry(cache_entry *entry); static cache_entry *find_host_entry(char *hostname); static void add_hostname(cache_entry *entry, const char *hostname); int hcache_open(char *filename, int update) { FILE *file; char line[500], ipstr[500], hostname[500]; char *l; int line_no, end; unsigned long ip1, ip2, ip3, ip4, ipaddr; cache_entry *entry; file = fopen(filename, update ? "r+" : "r"); if (file == NULL) { if (errno == ENOENT) { debug(2, "Creating new host cache \"%s\"\n", filename); last_filename = filename; return (0); } perror(filename); return (-1); } last_filename = filename; for (line_no = 1; fgets(line, sizeof(line), file) != NULL; line_no++) { if (strlen(line) < 2) { continue; } if (line[strlen(line) - 1] != '\n') { printf("%d: line too long\n", line_no); continue; } l = line; while (isspace((unsigned char)*l)) { l++; } if ((*l == '#') || (*l == '\0')) { continue; } if (sscanf(l, "%s%n", ipstr, &end) != 1) { printf("%d: parse error\n", line_no); continue; } if (sscanf(ipstr, "%lu.%lu.%lu.%lu", &ip1, &ip2, &ip3, &ip4) != 4) { init_entry(0, ipstr, NULL); continue; } if ((ip1 & 0xffffff00) || (ip2 & 0xffffff00) || (ip3 & 0xffffff00) || (ip4 & 0xffffff00)) { printf("%d: invalid IP address \"%s\"\n", line_no, ipstr); continue; } ipaddr = (ip1 << 24) | (ip2 << 16) | (ip3 << 8) | ip4; entry = init_entry(ipaddr, NULL, NULL); while (1) { l += end; while (isspace((unsigned char)*l)) { l++; } if ((*l == '#') || (*l == '\0')) { break; } hostname[0] = '\0'; if (sscanf(l, "%s%n", hostname, &end) != 1) { printf("%d: parse error\n", line_no); continue; } init_entry(ipaddr, hostname, entry); } } fclose(file); return (0); } STATIC cache_entry * init_entry(unsigned long ipaddr, char *hostname, cache_entry *known) { cache_entry *entry; int e = 0, h; if (n_entry == max_entry) { if (max_entry == 0) { max_entry = 50; hcache = (cache_entry *)malloc(sizeof(cache_entry) * max_entry * 2); } else { hcache = (cache_entry *)realloc(hcache, sizeof(cache_entry) * max_entry * 2); } memset(hcache + n_entry, 0, sizeof(cache_entry) * max_entry * (n_entry == 0 ? 2 : 1)); max_entry *= 2; } if (ipaddr == 0) { entry = find_host_entry(hostname); if (entry == NULL) { hcache[n_entry].hostname[0] = strdup(hostname); return (&hcache[n_entry++]); } return (entry); } if (known != NULL) { entry = known; } else { for (e = 0; e < n_entry; e++) { if (hcache[e].ipaddr == ipaddr) { break; } } entry = &hcache[e]; entry->ipaddr = ipaddr; } if (hostname && (hostname[0] != '\0')) { for (h = 0; h < 5; h++) { if (entry->hostname[h] == NULL) { entry->hostname[h] = strdup(hostname); break; } } } if (e == n_entry) { n_entry++; } return (entry); } STATIC cache_entry * find_host_entry(char *hostname) { cache_entry *entry = &hcache[0]; char **ehost; int e, h; char first = *hostname; for (e = 0; e < n_entry; e++, entry++) { ehost = &entry->hostname[0]; for (h = 0; h < 5; h++, ehost++) { if (*ehost && (first == **ehost) && (strcmp(hostname, *ehost) == 0)) { return (entry); } } } return (NULL); } void hcache_write_file(char *filename) { FILE *file; if (filename != NULL) { file = fopen(filename, "w"); } else { file = stdout; } if (file == NULL) { perror(filename); return; } write_file(file); } void hcache_update_file() { FILE *file; if ((last_filename == NULL) || (n_changes == 0)) { return; } file = fopen(last_filename, "w"); if (file == NULL) { perror(last_filename); return; } write_file(file); } STATIC void write_file(FILE *file) { int e, h; for (e = 0; e < n_entry; e++) { unsigned long ipaddr = hcache[e].ipaddr; if (ipaddr == 0) { continue; } fprintf(file, "%lu.%lu.%lu.%lu", (ipaddr & 0xff000000) >> 24, (ipaddr & 0xff0000) >> 16, (ipaddr & 0xff00) >> 8, ipaddr & 0xff); if (hcache[e].hostname[0]) { for (h = 0; h < 5; h++) { if (hcache[e].hostname[h] != NULL) { fprintf(file, "%c%s", h ? ' ' : '\t', hcache[e].hostname[h]); } } } fprintf(file, "\n"); } fclose(file); } void hcache_invalidate() { int e; for (e = 0; e < n_entry; e++) { if (hcache[e].ipaddr != 0) { memset(&hcache[e].hostname[0], 0, sizeof(hcache[e].hostname)); } } } void hcache_validate() { int e; char **alias; struct hostent *ent; unsigned long ipaddr; cache_entry *entry; for (e = 0; e < n_entry; e++) { fprintf(stderr, "\r%d / %d validating ", e, n_entry); if (hcache[e].ipaddr != 0) { ipaddr = hcache[e].ipaddr; fprintf(stderr, "%lu.%lu.%lu.%lu", (ipaddr & 0xff000000) >> 24, (ipaddr & 0xff0000) >> 16, (ipaddr & 0xff00) >> 8, ipaddr & 0xff); ipaddr = htonl(ipaddr); ent = gethostbyaddr((char *)&ipaddr, sizeof(unsigned long), AF_INET); } else if (hcache[e].hostname[0] != NULL) { fprintf(stderr, "%s", hcache[e].hostname[0]); ent = gethostbyname(hcache[e].hostname[0]); if (ent != NULL) { memcpy(&ipaddr, ent->h_addr_list[0], sizeof(ipaddr)); ipaddr = ntohl(ipaddr); if ((entry = find_entry(ipaddr)) != NULL) { add_hostname(entry, hcache[e].hostname[0]); free_entry(&hcache[e]); } else { hcache[e].ipaddr = ipaddr; } } } else { continue; } if (ent == NULL) { continue; } if (ent->h_name && (ent->h_name[0] != '\0')) { add_hostname(&hcache[e], ent->h_name); } printf("h_name %s\n", ent->h_name ? ent->h_name : "NULL"); alias = ent->h_aliases; while (*alias) { add_hostname(&hcache[e], *alias); printf("h_aliases %s\n", *alias); alias++; } } } STATIC cache_entry * validate_entry(cache_entry *entry) { struct hostent *ent; char **alias; cache_entry *tmp; unsigned long ipaddr; if (entry->ipaddr != 0) { ipaddr = htonl(entry->ipaddr); /* fprintf( stderr, "%u.%u.%u.%u", (ipaddr&0xff000000)>>24, * (ipaddr&0xff0000)>>16, (ipaddr&0xff00)>>8, ipaddr&0xff); */ ent = gethostbyaddr((char *)&ipaddr, sizeof(unsigned long), AF_INET); } else if (entry->hostname[0] != NULL) { /* fprintf( stderr, "%s", entry->hostname[0]); */ ent = gethostbyname(entry->hostname[0]); if (ent != NULL) { memcpy(&ipaddr, ent->h_addr_list[0], sizeof(ipaddr)); ipaddr = ntohl(ipaddr); if ((tmp = find_entry(ipaddr)) != NULL) { add_hostname(tmp, entry->hostname[0]); free_entry(entry); entry = tmp; } else { entry->ipaddr = ipaddr; } } } else { return (NULL); } if (ent == NULL) { return (NULL); } if (ent->h_name && (ent->h_name[0] != '\0')) { add_hostname(entry, ent->h_name); } alias = ent->h_aliases; while (*alias) { add_hostname(entry, *alias); alias++; } return (entry); } unsigned long hcache_lookup_hostname(char *hostname) { cache_entry *entry; int e, h; debug(1, "looking up %s\n", hostname); for (e = 0; e < n_entry; e++) { for (h = 0; h < 5; h++) { if (hcache[e].hostname[h] && (strcmp(hostname, hcache[e].hostname[h]) == 0)) { return (hcache[e].ipaddr); } } } entry = init_entry(0, hostname, NULL); if (entry->ipaddr == 0) { debug(2, "validating %s\n", hostname); entry = validate_entry(entry); n_changes++; } if ((entry != NULL) && entry->ipaddr) { debug(2, "returning %lx\n", entry->ipaddr); return (entry->ipaddr); } return (INADDR_NONE); } char * hcache_lookup_ipaddr(unsigned long ipaddr) { cache_entry *entry; int e; for (e = 0; e < n_entry; e++) { if (hcache[e].ipaddr == ipaddr) { return (hcache[e].hostname[0]); } } entry = init_entry(ipaddr, 0, NULL); debug(1, "validating %lx\n", ipaddr); validate_entry(entry); n_changes++; return (entry ? entry->hostname[0] : NULL); } STATIC cache_entry * find_entry(unsigned long ipaddr) { int e; for (e = 0; e < n_entry; e++) { if (hcache[e].ipaddr == ipaddr) { return (&hcache[e]); } } return (NULL); } STATIC void free_entry(cache_entry *entry) { int h; for (h = 0; h < 5; h++) { if (entry->hostname[h] != NULL) { free(entry->hostname[h]); } } memset(entry, 0, sizeof(*entry)); } STATIC void add_hostname(cache_entry *entry, const char *hostname) { int h; for (h = 0; h < 5; h++) { if (entry->hostname[h] == NULL) { break; } if (strcmp(entry->hostname[h], hostname) == 0) { return; } } if (h < 5) { entry->hostname[h] = strdup(hostname); } } /* * main(int argc, char *argv[]) * { * hcache_open( argv[1], 0); * hcache_write(NULL); * * hcache_invalidate(); * printf( "invalidate\n"); * hcache_write(NULL); * hcache_validate(); * printf( "validate\n"); * hcache_write( "/tmp/qhcache.out"); * } * */ qstat-2.17/info/000077500000000000000000000000001412457473700135505ustar00rootroot00000000000000qstat-2.17/info/GhostRecon.txt000066400000000000000000000143551412457473700163740ustar00rootroot00000000000000Ghost Recon - QStat notes ------------------------- The following Server Stats are pulled from the Ghost Recon Server - NOTE many other stats continue to work as normal due to the base qstat program. $SERVERNAME The name of the GR Server. $PLAYERS The number of Players that are playing, oberving or in the Lobby (note the ignoreserverplayer Argument above) $MAXPLAYERS The maximum players that the server will allow playing, oberving or in the Lobby (note the ignoreserverplayer Argument above) $MAP The Name of the MAP that is being used (NOTE not the Mission) $GAME The Mods that the server is running. Ex: mp1; is the Desert Seige Mod $(RULE:error) If an error occured there may be some detail here. IF the problm occurred very early in the interpretation then $SERVERNAME will hold the details. $(RULE:mission) The name of the Mission that the server is running. $(RULE:gamemode) What is the Game Mode that the server is in. Known values are COOP, TEAM and SOLO $(RULE:missiontype) What is the Mission Type. Known Values are: Mission, Firefight, Recon, Hamburger Hill, Last Man Standing, Sharpshooter, Search And Rescue, Domination, and Seige. $(RULE:dedicated) Is this server Dedicated; Yes or No. $(RULE:status) What is the Playing Status of the Server, values are Playing, Joining or Debrief. $(RULE:gametime) What is the Time limit for the Game. Values are 00:00, 05:00, 10:00, 15:00 20:00, 25:00, 30:00, 45:00 and 60:00. The 00:00 is for an unlimited game. The format of this uses the -ts, -tc and -tsw command line options. $(RULE:timeplayed) How long has this game been playing. The format of this uses the -ts, -tc and -tsw command line options. $(RULE:remainingtime) How much time is left in this game. The format of this uses the -ts, -tc and -tsw command line options. $(RULE:version) What is the Version number reported by the server. Patch 1.2 = 10.1010A, Patch 1.3 = 11.101A $(RULE:spawntype) What type of spawn is in use. Known Values are None, Infinite, Individual and Team. $(RULE:spawncount) How many spawns are allowed. Enhancment possibility to add $(IF:SPAWN) to filter out when spawntype is none. $(RULE:restrict) What Weapon restrictions are in force for the server. $(RULE:password) Does the Server have a join password defined Yes or No. $(RULE:ti) Is the server using the Threat Indicator. $(RULE:motd) What is the Message Of The Day - Note these can be quite big. $(RULE:patch) What is the patch level of the GR Server. $(RULE:usestarttime) Is the server configured to start a game after "starttimeset" (Yes) OR does everyone need to click on ready (no). $(RULE:starttimeset) What time is configured to automatically start the next round. $(RULE:debrieftime) How long does the server wait at the Debrief screen after a mission. $(RULE:respawnmin) How long must a dead player wait before he can repawn. $(RULE:respawnmax) What is the longest time that a user has to respawn. $(RULE:respawnsafe) How long after respawn is a player invulnerable/cannot damage others. $(RULE:allowobservers) Does the server allow observers? Yes or No $(RULE:startwait) How long untill the automatic start timer forces the next game to start. $(RULE:iff) What Identification - Friend or Foe is configured. None, Reticule or Names $PLAYERNAME What is the Players Name. $TEAMNUM What Team Number is the Player On. Known Values are 1,2,3,4,5. 1 is Team BLUE, 2 is Team Read, 3 is Team Yellow, 4 is Team Green, 5 is Unassigned (observer or in lobby) $TEAMNAME What is the Name of the Team, see above. $DEATHS What is the health of this player. 0 Alive, 1 Dead. Note if the player has spawns remaining this can change from 1 back to 0. Enhancement possibility to add $HEALTH or $(RULE:health). Hopefully RSE/UBI will add the Deaths, Frags, and Ping to the availible information. If this happens then it would be better to have a $HEALTH $(IF:DEATHS) and $(IFNOT:DEATHS) A Test to see if the player is dead. Usefull in this constuct: $(IF:DEATHS)Dead$(ENDIF)$(IFNOT:DEATHS)Alive$(ENDIF) Ghost Recon communicates on two UDP ports and one TCP stream. Normally TCP is on port 2346 and carries the game dialog. This is the port number that is mentioned in the game so we use it and apply an offset to get the port number for status queries. Port 2347 gives some high level server stats and 2348 gives fairly low level server stats. QStat is designed around a single port per server so the 2348 port is used. One down side to this is the lack of many meaningful detail player stats (Deaths, frags, hit percentage, ping etc.). I imagines that some of these are availible in the TCP stream but that would be difficult to add to a program like QStat. The Ghost Recon packets are variable structures with a lot of string lengths. This requires fairly defensive programming as Red Storm Entertainment is not forthcoming with any details. This release adds support for the GhostRecon game. Number one note is that Red Storm and UBI do not provide the information that many Quake based users expect. Specifically they do not make Frags, Deaths Connect Time or Pings availible - at least not as far as I can tell. That said there are quite a few things that are availible and allow a server administrator to make the status of his or her server available to the public via the web. This change uses all undocumented interfaces to the Ghost Recon server so will most likely break when you install a patch. It has been tested against the Desert Seige update and several public servers. It should work against the 1.2, 1.3, and 1.4 patches and Island Thunder add-on to Ghost Recon. The Ghost Recon game type is GRS. For command-line queries, use -grs There is one query argument to this server, ignoreserverplayer. This option controls whether the first player is ignored. Ghost Recon requires that the dedicated server program take up one of the player slots (always the first slot). The ignoreserverplayer option defaults to 'yes', so the "server player" will normally not be seen. If you are running a non-dedicated server, then set ignoreserverplayer to 'no' like this: -grs,ignoreserverplayer=no Otherwise you would not be able to display your own stats. Ghost Recon support provided by Bob Marriott. qstat-2.17/info/Makefile.am000066400000000000000000000000371412457473700156040ustar00rootroot00000000000000EXTRA_DIST = $(wildcard *.txt) qstat-2.17/info/UT2003.txt000066400000000000000000000037421412457473700151540ustar00rootroot00000000000000Unreal Tournament 2003 servers can be queried with the Gamespy style protocol (-gps) or with the native UT2003 protocol (-ut2s). The query port offset for Gamespy is usually 10. The query port offset for the native UT2003 protocol is always 1. The gamespy response returns a team name for each player, the UT2S response does not. Don't be concerned because the team name is the same for all players, and seems to be map name followed by the string ".xTeamRoster" Probably a bug in the UT2003 gamespy support. The UT2S response includes a player global statistics id. The gamespy response does not. However, this id is zero for all players on both demo and retail servers. I guess they don't have global stats implemented. The protocols return similar but different servers rules. In UT2S the rule names are all lower-case. In gamespy, they are mixed-case. Some of the rules overlap, but each returns info not available from the other protocol. In the UT2S response, the "Mutator" rule (the only one with mixed-case) may appear multiple times. If you use $(RULE:Mutator) only the value of the first Mutator will be output. UT2003 servers frequently do not return information for all of the players. I don't know why. UT2003 master server lists are available from Epic Games. Here's the description from the Unreal Technology page: --------------------- We have made server lists available via HTTP for both demo and full version UT2003 servers so that 3rd party server query tools can add UT2003 support. http://ut2003master.epicgames.com/serverlist/full-all.txt http://ut2003master.epicgames.com/serverlist/demo-all.txt These URLs contain a tab-separated list of server IP, game port and query port for all servers in our master server. The query port is the port number the server is listening on for UDP queries. The query format and response is exactly the same as UT 1. The source code to the game server's query responder is in the UdpGameSpyQuery UnrealScript class. --------------------- qstat-2.17/info/a2s.txt000066400000000000000000000123161412457473700150010ustar00rootroot00000000000000Server Queries The Source engine allows you to query information from a running game server using UDP/IP packets. This document describes the packet formats and protocol to access this data. Basic Data Types All server queries consist of 5 basic types of data packed together into a data stream. All types are little endian. Name Description byte 8 bit character short 16 bit signed integer long 32 bit signed integer float 32 bit float value string variable length byte field, terminated by 0x00 Query Types The server responds to 4 queries: • A2S_SERVERQUERY_GETCHALLENGE - Returns a challenge number for use in the player and rules query. • A2S_INFO - Basic information about the server. • A2S_PLAYER - Details about each player on the server. • A2S_RULES - The rules the server is using. Queries should be sent in UDP packets to the listen port of the server, which is typically port 27015. A2S_SERVERQUERY_GETCHALLENGE Request format Challenge values are required for A2S_PLAYER and A2S_RULES requests, you can use this request to get one. Note: You can also send A2S_PLAYER and A2S_RULES queries with a challenge value of -1 (0xFF FF FF FF FF FF FF FF) and they will respond with a challenge value to use (using the reply format below). FF FF FF FF 57 Reply format Data Type Comment Type byte Should be equal to 'A' (0x41) Challenge long The challenge number to use Example reply: FF FF FF FF FF 41 32 42 59 45 53 93 43 71 A2S_INFO Request format Server info can be requested by sending the following byte values in a UDP packet to the server. FF FF FF FF 54 53 6F 75 72 63 65 20 45 6E 67 69 6E 65 20 51 75 65 72 79 00 Reply format Data Type Comment Type byte Should be equal to 'I' (0x49) Version byte Network version Hostname string The Servers name Map string The current map being played Game Directory string The Game type Game string A friendly string name for the game type Description AppID short Steam Application number (currently always set to 0) Num players byte The number of players currently on the server Max players byte Maximum allowed players for the server Num of bots byte Number of bot players currently on the server Dedicated byte Set to 1 for dedicated servers OS byte 'l' for Linux, 'w' for Windows Password byte If set to 1 a password is required to join this server Secure byte If set to 1 this server is running VAC Game Version string The version of the game Example reply: FF FF FF FF 49 02 67 61 6D 65 32 78 73 2E 63 6F ....I.game2xs.co 6D 20 43 6F 75 6E 74 65 72 2D 53 74 72 69 6B 65 m.Counter-Strike 20 53 6F 75 72 63 65 20 23 31 00 64 65 5F 64 75 .Source.#1.de_du 73 74 00 63 73 74 72 69 6B 65 00 43 6F 75 6E 74 st.cstrike.Count 65 72 2D 53 74 72 69 6B 65 00 00 00 0B 28 00 64 er-Strike....(.d 6C 00 00 31 2e 31 2e 30 2e 31 36 00 l..1.1.0.16 A2S_PLAYER Request format FF FF FF FF 55 <4 byte challenge number> The challenge number can either be set to -1 (0xFF FF FF FF FF FF FF FF) to have the server reply with S2C_CHALLENGE, or use the value from a previous A2S_SERVERQUERY_GETCHALLENGE request. Reply format The players response has two sections, the initial header: Data Type Comment Type byte Should be equal to 'D' (0x44) Num Players byte The number of players reported in this response Then for each player the following fields are sent: Data Type Comment Index byte The index into [0.. Num Players] for this entry Player Name string Player's name Kills long Number of kills this player has Time connected float The time in seconds this player has been connected A2S_RULES Request format FF FF FF FF 56 <4 byte challenge number> The challenge number can either be set to -1 (0xFF FF FF FF FF FF FF FF) to have the server reply with S2C_CHALLENGE, or use the value from a previous A2S_SERVERQUERY_GETCHALLENGE request. Reply format The rules response has two sections, the initial header: Data Type Comment Type byte Should be equal to 'E' (0x45) Num Rules short The number of rules reported in this response Then for each rule the following fields are sent: Data Type Comment Rule Name string The name of the rule Rule Value string The rules value â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â” (C) 2004 Valve Corporation. All rights reserved. Valve, the Valve logo, Half-Life, the Half-Life logo, the Lambda logo, Steam, the Steam logo, Team Fortress, the Team Fortress logo, Opposing Force, Day of Defeat, the Day of Defeat logo, Counter-Strike, the Counter-Strike logo, Source, the Source logo, Hammer and Counter-Strike: Condition Zero are trademarks and/or registered trademarks of Valve Corporation. Microsoft and Visual Studio are trademarks and/or registered trademarks of Microsoft Corporation. All other trademarks are property of their respective owners. qstat-2.17/ksp.c000066400000000000000000000124131412457473700135570ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * KSP query protocol * Copyright 2014 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms * */ #include #ifndef _WIN32 #include #include #include #else #include #endif #include #include #include #include "debug.h" #include "utils.h" #include "qstat.h" #include "md5.h" #include "packet_manip.h" char * decode_ksp_val(char *val) { // Very basic html conversion val = str_replace(val, """, "\""); return (str_replace(val, "&", "&")); } query_status_t send_ksp_request_packet(struct qserver *server) { char buf[256]; server->saved_data.pkt_max = -1; sprintf(buf, "GET / HTTP/1.1\015\012User-Agent: qstat\015\012Host: %s:%d\015\012\015\012", server->host_name, server->port); return (send_packet(server, buf, strlen(buf))); } query_status_t valid_ksp_response(struct qserver *server, char *rawpkt, int pktlen) { char *s; int len; int cnt = packet_count(server); if ((0 == cnt) && (0 != strncmp("HTTP/1.1 200 OK", rawpkt, 15))) { // not valid response debug(2, "Invalid"); return (REQ_ERROR); } s = strnstr(rawpkt, "Content-Length: ", pktlen); if (s == NULL) { // not valid response debug(2, "Invalid (no content-length)"); return (INPROGRESS); } s += 16; // TODO: remove this bug work around if (*s == ':') { s += 2; } if (sscanf(s, "%d", &len) != 1) { debug(2, "Invalid (no length)"); return (INPROGRESS); } s = strnstr(rawpkt, "\015\012\015\012", pktlen); if (s == NULL) { debug(2, "Invalid (no end of header"); return (INPROGRESS); } s += 4; if (pktlen != (s - rawpkt + len)) { debug(2, "Outstanding data"); return (INPROGRESS); } debug(2, "Valid data"); return (DONE_FORCE); } char * ksp_json_attrib(char *line, char *name) { char *q, *p, *val; p = strstr(line, name); if (p == NULL) { return (NULL); } p += strlen(name); if (strlen(p) < 3) { return (NULL); } p += 2; if (*p == '"') { // String p++; q = strchr(p, '"'); if (q == NULL) { return (NULL); } } else { // Integer, bool etc q = strchr(p, ','); if (q == NULL) { return (NULL); } } *q = '\0'; val = strdup(p); *q = '"'; debug(4, "%s = %s", name, val); return (val); } query_status_t deal_with_ksp_packet(struct qserver *server, char *rawpkt, int pktlen) { char *s, *val, *line; query_status_t state = INPROGRESS; debug(2, "processing..."); if (!server->combined) { state = valid_ksp_response(server, rawpkt, pktlen); server->retry1 = n_retries; if (server->n_requests == 0) { server->ping_total = time_delta(&packet_recv_time, &server->packet_time1); server->n_requests++; } switch (state) { case INPROGRESS: { // response fragment recieved int pkt_id; int pkt_max; // We're expecting more to come debug(5, "fragment recieved..."); pkt_id = packet_count(server); pkt_max = pkt_id + 1; if (!add_packet(server, 0, pkt_id, pkt_max, pktlen, rawpkt, 1)) { // fatal error e.g. out of memory return (MEM_ERROR); } // combine_packets will call us recursively return (combine_packets(server)); } case DONE_FORCE: break; // single packet response fall through default: return (state); } } if (state != DONE_FORCE) { state = valid_ksp_response(server, rawpkt, pktlen); switch (state) { case DONE_FORCE: break; // actually process default: return (state); } } // Correct ping // Not quite right but gives a good estimate server->ping_total = (server->ping_total * server->n_requests) / 2; debug(3, "processing response..."); s = rawpkt; // Ensure we're null terminated (will only loose the last \x0a) s[pktlen - 1] = '\0'; s = decode_ksp_val(s); line = strtok(s, "\012"); // NOTE: id=XXX and msg=XXX will be processed by the mod following the one they where the response of while (line != NULL) { debug(4, "LINE: %s\n", line); if (strstr(line, "{") != NULL) { debug(1, "{..."); // { // "cheats":true, // "game_mode":"SANDBOX", // "lastPlayerActivity":81403, // "max_players":12, // "modControlSha":"e46569487926a3273f58e06a080b0747b0ae702ec1877906511fe2c29816528b", // "mod_control":1, // "player_count":0, // "players":"", // "port":6752, // "protocol_version":25, // "server_name":"Multiplay :: Online - Clanserver", // "universeSize":96576, // "version":"v0.1.5.6" // } // Server Name val = ksp_json_attrib(line, "server_name"); if (val != NULL) { server->server_name = val; } else { server->server_name = strdup("Unknown"); } // Map Name val = ksp_json_attrib(line, "mapName"); if (val != NULL) { server->map_name = val; } else { server->map_name = strdup("Default"); } // Max Players val = ksp_json_attrib(line, "max_players"); if (val != NULL) { server->max_players = atoi(val); free(val); } else { server->max_players = get_param_ui_value(server, "max_players", 1); } // Num Players val = ksp_json_attrib(line, "player_count"); if (val != NULL) { server->num_players = atoi(val); free(val); } else { server->num_players = 0; } } line = strtok(NULL, "\012"); } gettimeofday(&server->packet_time1, NULL); return (DONE_FORCE); } qstat-2.17/ksp.h000066400000000000000000000006461412457473700135710ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * KSP query protocol * Copyright 2014 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_KSP_H #define QSTAT_KSP_H #include "qserver.h" // Packet processing methods query_status_t deal_with_ksp_packet(struct qserver *server, char *pkt, int pktlen); query_status_t send_ksp_request_packet(struct qserver *server); #endif qstat-2.17/md5.c000066400000000000000000000323571412457473700134600ustar00rootroot00000000000000/* * Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved. * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. * * L. Peter Deutsch * ghost@aladdin.com * */ /* $Id$ */ /* * Independent implementation of MD5 (RFC 1321). * * This code implements the MD5 Algorithm defined in RFC 1321, whose * text is available at * http://www.ietf.org/rfc/rfc1321.txt * The code is derived from the text of the RFC, including the test suite * (section A.5) but excluding the rest of Appendix A. It does not include * any code or documentation that is identified in the RFC as being * copyrighted. * * The original and principal author of md5.c is L. Peter Deutsch * . Other authors are noted in the change history * that follows (in reverse chronological order): * * 2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order * either statically or dynamically; added missing #include * in library. * 2002-03-11 lpd Corrected argument list for main(), and added int return * type, in test program and T value program. * 2002-02-21 lpd Added missing #include in test program. * 2000-07-03 lpd Patched to eliminate warnings about "constant is * unsigned in ANSI C, signed in traditional"; made test program * self-checking. * 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. * 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5). * 1999-05-03 lpd Original version. */ #include "md5.h" #include #undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */ #ifdef ARCH_IS_BIG_ENDIAN #define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1) #else #define BYTE_ORDER 0 #endif #define T_MASK ((md5_word_t) ~0) #define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87) #define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9) #define T3 0x242070db #define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111) #define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050) #define T6 0x4787c62a #define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec) #define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe) #define T9 0x698098d8 #define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850) #define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e) #define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841) #define T13 0x6b901122 #define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c) #define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71) #define T16 0x49b40821 #define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d) #define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf) #define T19 0x265e5a51 #define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855) #define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2) #define T22 0x02441453 #define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e) #define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437) #define T25 0x21e1cde6 #define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829) #define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278) #define T28 0x455a14ed #define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa) #define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07) #define T31 0x676f02d9 #define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375) #define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd) #define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e) #define T35 0x6d9d6122 #define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3) #define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb) #define T38 0x4bdecfa9 #define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f) #define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f) #define T41 0x289b7ec6 #define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805) #define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a) #define T44 0x04881d05 #define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6) #define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a) #define T47 0x1fa27cf8 #define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a) #define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb) #define T50 0x432aff97 #define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58) #define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6) #define T53 0x655b59c3 #define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d) #define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82) #define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e) #define T57 0x6fa87e4f #define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f) #define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb) #define T60 0x4e0811a1 #define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d) #define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca) #define T63 0x2ad7d2bb #define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e) static void md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/) { md5_word_t a = pms->abcd[0], b = pms->abcd[1], c = pms->abcd[2], d = pms->abcd[3]; md5_word_t t; #if BYTE_ORDER > 0 /* Define storage only for big-endian CPUs. */ md5_word_t X[16]; #else /* Define storage for little-endian or both types of CPUs. */ md5_word_t xbuf[16]; const md5_word_t *X; #endif { #if BYTE_ORDER == 0 /* * Determine dynamically whether this is a big-endian or * little-endian machine, since we can use a more efficient * algorithm on the latter. */ static const int w = 1; if (*((const md5_byte_t *)&w)) /* dynamic little-endian */ #endif #if BYTE_ORDER <= 0 /* little-endian */ { /* * On little-endian machines, we can process properly aligned * data without copying it. */ if (!((data - (const md5_byte_t *)0) & 3)) { /* data are properly aligned */ X = (const md5_word_t *)data; } else { /* not aligned */ memcpy(xbuf, data, 64); X = xbuf; } } #endif #if BYTE_ORDER == 0 else /* dynamic big-endian */ #endif #if BYTE_ORDER >= 0 /* big-endian */ { /* * On big-endian machines, we must arrange the bytes in the * right order. */ const md5_byte_t *xp = data; int i; #if BYTE_ORDER == 0 X = xbuf; /* (dynamic only) */ #else #define xbuf X /* (static only) */ #endif for (i = 0; i < 16; ++i, xp += 4) { xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24); } } #endif } #define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) /* Round 1. */ /* Let [abcd k s i] denote the operation * a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */ #define F(x, y, z) (((x) & (y)) | (~(x) & (z))) #define SET(a, b, c, d, k, s, Ti) \ t = a + F(b, c, d) + X[k] + Ti; \ a = ROTATE_LEFT(t, s) + b /* Do the following 16 operations. */ SET(a, b, c, d, 0, 7, T1); SET(d, a, b, c, 1, 12, T2); SET(c, d, a, b, 2, 17, T3); SET(b, c, d, a, 3, 22, T4); SET(a, b, c, d, 4, 7, T5); SET(d, a, b, c, 5, 12, T6); SET(c, d, a, b, 6, 17, T7); SET(b, c, d, a, 7, 22, T8); SET(a, b, c, d, 8, 7, T9); SET(d, a, b, c, 9, 12, T10); SET(c, d, a, b, 10, 17, T11); SET(b, c, d, a, 11, 22, T12); SET(a, b, c, d, 12, 7, T13); SET(d, a, b, c, 13, 12, T14); SET(c, d, a, b, 14, 17, T15); SET(b, c, d, a, 15, 22, T16); #undef SET /* Round 2. */ /* Let [abcd k s i] denote the operation * a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */ #define G(x, y, z) (((x) & (z)) | ((y) & ~(z))) #define SET(a, b, c, d, k, s, Ti) \ t = a + G(b, c, d) + X[k] + Ti; \ a = ROTATE_LEFT(t, s) + b /* Do the following 16 operations. */ SET(a, b, c, d, 1, 5, T17); SET(d, a, b, c, 6, 9, T18); SET(c, d, a, b, 11, 14, T19); SET(b, c, d, a, 0, 20, T20); SET(a, b, c, d, 5, 5, T21); SET(d, a, b, c, 10, 9, T22); SET(c, d, a, b, 15, 14, T23); SET(b, c, d, a, 4, 20, T24); SET(a, b, c, d, 9, 5, T25); SET(d, a, b, c, 14, 9, T26); SET(c, d, a, b, 3, 14, T27); SET(b, c, d, a, 8, 20, T28); SET(a, b, c, d, 13, 5, T29); SET(d, a, b, c, 2, 9, T30); SET(c, d, a, b, 7, 14, T31); SET(b, c, d, a, 12, 20, T32); #undef SET /* Round 3. */ /* Let [abcd k s t] denote the operation * a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */ #define H(x, y, z) ((x) ^ (y) ^ (z)) #define SET(a, b, c, d, k, s, Ti) \ t = a + H(b, c, d) + X[k] + Ti; \ a = ROTATE_LEFT(t, s) + b /* Do the following 16 operations. */ SET(a, b, c, d, 5, 4, T33); SET(d, a, b, c, 8, 11, T34); SET(c, d, a, b, 11, 16, T35); SET(b, c, d, a, 14, 23, T36); SET(a, b, c, d, 1, 4, T37); SET(d, a, b, c, 4, 11, T38); SET(c, d, a, b, 7, 16, T39); SET(b, c, d, a, 10, 23, T40); SET(a, b, c, d, 13, 4, T41); SET(d, a, b, c, 0, 11, T42); SET(c, d, a, b, 3, 16, T43); SET(b, c, d, a, 6, 23, T44); SET(a, b, c, d, 9, 4, T45); SET(d, a, b, c, 12, 11, T46); SET(c, d, a, b, 15, 16, T47); SET(b, c, d, a, 2, 23, T48); #undef SET /* Round 4. */ /* Let [abcd k s t] denote the operation * a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */ #define I(x, y, z) ((y) ^ ((x) | ~(z))) #define SET(a, b, c, d, k, s, Ti) \ t = a + I(b, c, d) + X[k] + Ti; \ a = ROTATE_LEFT(t, s) + b /* Do the following 16 operations. */ SET(a, b, c, d, 0, 6, T49); SET(d, a, b, c, 7, 10, T50); SET(c, d, a, b, 14, 15, T51); SET(b, c, d, a, 5, 21, T52); SET(a, b, c, d, 12, 6, T53); SET(d, a, b, c, 3, 10, T54); SET(c, d, a, b, 10, 15, T55); SET(b, c, d, a, 1, 21, T56); SET(a, b, c, d, 8, 6, T57); SET(d, a, b, c, 15, 10, T58); SET(c, d, a, b, 6, 15, T59); SET(b, c, d, a, 13, 21, T60); SET(a, b, c, d, 4, 6, T61); SET(d, a, b, c, 11, 10, T62); SET(c, d, a, b, 2, 15, T63); SET(b, c, d, a, 9, 21, T64); #undef SET /* Then perform the following additions. (That is increment each * of the four registers by the value it had before this block * was started.) */ pms->abcd[0] += a; pms->abcd[1] += b; pms->abcd[2] += c; pms->abcd[3] += d; } void md5_init(md5_state_t *pms) { pms->count[0] = pms->count[1] = 0; pms->abcd[0] = 0x67452301; pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476; pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301; pms->abcd[3] = 0x10325476; } void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes) { const md5_byte_t *p = data; int left = nbytes; int offset = (pms->count[0] >> 3) & 63; md5_word_t nbits = (md5_word_t)(nbytes << 3); if (nbytes <= 0) { return; } /* Update the message length. */ pms->count[1] += nbytes >> 29; pms->count[0] += nbits; if (pms->count[0] < nbits) { pms->count[1]++; } /* Process an initial partial block. */ if (offset) { int copy = (offset + nbytes > 64 ? 64 - offset : nbytes); memcpy(pms->buf + offset, p, copy); if (offset + copy < 64) { return; } p += copy; left -= copy; md5_process(pms, pms->buf); } /* Process full blocks. */ for ( ; left >= 64; p += 64, left -= 64) { md5_process(pms, p); } /* Process a final partial block. */ if (left) { memcpy(pms->buf, p, left); } } void md5_finish(md5_state_t *pms, md5_byte_t digest[16]) { static const md5_byte_t pad[64] = { 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; md5_byte_t data[8]; int i; /* Save the length before padding. */ for (i = 0; i < 8; ++i) { data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3)); } /* Pad to 56 bytes mod 64. */ md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1); /* Append the length. */ md5_append(pms, data, 8); for (i = 0; i < 16; ++i) { digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3)); } } static const char hexchar[] = "0123456789abcdef"; char * md5_hex(const char *bytes, int nbytes) { char out[33]; char *o = out + 32; char digest[16]; char *digestp = digest + 16; md5_state_t md5; out[32] = '\0'; md5_init(&md5); md5_append(&md5, (unsigned char *)bytes, nbytes); md5_finish(&md5, (unsigned char *)digest); do { *--o = hexchar[*--digestp & 0x0F]; *--o = hexchar[(*digestp >> 4) & 0x0F]; } while (o != out); return (strdup(out)); } qstat-2.17/md5.h000066400000000000000000000067201412457473700134600ustar00rootroot00000000000000/* * Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved. * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. * * L. Peter Deutsch * ghost@aladdin.com * */ /* $Id$ */ /* * Independent implementation of MD5 (RFC 1321). * * This code implements the MD5 Algorithm defined in RFC 1321, whose * text is available at * http://www.ietf.org/rfc/rfc1321.txt * The code is derived from the text of the RFC, including the test suite * (section A.5) but excluding the rest of Appendix A. It does not include * any code or documentation that is identified in the RFC as being * copyrighted. * * The original and principal author of md5.h is L. Peter Deutsch * . Other authors are noted in the change history * that follows (in reverse chronological order): * * 2002-04-13 lpd Removed support for non-ANSI compilers; removed * references to Ghostscript; clarified derivation from RFC 1321; * now handles byte order either statically or dynamically. * 1999-11-04 lpd Edited comments slightly for automatic TOC extraction. * 1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5); * added conditionalization for C++ compilation from Martin * Purschke . * 1999-05-03 lpd Original version. */ #ifndef md5_INCLUDED #define md5_INCLUDED /* * This package supports both compile-time and run-time determination of CPU * byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be * compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is * defined as non-zero, the code will be compiled to run only on big-endian * CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to * run on either big- or little-endian CPUs, but will run slightly less * efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined. */ typedef unsigned char md5_byte_t; /* 8-bit byte */ typedef unsigned int md5_word_t; /* 32-bit word */ /* Define the state of the MD5 Algorithm. */ typedef struct md5_state_s { md5_word_t count[2]; /* message length in bits, lsw first */ md5_word_t abcd[4]; /* digest buffer */ md5_byte_t buf[64]; /* accumulate block */ } md5_state_t; #ifdef __cplusplus extern "C" { #endif /* Initialize the algorithm. */ void md5_init(md5_state_t *pms); /* Append a string to the message. */ void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes); /* Finish the message and return the digest. */ void md5_finish(md5_state_t *pms, md5_byte_t digest[16]); /* Return an hex md5 of the given string */ char *md5_hex(const char *bytes, int nbytes); #ifdef __cplusplus } /* end extern "C" */ #endif #endif /* md5_INCLUDED */ qstat-2.17/mumble.c000066400000000000000000000031611412457473700142430ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * Mumble protocol * Copyright 2012 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms * */ #include #include #include #ifndef _WIN32 #include #endif #include "debug.h" #include "qstat.h" #include "packet_manip.h" query_status_t send_mumble_request_packet(struct qserver *server) { return (send_packet(server, server->type->status_packet, server->type->status_len)); } query_status_t deal_with_mumble_packet(struct qserver *server, char *rawpkt, int pktlen) { // skip unimplemented ack, crc, etc char *pkt = rawpkt; char bandwidth[11]; char version[12]; server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); if ((24 != pktlen) || (0 != memcmp(pkt + 4, server->type->status_packet + 4, 8))) { // unknown packet return (PKT_ERROR); } // version server->protocol_version = ntohl(*(unsigned long *)pkt); sprintf(version, "%u.%u.%u", (unsigned char)*pkt + 1, (unsigned char)*pkt + 2, (unsigned char)*pkt + 3); add_rule(server, "version", version, NO_FLAGS); pkt += 4; // ident pkt += 8; // num players server->num_players = ntohl(*(unsigned long *)pkt); pkt += 4; // max players server->max_players = ntohl(*(unsigned long *)pkt); pkt += 4; // allowed bandwidth sprintf(bandwidth, "%d", ntohl(*(unsigned long *)pkt)); add_rule(server, "allowed_bandwidth", bandwidth, NO_FLAGS); pkt += 4; // Unknown details server->map_name = strdup("N/A"); server->server_name = strdup("Unknown"); add_rule(server, "gametype", "Unknown", NO_FLAGS); return (DONE_FORCE); } qstat-2.17/mumble.h000066400000000000000000000006571412457473700142570ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * Mumble protocol * Copyright 2012 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_MUMBLE_H #define QSTAT_MUMBLE_H #include "qserver.h" // Packet processing methods query_status_t deal_with_mumble_packet(struct qserver *server, char *pkt, int pktlen); query_status_t send_mumble_request_packet(struct qserver *server); #endif qstat-2.17/ottd.c000066400000000000000000000201731412457473700137360ustar00rootroot00000000000000/* * qstat * * opentTTD protocol * Copyright 2007 Ludwig Nussel * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #include #ifndef _WIN32 #include #endif #include #include #include "qstat.h" #include "qserver.h" #include "debug.h" enum { MAX_VEHICLE_TYPES = 5, MAX_STATION_TYPES = 5 }; static const char *vehicle_types[] = { "num_trains", "num_trucks", "num_busses", "num_aircrafts", "num_ships", }; static const char *station_types[] = { "num_stations", "num_truckbays", "num_busstations", "num_airports", "num_docks", }; query_status_t deal_with_ottdmaster_packet(struct qserver *server, char *rawpkt, int pktlen) { unsigned num; server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); server->server_name = MASTER; if (swap_short_from_little(rawpkt) != pktlen) { malformed_packet(server, "invalid packet length"); return (PKT_ERROR); } if (rawpkt[2] != 7) { malformed_packet(server, "invalid packet type"); return (PKT_ERROR); } if (rawpkt[3] != 1) { malformed_packet(server, "invalid packet version"); return (PKT_ERROR); } num = swap_short_from_little(&rawpkt[4]); rawpkt += 6; pktlen -= 6; if (num && (num * 6 <= pktlen)) { unsigned i; server->master_pkt = (char *)realloc(server->master_pkt, server->master_pkt_len + pktlen); memset(server->master_pkt + server->master_pkt_len, 0, pktlen); server->master_pkt_len += pktlen; for (i = 0; i < num * 6; i += 6) { memcpy(&server->master_pkt[i], &rawpkt[i], 4); server->master_pkt[i + 4] = rawpkt[i + 5]; server->master_pkt[i + 5] = rawpkt[i + 4]; } server->n_servers += num; } else { malformed_packet(server, "invalid packet"); return (PKT_ERROR); } bind_sockets(); return (DONE_AUTO); } #define xstr(s) str(s) #define str(s) # s #define GET_STRING \ do { \ str = (char *)ptr; \ ptr = memchr(ptr, '\0', end - ptr); \ if (!ptr) \ { \ malformed_packet(server, "%s:%s invalid packet", __FILE__, xstr(__LINE__)); \ return PKT_ERROR; \ } \ ++ptr; \ } while (0) #define FAIL_IF(cond, msg) \ if ((cond)) { \ malformed_packet(server, "%s:%s %s", __FILE__, xstr(__LINE__), msg); \ return PKT_ERROR; \ } #define INVALID_IF(cond) \ FAIL_IF(cond, "invalid packet") query_status_t deal_with_ottd_packet(struct qserver *server, char *rawpkt, int pktlen) { unsigned char *ptr = (unsigned char *)rawpkt; unsigned char *end = (unsigned char *)(rawpkt + pktlen); unsigned char type; char *str; char buf[32]; unsigned ver; server->n_servers++; if (server->server_name == NULL) { server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); server->n_requests++; } else { gettimeofday(&server->packet_time1, NULL); } FAIL_IF(pktlen < 4 || swap_short_from_little(rawpkt) > pktlen, "invalid packet"); type = ptr[2]; ver = ptr[3]; ptr += 4; debug(3, "len %hu type %hhu ver %u", swap_short_from_little(rawpkt), type, ver); FAIL_IF(ver != 4 && ver != 5, "only version 4 and 5 servers are supported"); if (type == 1) {// info packet unsigned numgrf = *ptr; FAIL_IF(ptr + numgrf * 20 + 1 > end, "invalid newgrf number"); ptr += numgrf * 20 + 1; snprintf(buf, sizeof(buf), "%u", swap_long_from_little(ptr)); add_rule(server, "date_days", buf, NO_FLAGS); ptr += 4; snprintf(buf, sizeof(buf), "%u", swap_long_from_little(ptr)); add_rule(server, "startdate_days", buf, NO_FLAGS); ptr += 4; FAIL_IF(ptr + 3 > end, "invalid packet"); snprintf(buf, sizeof(buf), "%hhu", ptr[0]); add_rule(server, "maxcompanies", buf, NO_FLAGS); snprintf(buf, sizeof(buf), "%hhu", ptr[1]); add_rule(server, "numcompanies", buf, NO_FLAGS); server->max_spectators = ptr[2]; ptr += 3; GET_STRING; server->server_name = strdup(str); GET_STRING; add_rule(server, "version", str, NO_FLAGS); FAIL_IF(ptr + 7 > end, "invalid packet"); { static const char *langs[] = { "any", "English", "German", "French" }; unsigned i = *ptr++; if (i > 3) { i = 0; } add_rule(server, "language", (char *)langs[i], NO_FLAGS); } add_rule(server, "password", *ptr++ ? "1" : "0", NO_FLAGS); server->max_players = *ptr++; server->num_players = *ptr++; server->num_spectators = *ptr++; GET_STRING; server->map_name = strdup(str); snprintf(buf, sizeof(buf), "%hu", swap_short_from_little(ptr)); add_rule(server, "map_width", buf, NO_FLAGS); snprintf(buf, sizeof(buf), "%hu", swap_short_from_little(ptr)); add_rule(server, "map_height", buf, NO_FLAGS); { static const char *sets[] = { "temperate", "arctic", "desert", "toyland" }; unsigned i = *ptr++; if (i > 3) { i = 0; } add_rule(server, "map_set", (char *)sets[i], NO_FLAGS); } add_rule(server, "dedicated", *ptr++ ? "1" : "0", NO_FLAGS); } else if (type == 3) { // player packet unsigned i, j; INVALID_IF(ptr + 2 > end); server->num_players = *ptr++; for (i = 0; i < server->num_players; ++i) { unsigned long long lli; struct player *player; unsigned char nr; nr = *ptr++; debug(3, "player number %d", nr); player = add_player(server, i); FAIL_IF(!player, "can't allocate player"); GET_STRING; player->name = strdup(str); debug(3, "name %s", str); player->frags = 0; INVALID_IF(ptr + 4 + 3 * 8 + 2 + 1 + 2 * MAX_VEHICLE_TYPES + 2 * MAX_STATION_TYPES > end); snprintf(buf, sizeof(buf), "%u", swap_long_from_little(ptr)); player_add_info(player, "startdate", buf, 0); ptr += 4; lli = swap_long_from_little(ptr + 4); lli <<= 32; lli += swap_long_from_little(ptr); snprintf(buf, sizeof(buf), "%lld", lli); player_add_info(player, "value", buf, 0); ptr += 8; lli = swap_long_from_little(ptr + 4); lli <<= 32; lli = swap_long_from_little(ptr); snprintf(buf, sizeof(buf), "%lld", lli); player_add_info(player, "money", buf, 0); ptr += 8; lli = swap_long_from_little(ptr + 4); lli <<= 32; lli += swap_long_from_little(ptr); snprintf(buf, sizeof(buf), "%lld", lli); player_add_info(player, "income", buf, 0); ptr += 8; snprintf(buf, sizeof(buf), "%hu", swap_short_from_little(ptr)); player_add_info(player, "performance", buf, 0); ptr += 2; player_add_info(player, "password", *ptr ? "1" : "0", 0); ++ptr; for (j = 0; j < MAX_VEHICLE_TYPES; ++j) { snprintf(buf, sizeof(buf), "%hu", swap_short_from_little(ptr)); player_add_info(player, (char *)vehicle_types[j], buf, 0); ptr += 2; } for (j = 0; j < MAX_STATION_TYPES; ++j) { snprintf(buf, sizeof(buf), "%hu", swap_short_from_little(ptr)); player_add_info(player, (char *)station_types[j], buf, 0); ptr += 2; } if (ver != 5) { // connections while (ptr + 1 < end && *ptr) { ++ptr; GET_STRING; // client name debug(3, "%s played by %s", str, player->name); GET_STRING; // id INVALID_IF(ptr + 4 > end); ptr += 4; } ++ptr; // record terminated by zero byte } } // spectators while (ptr + 1 < end && *ptr) { ++ptr; GET_STRING; // client name debug(3, "spectator %s", str); GET_STRING; // id INVALID_IF(ptr + 4 > end); ptr += 4; } ++ptr; // record terminated by zero byte server->next_rule = NO_SERVER_RULES; // we're done server->next_player_info = server->num_players; // we're done } else { malformed_packet(server, "invalid type"); return (PKT_ERROR); } server->retry1 = n_retries; // we're done with this packet, reset retry counter return (DONE_AUTO); } query_status_t send_ottdmaster_request_packet(struct qserver *server) { return (qserver_send_initial(server, server->type->master_packet, server->type->master_len)); } query_status_t send_ottd_request_packet(struct qserver *server) { qserver_send_initial(server, server->type->status_packet, server->type->status_len); if (get_server_rules || get_player_info) { server->next_rule = ""; // trigger calling send_a2s_rule_request_packet } return (INPROGRESS); } qstat-2.17/ottd.h000066400000000000000000000010331412457473700137350ustar00rootroot00000000000000/* * qstat * * opentTTD protocol * Copyright 2007 Ludwig Nussel * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_OTTD_H #define QSTAT_OTTD_H #include "qstat.h" query_status_t send_ottdmaster_request_packet(struct qserver *server); query_status_t deal_with_ottdmaster_packet(struct qserver *server, char *rawpkt, int pktlen); query_status_t send_ottd_request_packet(struct qserver *server); query_status_t deal_with_ottd_packet(struct qserver *server, char *rawpkt, int pktlen); #endif qstat-2.17/packet_manip.c000066400000000000000000000144461412457473700154250ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * Packet module * Copyright 2005 Steven Hartland based on code by Steve Jankowski * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #include "qstat.h" #include "debug.h" #include #include #define MAX_PACKETS 8 #define MAX_FAGMENTS 128 int pkt_seq = 0; int pkt_id_index = -1; int n_ids; int counts[MAX_PACKETS]; SavedData *segments[MAX_PACKETS][MAX_FAGMENTS]; int combine_packets(struct qserver *server) { unsigned int ids[MAX_PACKETS]; int maxes[MAX_PACKETS]; int lengths[MAX_PACKETS]; SavedData *sdata = &server->saved_data; int i, p, ret = INPROGRESS; n_ids = 0; memset(&segments[0][0], 0, sizeof(segments)); memset(&counts[0], 0, sizeof(counts)); memset(&lengths[0], 0, sizeof(lengths)); // foreach packet for ( ; sdata != NULL; sdata = sdata->next) { debug(4, "max:%d, id:%d\n", sdata->pkt_max, sdata->pkt_id); if (sdata->pkt_max == 0) { // not expecting multi packets or already processed? continue; } if (sdata->pkt_index >= MAX_FAGMENTS) { // we only deal up to MAX_FAGMENTS packet fragment fprintf(stderr, "Too many fragments %d for packetid %d max %d\n", sdata->pkt_index, sdata->pkt_id, MAX_FAGMENTS); return (PKT_ERROR); } for (i = 0; i < n_ids; i++) { if (sdata->pkt_id == ids[i]) { // found this packetid break; } } if (i >= n_ids) { // packetid we havent seen yet if (n_ids >= MAX_PACKETS) { // we only deal up to MAX_PACKETS packetids fprintf(stderr, "Too many distinct packetids %d max %d\n", n_ids, MAX_PACKETS); return (PKT_ERROR); } ids[n_ids] = sdata->pkt_id; maxes[n_ids] = sdata->pkt_max; i = n_ids++; } else if (maxes[i] != sdata->pkt_max) { // max's dont match debug(4, "max mismatch %d != %d", maxes[i], sdata->pkt_max); continue; } if (segments[i][sdata->pkt_index] == NULL) { // add the packet to the list of segments segments[i][sdata->pkt_index] = sdata; counts[i]++; lengths[i] += sdata->datalen; } else { debug(2, "duplicate packet detected for id %d, index %d", sdata->pkt_id, sdata->pkt_index); } } // foreach distinct packetid for (pkt_id_index = 0; pkt_id_index < n_ids; pkt_id_index++) { char *combined; int datalen = 0; int combinedlen; if (counts[pkt_id_index] != maxes[pkt_id_index]) { // we dont have all the expected packets yet debug(4, "more expected: %d != %d\n", counts[pkt_id_index], maxes[pkt_id_index]); continue; } // combine all the segments combinedlen = lengths[pkt_id_index]; combined = (char *)malloc(combinedlen); for (p = 0; p < counts[pkt_id_index]; p++) { if (segments[pkt_id_index][p] == NULL) { debug(4, "missing segment[%d][%d]", pkt_id_index, p); // reset to be unusable pkt_id_index = -1; free(combined); return (INPROGRESS); } if (datalen + segments[pkt_id_index][p]->datalen > combinedlen) { fprintf(stderr, "Data length %d > combined length %d\n", datalen + segments[pkt_id_index][p]->datalen, combinedlen); // reset to be unusable pkt_id_index = -1; free(combined); return (MEM_ERROR); } memcpy(combined + datalen, segments[pkt_id_index][p]->data, segments[pkt_id_index][p]->datalen); datalen += segments[pkt_id_index][p]->datalen; } // prevent reprocessing? for (p = 0; p < counts[pkt_id_index]; p++) { segments[pkt_id_index][p]->pkt_max = 0; } debug(4, "callback"); if (4 <= get_debug_level()) { print_packet(server, combined, datalen); } // Call the server's packet processing method flagging as a combine call server->combined = 1; ret = ((int (*)())server->type->packet_func)(server, combined, datalen); free(combined); server->combined = 0; // Note: this is currently invalid as packet processing methods // are void not int if ((INPROGRESS != ret) || (NULL == server->saved_data.data)) { break; } } // reset to be unusable pkt_id_index = -1; return (ret); } // NOTE: // pkt_id is the packet aka response identifier // pkt_index is the index of the packet fragment int add_packet(struct qserver *server, unsigned int pkt_id, int pkt_index, int pkt_max, int datalen, char *data, int calc_max) { SavedData *sdata; // safety net for bad data if (datalen == 0) { debug(1, "Empty packet received!"); return (0); } if (server->saved_data.data == NULL) { debug(4, "first packet: %d id, %d index, %d max, %d calc_max", pkt_id, pkt_index, pkt_max, calc_max); sdata = &server->saved_data; } else { debug(4, "another packet: %d id, %d index, %d max, %d calc_max", pkt_id, pkt_index, pkt_max, calc_max); if (calc_max) { // check we have the correct max SavedData *cdata = &server->saved_data; for ( ; cdata != NULL; cdata = cdata->next) { if (cdata->pkt_max > pkt_max) { pkt_max = cdata->pkt_max; } } // ensure all the packets know about this new max for (cdata = &server->saved_data; cdata != NULL; cdata = cdata->next) { cdata->pkt_max = pkt_max; } } debug(4, "calced max = %d", pkt_max); // allocate a new packet data and prepend to the list sdata = (SavedData *)calloc(1, sizeof(SavedData)); sdata->next = server->saved_data.next; server->saved_data.next = sdata; } sdata->pkt_id = pkt_id; sdata->pkt_index = pkt_index; sdata->pkt_max = pkt_max; sdata->datalen = datalen; sdata->data = (char *)malloc(sdata->datalen); if (NULL == sdata->data) { fprintf(stderr, "Out of memory\n"); return (0); } memcpy(sdata->data, data, sdata->datalen); return (1); } int next_sequence() { return (++pkt_seq); } SavedData * get_packet_fragment(int index) { if (-1 == pkt_id_index) { fprintf(stderr, "Invalid call to get_packet_fragment"); return (NULL); } if (index > counts[pkt_id_index]) { debug(4, "Invalid index requested %d > %d", index, pkt_id_index); return (NULL); } return (segments[pkt_id_index][index]); } unsigned combined_length(struct qserver *server, int pkt_id) { SavedData *sdata = &server->saved_data; unsigned len = 0; for ( ; sdata != NULL; sdata = sdata->next) { if (pkt_id == sdata->pkt_id) { len += sdata->datalen; } } return (len); } unsigned packet_count(struct qserver *server) { SavedData *sdata = &server->saved_data; unsigned cnt = 0; if (NULL == sdata->data) { return (0); } for ( ; sdata != NULL; sdata = sdata->next) { cnt++; } return (cnt); } qstat-2.17/packet_manip.h000066400000000000000000000011621412457473700154210ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * Packet module * Copyright 2005 Steven Hartland based on code by Steve Jankowski * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_PACKETS_H #define QSTAT_PACKETS_H #include "qstat.h" int combine_packets(struct qserver *server); int add_packet(struct qserver *server, unsigned int pkt_id, int pkt_index, int pkt_max, int datalen, char *data, int calc_max); int next_sequence(); SavedData *get_packet_fragment(int index); unsigned combined_length(struct qserver *server, int pkt_id); unsigned packet_count(struct qserver *server); #endif qstat-2.17/qserver.c000066400000000000000000000066471412457473700144650ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * qserver functions * Copyright 2004 Ludwig Nussel * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #include "qstat.h" #include "qserver.h" #include "debug.h" #ifndef _WIN32 #include #include #endif #include #include #include #include #include // TODO: get rid of this and use send_packet instead, remove n_requests hack from a2s query_status_t qserver_send_initial(struct qserver *server, const char *data, size_t len) { int status = INPROGRESS; if (data) { int ret; debug(2, "[%s] send", server->type->type_prefix); if (4 <= get_debug_level()) { output_packet(server, data, len, 1); } if (server->flags & FLAG_BROADCAST) { ret = send_broadcast(server, data, len); } else { ret = send(server->fd, data, len, 0); } if (ret == SOCKET_ERROR) { send_error(server, ret); status = SYS_ERROR; } } if ((server->retry1 == n_retries) || server->flags & FLAG_BROADCAST) { gettimeofday(&server->packet_time1, NULL); } else { server->n_retries++; } server->retry1--; server->n_packets++; return (status); } query_status_t qserver_send(struct qserver *server, const char *data, size_t len) { int status = INPROGRESS; if (data) { int ret; if (server->flags & FLAG_BROADCAST) { ret = send_broadcast(server, data, len); } else { ret = send(server->fd, data, len, 0); } if (ret == SOCKET_ERROR) { send_error(server, ret); status = SYS_ERROR; } } server->retry1 = n_retries - 1; gettimeofday(&server->packet_time1, NULL); server->n_requests++; server->n_packets++; return (status); } int send_broadcast(struct qserver *server, const char *pkt, size_t pktlen) { struct sockaddr_in addr; addr.sin_family = AF_INET; if (no_port_offset || server->flags & TF_NO_PORT_OFFSET) { addr.sin_port = htons(server->port); } else { addr.sin_port = htons((unsigned short)(server->port + server->type->port_offset)); } addr.sin_addr.s_addr = server->ipaddr; memset(&(addr.sin_zero), 0, sizeof(addr.sin_zero)); return (sendto(server->fd, (const char *)pkt, pktlen, 0, (struct sockaddr *)&addr, sizeof(addr))); } int register_send(struct qserver *server) { if ((server->retry1 == n_retries) || server->flags & FLAG_BROADCAST) { server->n_requests++; } else { server->n_retries++; } // New request so reset the sent time. This ensures // that we record an accurate ping time even on retry gettimeofday(&server->packet_time1, NULL); server->retry1--; server->n_packets++; return (INPROGRESS); } query_status_t send_packet(struct qserver *server, const char *data, size_t len) { debug(2, "[%s] send", server->type->type_prefix); if (4 <= get_debug_level()) { output_packet(server, data, len, 1); } if (data) { int ret; if (server->flags & FLAG_BROADCAST) { ret = send_broadcast(server, data, len); } else { ret = send(server->fd, data, len, 0); } if (ret == SOCKET_ERROR) { return (send_error(server, ret)); } } register_send(server); return (INPROGRESS); } query_status_t send_error(struct qserver *server, int rc) { unsigned int ipaddr = ntohl(server->ipaddr); const char *errstr = strerror(errno); fprintf(stderr, "Error on %d.%d.%d.%d: %s, skipping ...\n", (ipaddr >> 24) & 0xff, (ipaddr >> 16) & 0xff, (ipaddr >> 8) & 0xff, ipaddr & 0xff, errstr ); return (SYS_ERROR); } qstat-2.17/qserver.h000066400000000000000000000134731412457473700144650ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * qserver functions and definitions * Copyright 2004 Ludwig Nussel * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_QSERVER_H #define QSTAT_QSERVER_H struct query_param { char *key; char *value; int i_value; unsigned int ui_value; struct query_param *next; }; typedef struct SavedData { char *data; int datalen; int pkt_index; int pkt_max; unsigned int pkt_id; struct SavedData *next; } SavedData; typedef enum { STATE_INIT = 0, STATE_CONNECTING = 1, STATE_CONNECTED = 2, STATE_QUERYING = 3, STATE_QUERIED = 4, STATE_TIMEOUT = -1, STATE_SYS_ERROR = -2, } server_state_t; struct qserver { char *arg; char *host_name; unsigned int ipaddr; int flags; server_type *type; int fd; server_state_t state; char *outfilename; char *query_arg; char *challenge_string; struct query_param *params; unsigned long challenge; unsigned short port; unsigned short orig_port; // This port is always constant from creation where as port can be updated based on the query results unsigned short query_port; // State variable to flag if the current processing call is a call from combine_packets // This should really by done via a flag to the method itself but that would require changes // to all handlers :( unsigned short combined; /** \brief number of retries _left_ for status query or rule query. * * That means * if s->retry1 == (global)n_retries then no retries were necessary so far. * if s->retry1 == 0 then the server has to be cleaned up after timeout */ int retry1; /** \brief number retries _left_ for player query. @see retry1 */ int retry2; /** \brief how much retry packets were sent */ int n_retries; /** \brief time when the last packet to the server was sent */ struct timeval packet_time1; struct timeval packet_time2; /** \brief sum of packet deltas * * average server ping is ping_total / n_requests */ int ping_total; /** \brief number of requests send to a server. * * used for ping calculation * @see ping_total */ int n_requests; /** \brief number of packets already sent to this server * * doesn't seemt to have any use */ int n_packets; /** \brief number of servers in master_pkt * * normally master_pkt_len/6 */ int n_servers; /** \brief length of master_pkt */ int master_pkt_len; /** \brief IPs received from master. * * array of four bytes ip, two bytes port in network byte order */ char *master_pkt; /** \brief state info * * used for progressive master 4 bytes for WON 22 for Steam */ char master_query_tag[22]; char *error; /** \brief in-game name of the server. * * A server that has a NULL name did not receive any packets yet and is * considered down after a timeout. */ char *server_name; char *address; char *map_name; char *game; int max_players; int num_players; int protocol_version; int max_spectators; int num_spectators; SavedData saved_data; /** \brief number of the next player to retrieve info for. * * Only meaningful for servers that have type->player_packet. * This is used by q1 as it sends packets for each player individually. * cleanup_qserver() cleans up a server if next_rule == NULL and * next_player_info >= num_players */ int next_player_info; /** \brief number of player info packets received */ int n_player_info; struct player *players; /** \brief name of next rule to retreive * * Used by Q1 as it needs to send a packet for each rule. Other games would * set this to an empty string to indicate that rules need to be retrieved. * After rule packet is received set this to NULL. */ char *next_rule; int n_rules; struct rule *rules; struct rule **last_rule; int missing_rules; struct qserver *next; struct qserver *prev; }; void qserver_disconnect(struct qserver *server); /* server specific query parameters */ void add_query_param(struct qserver *server, char *arg); /** \brief get a parameter for the server as string * * @param server the server to get the value from * @param key which key to get * @param default_value value to return if key was not found * @return value for key or default_value */ char *get_param_value(struct qserver *server, const char *key, char *default_value); /** @see get_param_value */ int get_param_i_value(struct qserver *server, char *key, int default_value); /** @see get_param_value */ unsigned int get_param_ui_value(struct qserver *server, char *key, unsigned int default_value); /** \brief send an initial query packet to a server * * Sends a unicast packet to the server's file descriptor. The descriptor must * be connected. Updates n_requests or n_retries, retry1, n_packets, packet_time1 * * \param server the server * \param data data to send * \param len length of data * \returns number of bytes sent or SOCKET_ERROR */ query_status_t qserver_send_initial(struct qserver *server, const char *data, size_t len); /** \brief send an initial query packet to a server * * Sends a unicast packet to the server's file descriptor. The descriptor must * be connected. Updates n_requests, n_packets, packet_time1. Sets retry1 to * (global) n_retries * * @see qserver_send_initial */ query_status_t qserver_send(struct qserver *server, const char *data, size_t len); int send_broadcast(struct qserver *server, const char *pkt, size_t pktlen); /** * Registers the send of a request packet. * * This updates n_requests, n_packets, packet_time1 and decrements n_retries */ int register_send(struct qserver *server); /** * Sends a packet to the server either direct or via broadcast. * * Once sent calls register_send to register the send of the packet */ query_status_t send_packet(struct qserver *server, const char *data, size_t len); /** * Logs the error from a socket send */ query_status_t send_error(struct qserver *server, int rc); #endif qstat-2.17/qstat.c000066400000000000000000010524601412457473700141250ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * steve@qstat.org * http://www.qstat.org * * Thanks to Per Hammer for the OS/2 patches (per@mindbend.demon.co.uk) * Thanks to John Ross Hunt for the OpenVMS Alpha patches (bigboote@ais.net) * Thanks to Scott MacFiggen for the quicksort code (smf@webmethods.com) * Thanks to Simon Garner for the XML patch (sgarner@gameplanet.co.nz) * Thanks to Bob Marriott for the Ghost Recon code (bmarriott@speakeasy.net) * * Inspired by QuakePing by Len Norton * * Copyright 1996,1997,1998,1999,2000,2001,2002,2003,2004 by Steve Jankowski * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #define RECV_BUF 204800 /* OS/2 defines */ #ifdef __OS2__ #define BSD_SELECT #endif #include #include #include #include #include #include #include #include #include #define QUERY_PACKETS #include "qstat.h" #include "packet_manip.h" #include "config.h" #include "xform.h" #ifndef _WIN32 #include #include #include #ifndef VMS #include #endif #include #include #include #include #include #ifndef F_SETFL #include #endif #ifdef __hpux extern int h_errno; #define STATIC static #else #define STATIC #endif #define INVALID_SOCKET -1 #ifndef INADDR_NONE #define INADDR_NONE ~0 #endif #define sockerr() errno #else /* _WIN32 */ #include "utils.h" #endif /* !_WIN32 */ #ifdef __OS2__ #include #include #include #include #include #include #define INVALID_SOCKET -1 #define SOCKET_ERROR -1 #define close(a) soclose(a) #endif /* __OS2__ */ #ifndef FD_SETSIZE #define FD_SETSIZE 64 #endif /* Figure out whether to use poll() or select() */ #ifndef USE_POLL #ifndef USE_SELECT #ifdef sun #define USE_POLL #endif #ifdef linux #define USE_POLL #include #endif #ifdef __linux__ #define USE_POLL #include #endif #ifdef __linux #define USE_POLL #include #endif #ifdef __hpux #define USE_POLL #include #endif #ifdef __OpenBSD__ #define USE_POLL #include #endif #ifdef _AIX #define USE_POLL #include #endif #ifdef _WIN32 #define USE_SELECT #endif #ifdef __EMX__ #define USE_SELECT #endif #endif /* USE_SELECT */ #endif /* USE_POLL */ /* If did not chose, then use select() */ #ifndef USE_POLL #ifndef USE_SELECT #define USE_SELECT #endif #endif #include "debug.h" server_type *types; int n_server_types; char *qstat_version = QSTAT_VERSION; /* * Values set by command-line arguments */ int hostname_lookup = 0; /* set if -H was specified */ int new_style = 1; /* unset if -old was specified */ int n_retries = DEFAULT_RETRIES; int retry_interval = DEFAULT_RETRY_INTERVAL; int master_retry_interval = DEFAULT_RETRY_INTERVAL * 4; int syncconnect = 0; int get_player_info = 0; int get_server_rules = 0; int up_servers_only = 0; int no_full_servers = 0; int no_empty_servers = 0; int no_header_display = 0; int raw_display = 0; char *raw_delimiter = "\t"; char *multi_delimiter = "|"; int player_address = 0; int max_simultaneous = MAXFD_DEFAULT; int sendinterval = 5; extern int xform_names; extern int xform_strip_unprintable; extern int xform_hex_player_names; extern int xform_hex_server_names; extern int xform_strip_carets; extern int xform_html_names; extern int html_mode; int raw_arg = 0; int show_game_in_raw = 0; int progress = 0; int num_servers_total = 0; int num_players_total = 0; int max_players_total = 0; int num_servers_returned = 0; int num_servers_timed_out = 0; int num_servers_down = 0; server_type *default_server_type = NULL; FILE *OF; /* output file */ unsigned int source_ip = INADDR_ANY; unsigned short source_port_low = 0; unsigned short source_port_high = 0; unsigned short source_port = 0; int show_game_port = 0; int no_port_offset = 0; int output_bom = 0; int xml_display = 0; int xml_encoding = ENCODING_LATIN_1; #define SUPPORTED_SERVER_SORT "pgihn" #define SUPPORTED_PLAYER_SORT "PFTNS" #define SUPPORTED_SORT_KEYS "l" SUPPORTED_SERVER_SORT SUPPORTED_PLAYER_SORT char sort_keys[32]; int player_sort = 0; int server_sort = 0; int qpartition(void **array, int i, int j, int (*compare)(void *, void *)); void sort_servers(struct qserver **array, int size); void sort_players(struct qserver *server); int server_compare(struct qserver *one, struct qserver *two); int player_compare(struct player *one, struct player *two); int process_func_ret(struct qserver *server, int ret); int connection_inprogress(); void clear_socketerror(); int show_errors = 0; static int noserverdups = 1; #define DEFAULT_COLOR_NAMES_RAW 0 #define DEFAULT_COLOR_NAMES_DISPLAY 1 int color_names = -1; #define SECONDS 0 #define CLOCK_TIME 1 #define STOPWATCH_TIME 2 #define DEFAULT_TIME_FMT_RAW SECONDS #define DEFAULT_TIME_FMT_DISPLAY CLOCK_TIME int time_format = -1; struct qserver *servers = NULL; struct qserver **last_server = &servers; struct qserver **connmap = NULL; int max_connmap; struct qserver *last_server_bind = NULL; struct qserver *first_server_bind = NULL; int connected = 0; time_t run_timeout = 0; time_t start_time; int waiting_for_masters; #define ADDRESS_HASH_LENGTH 2999 static unsigned num_servers;/* current number of servers in memory */ static struct qserver **server_hash[ADDRESS_HASH_LENGTH]; static unsigned int server_hash_len[ADDRESS_HASH_LENGTH]; static void free_server_hash(); static void xml_display_player_info_info(struct player *player); char *DOWN = "DOWN"; char *SYSERROR = "SYSERROR"; char *TIMEOUT = "TIMEOUT"; char *MASTER = "MASTER"; char *SERVERERROR = "ERROR"; char *HOSTNOTFOUND = "HOSTNOTFOUND"; char *BFRIS_SERVER_NAME = "BFRIS Server"; char *GAMESPY_MASTER_NAME = "Gamespy Master"; int display_prefix = 0; char *current_filename; int current_fileline; int count_bits(int n); static int qserver_get_timeout(struct qserver *server, struct timeval *now); static int wait_for_timeout(unsigned int ms); static void finish_output(); static int decode_stefmaster_packet(struct qserver *server, char *pkt, int pktlen); static int decode_q3master_packet(struct qserver *server, char *ikt, int pktlen); char *ut2003_strdup(const char *string, const char *end, char **next); void free_server(struct qserver *server); void free_player(struct player *player); void free_rule(struct rule *rule); void standard_display_server(struct qserver *server); /* MODIFY HERE * Change these functions to display however you want */ void display_server(struct qserver *server) { if (player_sort) { sort_players(server); } if (raw_display) { raw_display_server(server); } else if (xml_display) { xml_display_server(server); } else if (json_display) { json_display_server(server); } else if (have_server_template()) { template_display_server(server); } else { standard_display_server(server); } free_server(server); } void standard_display_server(struct qserver *server) { char prefix[64]; if (display_prefix) { sprintf(prefix, "%-4s ", server->type->type_prefix); } else { prefix[0] = '\0'; } if ((server->server_name == DOWN) || (server->server_name == SYSERROR)) { if (!up_servers_only) { xform_printf(OF, "%s%-16s %10s\n", prefix, (hostname_lookup) ? server->host_name : server->arg, server->server_name); } return; } if (server->server_name == TIMEOUT) { if (server->flags & FLAG_BROADCAST && server->n_servers) { xform_printf(OF, "%s%-16s %d servers\n", prefix, server->arg, server->n_servers); } else if (!up_servers_only) { xform_printf(OF, "%s%-16s no response\n", prefix, (hostname_lookup) ? server->host_name : server->arg); } return; } if (server->type->master) { display_qwmaster(server); return; } if (no_full_servers && (server->num_players >= server->max_players)) { return; } if (no_empty_servers && (server->num_players == 0)) { return; } if (server->error != NULL) { xform_printf(OF, "%s%-21s ERROR <%s>\n", prefix, (hostname_lookup) ? server->host_name : server->arg, server->error); return; } if (new_style) { char *game = get_qw_game(server); int map_name_width = 8, game_width = 0; switch (server->type->id) { case QW_SERVER: case Q2_SERVER: case Q3_SERVER: game_width = 9; break; case TRIBES2_SERVER: map_name_width = 14; game_width = 8; break; case GHOSTRECON_SERVER: map_name_width = 15; game_width = 15; break; case HL_SERVER: map_name_width = 12; break; default: break; } xform_printf(OF, "%s%-21s %2d/%-2d %2d/%-2d %*s %6d / %1d %*s %s\n", prefix, (hostname_lookup) ? server->host_name : server->arg, server->num_players, server->max_players, server->num_spectators, server->max_spectators, map_name_width, (server->map_name) ? xform_name(server->map_name, server) : "?", server->n_requests ? server->ping_total / server->n_requests : 999, server->n_retries, game_width, game, xform_name(server->server_name, server) ); if (get_server_rules && (NULL != server->type->display_rule_func)) { server->type->display_rule_func(server); } if (get_player_info && (NULL != server->type->display_player_func)) { server->type->display_player_func(server); } } else { char name[512]; sprintf(name, "\"%s\"", server->server_name); xform_printf(OF, "%-16s %10s map %s at %22s %d/%d players %d ms\n", (hostname_lookup) ? server->host_name : server->arg, name, server->map_name, server->address, server->num_players, server->max_players, server->n_requests ? server->ping_total / server->n_requests : 999 ); } } void display_qwmaster(struct qserver *server) { char *prefix; prefix = server->type->type_prefix; if (server->error != NULL) { xform_printf(OF, "%s %-17s ERROR <%s>\n", prefix, (hostname_lookup) ? server->host_name : server->arg, server->error ); } else { xform_printf(OF, "%s %-17s %d servers %6d / %1d\n", prefix, (hostname_lookup) ? server->host_name : server->arg, server->n_servers, server->n_requests ? server->ping_total / server->n_requests : 999, server->n_retries ); } } void display_header() { if (!no_header_display) { xform_printf(OF, "%-16s %8s %8s %15s %s\n", "ADDRESS", "PLAYERS", "MAP", "RESPONSE TIME", "NAME"); } } void display_server_rules(struct qserver *server) { struct rule *rule; int printed = 0; rule = server->rules; for ( ; rule != NULL; rule = rule->next) { if (((server->type->id != Q_SERVER) && (server->type->id != H2_SERVER)) || !is_default_rule(rule)) { xform_printf(OF, "%c%s=%s", (printed) ? ',' : '\t', rule->name, rule->value); printed++; } } if (printed) { fputs("\n", OF); } } void display_q_player_info(struct qserver *server) { char fmt[128]; struct player *player; strcpy(fmt, "\t#%-2d %3d frags %9s "); if (color_names) { strcat(fmt, "%9s:%-9s "); } else { strcat(fmt, "%2s:%-2s "); } if (player_address) { strcat(fmt, "%22s "); } else { strcat(fmt, "%s"); } strcat(fmt, "%s\n"); player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, fmt, player->number, player->frags, play_time(player->connect_time, 1), quake_color(player->shirt_color), quake_color(player->pants_color), (player_address) ? player->address : "", xform_name(player->name, server) ); } } void display_qw_player_info(struct qserver *server) { char fmt[128]; struct player *player; strcpy(fmt, "\t#%-6d %5d frags %6s@%-5s %8s"); if (color_names) { strcat(fmt, "%9s:%-9s "); } else { strcat(fmt, "%2s:%-2s "); } strcat(fmt, "%12s %s\n"); player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, fmt, player->number, player->frags, play_time(player->connect_time, 0), ping_time(player->ping), player->skin ? player->skin : "", quake_color(player->shirt_color), quake_color(player->pants_color), xform_name(player->name, server), xform_name(player->team_name, server) ); } } void display_q2_player_info(struct qserver *server) { struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { if (server->flags & FLAG_PLAYER_TEAMS) { xform_printf(OF, "\t%3d frags team#%d %8s %s\n", player->frags, player->team, ping_time(player->ping), xform_name(player->name, server)); } else { xform_printf(OF, "\t%3d frags %8s %s\n", player->frags, ping_time(player->ping), xform_name(player->name, server)); } } } void display_unreal_player_info(struct qserver *server) { struct player *player; static const char *fmt_team_number = "\t%3d frags team#%-3d %7s %s\n"; static const char *fmt_team_name = "\t%3d frags %8s %7s %s\n"; static const char *fmt_no_team = "\t%3d frags %8s %s\n"; player = server->players; for ( ; player != NULL; player = player->next) { if (server->flags & FLAG_PLAYER_TEAMS) { // we use (player->score) ? player->score : player->frags, // so we get details from halo if (player->team_name) { xform_printf(OF, fmt_team_name, (player->score && NA_INT != player->score) ? player->score : player->frags, player->team_name, ping_time(player->ping), xform_name(player->name, server) ); } else { xform_printf(OF, fmt_team_number, (player->score && NA_INT != player->score) ? player->score : player->frags, player->team, ping_time(player->ping), xform_name(player->name, server) ); } } else { xform_printf(OF, fmt_no_team, player->frags, ping_time(player->ping), xform_name(player->name, server)); } } } void display_shogo_player_info(struct qserver *server) { struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t%3d frags %8s %s\n", player->frags, ping_time(player->ping), xform_name(player->name, server)); } } void display_halflife_player_info(struct qserver *server) { struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t%3d frags %8s %s\n", player->frags, play_time(player->connect_time, 1), xform_name(player->name, server)); } } void display_fl_player_info(struct qserver *server) { struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t%3d frags %8s %8s %s\n", player->frags, ping_time(player->ping), play_time(player->connect_time, 1), xform_name(player->name, server)); } } void display_tribes_player_info(struct qserver *server) { struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t%4d score team#%d %8s %s\n", player->frags, player->team, ping_time(player->ping), xform_name(player->name, server) ); } } void display_tribes2_player_info(struct qserver *server) { struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\tscore %4d %14s %s\n", player->frags, player->team_name ? player->team_name : (player->number == TRIBES_TEAM ? "TEAM" : "?"), xform_name(player->name, server) ); } } void display_bfris_player_info(struct qserver *server) { struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\ttid: %d, ship: %d, team: %s, ping: %d, score: %d, kills: %d, name: %s\n", player->number, player->ship, player->team_name, player->ping, player->score, player->frags, xform_name(player->name, server) ); } } void display_descent3_player_info(struct qserver *server) { struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t%3d frags %3d deaths team#%-3d %7s %s\n", player->frags, player->deaths, player->team, ping_time(player->ping), xform_name(player->name, server) ); } } void display_ghostrecon_player_info(struct qserver *server) { struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\tdead=%3d team#%-3d %s\n", player->deaths, player->team, xform_name(player->name, server)); } } void display_eye_player_info(struct qserver *server) { struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { if (player->team_name) { xform_printf(OF, "\tscore %4d %6s team %12s %s\n", player->score, ping_time(player->ping), player->team_name, xform_name(player->name, server) ); } else { xform_printf(OF, "\tscore %4d %6s team#%d %s\n", player->score, ping_time(player->ping), player->team, xform_name(player->name, server) ); } } } int calculate_armyops_score(struct player *player) { /* Calculates a player's score for ArmyOps from the basic components */ int score = 0; int kill_score = 0; struct info *info; for (info = player->info; info; info = info->next) { if ((0 == strcmp(info->name, "leader")) || (0 == strcmp(info->name, "goal")) || (0 == strcmp(info->name, "roe"))) { score += atoi(info->value); } else if ((0 == strcmp(info->name, "kia")) || (0 == strcmp(info->name, "enemy"))) { kill_score += atoi(info->value); } } if (kill_score > 0) { score += kill_score; } return (score); } void display_gs2_player_info(struct qserver *server) { struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { if (player->team_name) { xform_printf(OF, "\tscore %4d %6s team %12s %s\n", player->score, ping_time(player->ping), player->team_name, xform_name(player->name, server) ); } else { xform_printf(OF, "\tscore %4d %6s team#%d %s\n", player->score, ping_time(player->ping), player->team, xform_name(player->name, server) ); } } } void display_armyops_player_info(struct qserver *server) { struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { player->score = calculate_armyops_score(player); } display_gs2_player_info(server); } void display_ts2_player_info(struct qserver *server) { struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t%6s %s\n", ping_time(player->ping), xform_name(player->name, server)); } } void display_ts3_player_info(struct qserver *server) { struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t%s\n", xform_name(player->name, server)); } } void display_starmade_player_info(struct qserver *server) { struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t%s\n", xform_name(player->name, server)); } } void display_bfbc2_player_info(struct qserver *server) { struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t%s\n", xform_name(player->name, server)); } } void display_wic_player_info(struct qserver *server) { struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t#%-4d score %4d team %12s role %12s %s\n", player->number, player->score, player->team_name, player->tribe_tag ? player->tribe_tag : "", xform_name(player->name, server) ); } } void display_ventrilo_player_info(struct qserver *server) { struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t# %d ping %s time %d cid %i ch %s name %s\n", player->number, ping_time(player->ping), player->connect_time, player->team, player->team_name, xform_name(player->name, server) ); } } void display_tm_player_info(struct qserver *server) { struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t%6s %s\n", ping_time(player->ping), xform_name(player->name, server)); } } void display_doom3_player_info(struct qserver *server) { struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { if (player->tribe_tag) { xform_printf(OF, "\t#%-4d score %4d %6s team %12s %s\n", player->number, player->score, ping_time(player->ping), player->tribe_tag, xform_name(player->name, server) ); } else { xform_printf(OF, "\t#%-4d score %4d %6s team#%d %s\n", player->number, player->score, ping_time(player->ping), player->team, xform_name(player->name, server) ); } } } void display_ravenshield_player_info(struct qserver *server) { struct player *player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t%3d frags %8s %s\n", player->frags, play_time(player->connect_time, 1), xform_name(player->name, server)); } } void display_savage_player_info(struct qserver *server) { struct player *player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t%3d frags %8s %s\n", player->frags, play_time(player->connect_time, 1), xform_name(player->name, server)); } } void display_farcry_player_info(struct qserver *server) { struct player *player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t%3d frags %8s %s\n", player->frags, play_time(player->connect_time, 1), xform_name(player->name, server)); } } void display_tee_player_info(struct qserver *server) { struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t%4d score %s\n", player->score, xform_name(player->name, server)); } } char * get_qw_game(struct qserver *server) { struct rule *rule; char *game_rule = server->type->game_rule; if ((game_rule == NULL) || (*game_rule == '\0')) { return (""); } rule = server->rules; for ( ; rule != NULL; rule = rule->next) { if (strcmp(rule->name, game_rule) == 0) { if ((server->type->id == Q3_SERVER) && (strcmp(rule->value, "baseq3") == 0)) { return (""); } return (rule->value); } } rule = server->rules; for ( ; rule != NULL; rule = rule->next) { if ((0 == strcmp(rule->name, "game")) || (0 == strcmp(rule->name, "fs_game"))) { return (rule->value); } } return (""); } /* Raw output for web master types */ #define RD raw_delimiter void raw_display_server(struct qserver *server) { char *prefix; int ping_time; prefix = server->type->type_prefix; if (server->n_requests) { ping_time = server->ping_total / server->n_requests; } else { ping_time = 999; } if ((server->server_name == DOWN) || (server->server_name == SYSERROR)) { if (!up_servers_only) { xform_printf(OF, "%s" "%.*s%.*s" "%s%s" "%s%s\n\n", prefix, raw_arg, RD, raw_arg, server->arg, RD, (hostname_lookup) ? server->host_name : server->arg, RD, server->server_name ); } return; } if (server->server_name == TIMEOUT) { if (server->flags & FLAG_BROADCAST && server->n_servers) { xform_printf(OF, "%s" "%.*s%.*s" "%s%s" "%s%d\n", prefix, raw_arg, RD, raw_arg, server->arg, RD, server->arg, RD, server->n_servers); } else if (!up_servers_only) { xform_printf(OF, "%s" "%.*s%.*s" "%s%s" "%s%s\n\n", prefix, raw_arg, RD, raw_arg, server->arg, RD, (hostname_lookup) ? server->host_name : server->arg, RD, TIMEOUT); } return; } if (server->error != NULL) { xform_printf(OF, "%s" "%.*s%.*s" "%s%s" "%s%s" "%s%s", prefix, raw_arg, RD, raw_arg, server->arg, RD, (hostname_lookup) ? server->host_name : server->arg, RD, "ERROR", RD, server->error ); } else if (server->type->flags & TF_RAW_STYLE_QUAKE) { xform_printf(OF, "%s" "%.*s%.*s" "%s%s" "%s%s" "%s%s" "%s%d" "%s%s" "%s%d" "%s%d" "%s%d" "%s%d" "%s%d" "%s%d" "%s%s", prefix, raw_arg, RD, raw_arg, server->arg, RD, (hostname_lookup) ? server->host_name : server->arg, RD, xform_name(server->server_name, server), RD, server->address, RD, server->protocol_version, RD, server->map_name, RD, server->max_players, RD, server->num_players, RD, server->max_spectators, RD, server->num_spectators, RD, ping_time, RD, server->n_retries, show_game_in_raw ? RD : "", show_game_in_raw ? get_qw_game(server) : "" ); } else if (server->type->flags & TF_RAW_STYLE_TRIBES) { xform_printf(OF, "%s" "%.*s%.*s" "%s%s" "%s%s" "%s%s" "%s%d" "%s%d", prefix, raw_arg, RD, raw_arg, server->arg, RD, (hostname_lookup) ? server->host_name : server->arg, RD, xform_name(server->server_name, server), RD, (server->map_name) ? server->map_name : "?", RD, server->num_players, RD, server->max_players ); } else if (server->type->flags & TF_RAW_STYLE_GHOSTRECON) { xform_printf(OF, "%s" "%.*s%.*s" "%s%s" "%s%s" "%s%s" "%s%d" "%s%d", prefix, raw_arg, RD, raw_arg, server->arg, RD, (hostname_lookup) ? server->host_name : server->arg, RD, xform_name(server->server_name, server), RD, (server->map_name) ? server->map_name : "?", RD, server->num_players, RD, server->max_players ); } else if (server->type->master) { xform_printf(OF, "%s" "%.*s%.*s" "%s%s" "%s%d", prefix, raw_arg, RD, raw_arg, server->arg, RD, (hostname_lookup) ? server->host_name : server->arg, RD, server->n_servers ); } else { xform_printf(OF, "%s" "%.*s%.*s" "%s%s" "%s%s" "%s%s" "%s%d" "%s%d" "%s%d" "%s%d" "%s%s", prefix, raw_arg, RD, raw_arg, server->arg, RD, (hostname_lookup) ? server->host_name : server->arg, RD, xform_name(server->server_name, server), RD, (server->map_name) ? xform_name(server->map_name, server) : "?", RD, server->max_players, RD, server->num_players, RD, ping_time, RD, server->n_retries, show_game_in_raw ? RD : "", show_game_in_raw ? get_qw_game(server) : "" ); } fputs("\n", OF); if (server->type->master || (server->error != NULL)) { fputs("\n", OF); return; } if (get_server_rules && (NULL != server->type->display_raw_rule_func)) { server->type->display_raw_rule_func(server); } if (get_player_info && (NULL != server->type->display_raw_player_func)) { server->type->display_raw_player_func(server); } fputs("\n", OF); } void raw_display_server_rules(struct qserver *server) { struct rule *rule; int printed = 0; rule = server->rules; for ( ; rule != NULL; rule = rule->next) { if (server->type->id == TRIBES2_SERVER) { char *v; for (v = rule->value; *v; v++) { if (*v == '\n') { *v = ' '; } } } xform_printf(OF, "%s%s=%s", (printed) ? RD : "", rule->name, rule->value); printed++; } if (server->missing_rules) { xform_printf(OF, "%s?", (printed) ? RD : ""); } fputs("\n", OF); } void raw_display_q_player_info(struct qserver *server) { char fmt[] = "%d" "%s%s" "%s%s" "%s%d" "%s%s" "%s%s" "%s%s"; struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, fmt, player->number, RD, xform_name(player->name, server), RD, player->address, RD, player->frags, RD, play_time(player->connect_time, 1), RD, quake_color(player->shirt_color), RD, quake_color(player->pants_color) ); fputs("\n", OF); } } void raw_display_qw_player_info(struct qserver *server) { char fmt[128]; struct player *player; strcpy(fmt, "%d" "%s%s" "%s%d" "%s%s" "%s%s" "%s%s"); strcat(fmt, "%s%d" "%s%s" "%s%s"); player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, fmt, player->number, RD, xform_name(player->name, server), RD, player->frags, RD, play_time(player->connect_time, 1), RD, quake_color(player->shirt_color), RD, quake_color(player->pants_color), RD, player->ping, RD, player->skin ? player->skin : "", RD, player->team_name ? player->team_name : "" ); fputs("\n", OF); } } void raw_display_q2_player_info(struct qserver *server) { static const char *fmt = "%s" "%s%d" "%s%d"; static const char *fmt_team = "%s" "%s%d" "%s%d" "%s%d"; struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { if (server->flags & FLAG_PLAYER_TEAMS) { xform_printf(OF, fmt_team, xform_name(player->name, server), RD, player->frags, RD, player->ping, RD, player->team); } else { xform_printf(OF, fmt, xform_name(player->name, server), RD, player->frags, RD, player->ping); } fputs("\n", OF); } } void raw_display_unreal_player_info(struct qserver *server) { static const char *fmt = "%s" "%s%d" "%s%d" "%s%d" "%s%s" "%s%s" "%s%s"; static const char *fmt_team_name = "%s" "%s%d" "%s%d" "%s%s" "%s%s" "%s%s" "%s%s"; struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { if (player->team_name) { xform_printf(OF, fmt_team_name, xform_name(player->name, server), RD, player->frags, RD, player->ping, RD, player->team_name, RD, player->skin ? player->skin : "", RD, player->mesh ? player->mesh : "", RD, player->face ? player->face : "" ); } else { xform_printf(OF, fmt, xform_name(player->name, server), RD, player->frags, RD, player->ping, RD, player->team, RD, player->skin ? player->skin : "", RD, player->mesh ? player->mesh : "", RD, player->face ? player->face : "" ); } fputs("\n", OF); } } void raw_display_halflife_player_info(struct qserver *server) { static char fmt[24] = "%s" "%s%d" "%s%s"; struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, fmt, xform_name(player->name, server), RD, player->frags, RD, play_time(player->connect_time, 1)); fputs("\n", OF); } } void raw_display_fl_player_info(struct qserver *server) { static char fmt[24] = "%s" "%s%d" "%s%s" "%s%d" "%s%d"; struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { fprintf( OF, fmt, xform_name(player->name, server), RD, player->frags, RD, play_time(player->connect_time, 1), RD, player->ping, RD, player->team ); fputs("\n", OF); } } void raw_display_tribes_player_info(struct qserver *server) { static char fmt[24] = "%s" "%s%d" "%s%d" "%s%d" "%s%d"; struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, fmt, xform_name(player->name, server), RD, player->frags, RD, player->ping, RD, player->team, RD, player->packet_loss); fputs("\n", OF); } } void raw_display_tribes2_player_info(struct qserver *server) { static char fmt[] = "%s" "%s%d" "%s%d" "%s%s" "%s%s" "%s%s"; struct player *player; char *type; player = server->players; for ( ; player != NULL; player = player->next) { switch (player->type_flag) { case PLAYER_TYPE_BOT: type = "Bot"; break; case PLAYER_TYPE_ALIAS: type = "Alias"; break; default: type = ""; break; } xform_printf(OF, fmt, xform_name(player->name, server), RD, player->frags, RD, player->team, RD, player->team_name ? player->team_name : "TEAM", RD, type, RD, player->tribe_tag ? xform_name(player->tribe_tag, server) : "" ); fputs("\n", OF); } } void raw_display_bfris_player_info(struct qserver *server) { static char fmt[] = "%d" "%s%d" "%s%s" "%s%d" "%s%d" "%s%d" "%s%s"; struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, fmt, player->number, RD, player->ship, RD, player->team_name, RD, player->ping, RD, player->score, RD, player->frags, RD, xform_name(player->name, server) ); fputs("\n", OF); } } void raw_display_descent3_player_info(struct qserver *server) { static char fmt[] = "%s" "%s%d" "%s%d" "%s%d" "%s%d"; struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, fmt, xform_name(player->name, server), RD, player->frags, RD, player->deaths, RD, player->ping, RD, player->team); fputs("\n", OF); } } void raw_display_ghostrecon_player_info(struct qserver *server) { static char fmt[28] = "%s" "%s%d" "%s%d"; struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, fmt, xform_name(player->name, server), RD, player->deaths, RD, player->team); fputs("\n", OF); } } void raw_display_eye_player_info(struct qserver *server) { static const char *fmt = "%s" "%s%d" "%s%d" "%s%d" "%s%s" "%s%s"; static const char *fmt_team_name = "%s" "%s%d" "%s%d" "%s%s" "%s%s" "%s%s"; struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { if (player->team_name) { xform_printf(OF, fmt_team_name, xform_name(player->name, server), RD, player->score, RD, player->ping, RD, player->team_name, RD, player->skin ? player->skin : "", RD, play_time(player->connect_time, 1) ); } else { xform_printf(OF, fmt, xform_name(player->name, server), RD, player->score, RD, player->ping, RD, player->team, RD, player->skin ? player->skin : "", RD, play_time(player->connect_time, 1) ); } fputs("\n", OF); } } void raw_display_doom3_player_info(struct qserver *server) { static const char *fmt = "%s" "%s%d" "%s%d" "%s%d" "%s%u"; static const char *fmt_team_name = "%s" "%s%d" "%s%d" "%s%s" "%s%u"; struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { if (player->tribe_tag) { xform_printf(OF, fmt_team_name, xform_name(player->name, server), RD, player->score, RD, player->ping, RD, player->tribe_tag, RD, player->number); } else { xform_printf(OF, fmt, xform_name(player->name, server), RD, player->score, RD, player->ping, RD, player->team, RD, player->number); } fputs("\n", OF); } } void raw_display_gs2_player_info(struct qserver *server) { static const char *fmt = "%s" "%s%d" "%s%d" "%s%d" "%s%s" "%s%s"; static const char *fmt_team_name = "%s" "%s%d" "%s%d" "%s%s" "%s%s" "%s%s"; struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { if (player->team_name) { xform_printf(OF, fmt_team_name, xform_name(player->name, server), RD, player->score, RD, player->ping, RD, player->team_name, RD, player->skin ? player->skin : "", RD, play_time(player->connect_time, 1) ); } else { xform_printf(OF, fmt, xform_name(player->name, server), RD, player->score, RD, player->ping, RD, player->team, RD, player->skin ? player->skin : "", RD, play_time(player->connect_time, 1) ); } fputs("\n", OF); } } void raw_display_armyops_player_info(struct qserver *server) { struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { player->score = calculate_armyops_score(player); } raw_display_gs2_player_info(server); } void raw_display_ts2_player_info(struct qserver *server) { static const char *fmt = "%s" "%s%d" "%s%s" "%s%s"; struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, fmt, xform_name(player->name, server), RD, player->ping, RD, player->skin ? player->skin : "", RD, play_time(player->connect_time, 1) ); fputs("\n", OF); } } void raw_display_ts3_player_info(struct qserver *server) { static const char *fmt = "%s" "%s%s" "%s%s"; struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, fmt, xform_name(player->name, server), RD, player->skin ? player->skin : "", RD, play_time(player->connect_time, 1) ); fputs("\n", OF); } } void raw_display_starmade_player_info(struct qserver *server) { static const char *fmt = "%s" "%s%s" "%s%s"; struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, fmt, xform_name(player->name, server), RD, player->skin ? player->skin : "", RD, play_time(player->connect_time, 1) ); fputs("\n", OF); } } void raw_display_bfbc2_player_info(struct qserver *server) { static const char *fmt = "%s" "%s%s" "%s%s"; struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, fmt, xform_name(player->name, server), RD, player->skin ? player->skin : "", RD, play_time(player->connect_time, 1) ); fputs("\n", OF); } } void raw_display_wic_player_info(struct qserver *server) { static const char *fmt = "%s" "%s%d" "%s%s" "%s%s"; struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, fmt, xform_name(player->name, server), RD, player->score, RD, player->team_name, RD, player->tribe_tag ? player->tribe_tag : "" ); fputs("\n", OF); } } void raw_display_ventrilo_player_info(struct qserver *server) { static const char *fmt = "%s" "%s%d" "%s%s" "%s%s"; struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { fprintf( OF, fmt, xform_name(player->name, server), RD, player->team, RD, player->team_name, RD, play_time(player->connect_time, 1) ); fputs("\n", OF); } } void raw_display_tm_player_info(struct qserver *server) { static const char *fmt = "%s" "%s%d" "%s%s" "%s%s"; struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, fmt, xform_name(player->name, server), RD, player->ping, RD, player->skin ? player->skin : "", RD, play_time(player->connect_time, 1) ); fputs("\n", OF); } } void raw_display_tee_player_info(struct qserver *server) { static const char *fmt = "%s"; struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, fmt, xform_name(player->name, server)); fputs("\n", OF); } } void raw_display_ravenshield_player_info(struct qserver *server) { static char fmt[24] = "%s" "%s%d" "%s%s"; struct player *player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, fmt, xform_name(player->name, server), RD, player->frags, RD, play_time(player->connect_time, 1)); fputs("\n", OF); } } void raw_display_savage_player_info(struct qserver *server) { static char fmt[24] = "%s" "%s%d" "%s%s"; struct player *player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, fmt, xform_name(player->name, server), RD, player->frags, RD, play_time(player->connect_time, 1)); fputs("\n", OF); } } void raw_display_farcry_player_info(struct qserver *server) { static char fmt[24] = "%s" "%s%d" "%s%s"; struct player *player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, fmt, xform_name(player->name, server), RD, player->frags, RD, play_time(player->connect_time, 1)); fputs("\n", OF); } } /* XML output * Contributed by :-) */ void xml_display_server(struct qserver *server) { char *prefix; prefix = server->type->type_prefix; if (server->server_name == DOWN) { if (!up_servers_only) { xform_printf(OF, "\t\n", xml_escape(prefix), xml_escape(server->arg), xml_escape(DOWN)); xform_printf(OF, "\t\t%s\n", xml_escape((hostname_lookup) ? server->host_name : server->arg)); xform_printf(OF, "\t\n"); } return; } if (server->server_name == TIMEOUT) { if (server->flags & FLAG_BROADCAST && server->n_servers) { xform_printf(OF, "\t\n", xml_escape(prefix), xml_escape(server->arg), xml_escape(TIMEOUT), server->n_servers ); xform_printf(OF, "\t\n"); } else if (!up_servers_only) { xform_printf(OF, "\t\n", xml_escape(prefix), xml_escape(server->arg), xml_escape(TIMEOUT)); xform_printf(OF, "\t\t%s\n", xml_escape((hostname_lookup) ? server->host_name : server->arg)); xform_printf(OF, "\t\n"); } return; } if (server->error != NULL) { xform_printf(OF, "\t\n", xml_escape(prefix), xml_escape(server->arg), "ERROR"); xform_printf(OF, "\t\t%s\n", xml_escape((hostname_lookup) ? server->host_name : server->arg)); xform_printf(OF, "\t\t%s\n", xml_escape(server->error)); } else if (server->type->master) { xform_printf(OF, "\t\n", xml_escape(prefix), xml_escape(server->arg), "UP", server->n_servers); } else { xform_printf(OF, "\t\n", xml_escape(prefix), xml_escape(server->arg), "UP"); xform_printf(OF, "\t\t%s\n", xml_escape((hostname_lookup) ? server->host_name : server->arg)); xform_printf(OF, "\t\t%s\n", xml_escape(xform_name(server->server_name, server))); xform_printf(OF, "\t\t%s\n", xml_escape(get_qw_game(server))); xform_printf(OF, "\t\t%s\n", xml_escape(xform_name(server->map_name, server))); xform_printf(OF, "\t\t%d\n", server->num_players); xform_printf(OF, "\t\t%d\n", server->max_players); xform_printf(OF, "\t\t%d\n", server->num_spectators); xform_printf(OF, "\t\t%d\n", server->max_spectators); if (!(server->type->flags & TF_RAW_STYLE_TRIBES)) { xform_printf(OF, "\t\t%d\n", server->n_requests ? server->ping_total / server->n_requests : 999); xform_printf(OF, "\t\t%d\n", server->n_retries); } if (server->type->flags & TF_RAW_STYLE_QUAKE) { xform_printf(OF, "\t\t
%s
\n", xml_escape(server->address)); xform_printf(OF, "\t\t%d\n", server->protocol_version); } } if (!server->type->master && (server->error == NULL)) { if (get_server_rules && (NULL != server->type->display_xml_rule_func)) { server->type->display_xml_rule_func(server); } if (get_player_info && (NULL != server->type->display_xml_player_func)) { server->type->display_xml_player_func(server); } } xform_printf(OF, "\t
\n"); } void xml_header() { if (xml_encoding == ENCODING_LATIN_1) { xform_printf(OF, "\n\n"); } else if (output_bom) { xform_printf(OF, "%c%c%c\n\n", 0xEF, 0xBB, 0xBF); } else { xform_printf(OF, "\n\n"); } } void xml_footer() { xform_printf(OF, "\n"); } void xml_display_server_rules(struct qserver *server) { struct rule *rule; rule = server->rules; xform_printf(OF, "\t\t\n"); for ( ; rule != NULL; rule = rule->next) { xform_printf(OF, "\t\t\t%s\n", xml_escape(rule->name), xml_escape(rule->value)); } xform_printf(OF, "\t\t\n"); } void xml_display_q_player_info(struct qserver *server) { struct player *player; xform_printf(OF, "\t\t\n"); player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t\t\t\n", player->number); xform_printf(OF, "\t\t\t\t%s\n", xml_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t
%s
\n", xml_escape(player->address)); xform_printf(OF, "\t\t\t\t%d\n", player->frags); xform_printf(OF, "\t\t\t\t\n", xml_escape(play_time(player->connect_time, 2))); if (color_names) { xform_printf(OF, "\t\t\t\t%s\n", xml_escape(quake_color(player->shirt_color))); xform_printf(OF, "\t\t\t\t%s\n", xml_escape(quake_color(player->pants_color))); } else { xform_printf(OF, "\t\t\t\t%s\n", quake_color(player->shirt_color)); xform_printf(OF, "\t\t\t\t%s\n", quake_color(player->pants_color)); } xform_printf(OF, "\t\t\t
\n"); } xform_printf(OF, "\t\t
\n"); } void xml_display_qw_player_info(struct qserver *server) { struct player *player; xform_printf(OF, "\t\t\n"); player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t\t\t\n", player->number); xform_printf(OF, "\t\t\t\t%s\n", xml_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t%d\n", player->frags); xform_printf(OF, "\t\t\t\t\n", xml_escape(play_time(player->connect_time, 2))); if (color_names) { xform_printf(OF, "\t\t\t\t%s\n", xml_escape(quake_color(player->shirt_color))); xform_printf(OF, "\t\t\t\t%s\n", xml_escape(quake_color(player->pants_color))); } else { xform_printf(OF, "\t\t\t\t%s\n", quake_color(player->shirt_color)); xform_printf(OF, "\t\t\t\t%s\n", quake_color(player->pants_color)); } xform_printf(OF, "\t\t\t\t%d\n", player->ping); xform_printf(OF, "\t\t\t\t%s\n", player->skin ? xml_escape(player->skin) : ""); xform_printf(OF, "\t\t\t\t%s\n", player->team_name ? xml_escape(player->team_name) : ""); xform_printf(OF, "\t\t\t\n"); } xform_printf(OF, "\t\t\n"); } void xml_display_q2_player_info(struct qserver *server) { struct player *player; xform_printf(OF, "\t\t\n"); player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t\t\t\n"); xform_printf(OF, "\t\t\t\t%s\n", xml_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t%d\n", player->frags); if (server->flags & FLAG_PLAYER_TEAMS) { xform_printf(OF, "\t\t\t\t%d\n", player->team); } xform_printf(OF, "\t\t\t\t%d\n", player->ping); xform_printf(OF, "\t\t\t\n"); } xform_printf(OF, "\t\t\n"); } void xml_display_player_info_info(struct player *player) { struct info *info; for (info = player->info; info; info = info->next) { if (info->name) { char *name = xml_escape(info->name); char *value = xml_escape(info->value); xform_printf(OF, "\t\t\t\t<%s>%s\n", name, value, name); } } } void xml_display_unreal_player_info(struct qserver *server) { struct player *player; xform_printf(OF, "\t\t\n"); player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t\t\t\n"); xform_printf(OF, "\t\t\t\t%s\n", xml_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t%d\n", player->frags); if (-999 != player->deaths) { xform_printf(OF, "\t\t\t\t%d\n", player->deaths); } xform_printf(OF, "\t\t\t\t%d\n", player->ping); if (player->team_name) { xform_printf(OF, "\t\t\t\t%s\n", xml_escape(player->team_name)); } else if (-1 != player->team) { xform_printf(OF, "\t\t\t\t%d\n", player->team); } // Some games dont provide // so only display if they do if (player->skin) { xform_printf(OF, "\t\t\t\t%s\n", player->skin ? xml_escape(player->skin) : ""); } if (player->mesh) { xform_printf(OF, "\t\t\t\t%s\n", player->mesh ? xml_escape(player->mesh) : ""); } if (player->face) { xform_printf(OF, "\t\t\t\t%s\n", player->face ? xml_escape(player->face) : ""); } xml_display_player_info_info(player); xform_printf(OF, "\t\t\t\n"); } xform_printf(OF, "\t\t\n"); } void xml_display_halflife_player_info(struct qserver *server) { struct player *player; xform_printf(OF, "\t\t\n"); player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t\t\t\n"); xform_printf(OF, "\t\t\t\t%s\n", xml_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t%d\n", player->frags); xform_printf(OF, "\t\t\t\t\n", xml_escape(play_time(player->connect_time, 2))); xform_printf(OF, "\t\t\t\n"); } xform_printf(OF, "\t\t\n"); } void xml_display_fl_player_info(struct qserver *server) { struct player *player; xform_printf(OF, "\t\t\n"); player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t\t\t\n"); xform_printf(OF, "\t\t\t\t%s\n", xml_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t%d\n", player->frags); xform_printf(OF, "\t\t\t\t%d\n", player->ping); xform_printf(OF, "\t\t\t\t%d\n", player->team); xform_printf(OF, "\t\t\t\t\n", xml_escape(play_time(player->connect_time, 2))); xform_printf(OF, "\t\t\t\n"); } xform_printf(OF, "\t\t\n"); } void xml_display_tribes_player_info(struct qserver *server) { struct player *player; xform_printf(OF, "\t\t\n"); player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t\t\t\n"); xform_printf(OF, "\t\t\t\t%s\n", xml_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t%d\n", player->frags); xform_printf(OF, "\t\t\t\t%d\n", player->team); xform_printf(OF, "\t\t\t\t%d\n", player->ping); xform_printf(OF, "\t\t\t\t%d\n", player->packet_loss); xform_printf(OF, "\t\t\t\n"); } xform_printf(OF, "\t\t\n"); } void xml_display_tribes2_player_info(struct qserver *server) { struct player *player; char *type; xform_printf(OF, "\t\t\n"); player = server->players; for ( ; player != NULL; player = player->next) { if (player->team_name) { switch (player->type_flag) { case PLAYER_TYPE_BOT: type = "Bot"; break; case PLAYER_TYPE_ALIAS: type = "Alias"; break; default: type = ""; break; } xform_printf(OF, "\t\t\t\n"); xform_printf(OF, "\t\t\t\t%s\n", xml_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t%d\n", player->frags); xform_printf(OF, "\t\t\t\t%s\n", player->team, xml_escape(player->team_name)); xform_printf(OF, "\t\t\t\t%s\n", xml_escape(type)); xform_printf(OF, "\t\t\t\t%s\n", player->tribe_tag ? xml_escape(xform_name(player->tribe_tag, server)) : ""); xform_printf(OF, "\t\t\t\n"); } } xform_printf(OF, "\t\t\n"); } void xml_display_bfris_player_info(struct qserver *server) { struct player *player; xform_printf(OF, "\t\t\n"); player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t\t\t\n", player->number); xform_printf(OF, "\t\t\t\t%s\n", xml_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t%d\n", player->score); xform_printf(OF, "\t\t\t\t%d\n", player->frags); xform_printf(OF, "\t\t\t\t%s\n", xml_escape(player->team_name)); xform_printf(OF, "\t\t\t\t%d\n", player->ping); xform_printf(OF, "\t\t\t\t%d\n", player->ship); xform_printf(OF, "\t\t\t\n"); } xform_printf(OF, "\t\t\n"); } void xml_display_descent3_player_info(struct qserver *server) { struct player *player; xform_printf(OF, "\t\t\n"); player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t\t\t\n"); xform_printf(OF, "\t\t\t\t%s\n", xml_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t%d\n", player->frags); xform_printf(OF, "\t\t\t\t%d\n", player->deaths); xform_printf(OF, "\t\t\t\t%d\n", player->ping); xform_printf(OF, "\t\t\t\t%d\n", player->team); xform_printf(OF, "\t\t\t\n"); } xform_printf(OF, "\t\t\n"); } void xml_display_ravenshield_player_info(struct qserver *server) { struct player *player; xform_printf(OF, "\t\t\n"); player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t\t\t\n"); xform_printf(OF, "\t\t\t\t%s\n", xml_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t%d\n", player->frags); xform_printf(OF, "\t\t\t\t\n", xml_escape(play_time(player->connect_time, 2))); xform_printf(OF, "\t\t\t\n"); } xform_printf(OF, "\t\t\n"); } void xml_display_ghostrecon_player_info(struct qserver *server) { struct player *player; xform_printf(OF, "\t\t\n"); player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t\t\t\n"); xform_printf(OF, "\t\t\t\t%s\n", xml_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t%d\n", player->deaths); xform_printf(OF, "\t\t\t\t%d\n", player->team); xform_printf(OF, "\t\t\t\n"); } xform_printf(OF, "\t\t\n"); } void xml_display_eye_player_info(struct qserver *server) { struct player *player; xform_printf(OF, "\t\t\n"); player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t\t\t\n"); xform_printf(OF, "\t\t\t\t%s\n", xml_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t%d\n", player->score); xform_printf(OF, "\t\t\t\t%d\n", player->ping); if (player->team_name) { xform_printf(OF, "\t\t\t\t%s\n", xml_escape(player->team_name)); } else { xform_printf(OF, "\t\t\t\t%d\n", player->team); } if (player->skin) { xform_printf(OF, "\t\t\t\t%s\n", xml_escape(player->skin)); } if (player->connect_time) { xform_printf(OF, "\t\t\t\t\n", xml_escape(play_time(player->connect_time, 1))); } xform_printf(OF, "\t\t\t\n"); } xform_printf(OF, "\t\t\n"); } void xml_display_doom3_player_info(struct qserver *server) { struct player *player; xform_printf(OF, "\t\t\n"); player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t\t\t\n"); xform_printf(OF, "\t\t\t\t%u\n", player->number); xform_printf(OF, "\t\t\t\t%s\n", xml_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t%d\n", player->score); xform_printf(OF, "\t\t\t\t%d\n", player->ping); if (player->tribe_tag) { xform_printf(OF, "\t\t\t\t%s\n", player->tribe_tag ? xml_escape(xform_name(player->tribe_tag, server)) : ""); } else { xform_printf(OF, "\t\t\t\t%d\n", player->team); } if (player->skin) { xform_printf(OF, "\t\t\t\t%s\n", xml_escape(player->skin)); } if (player->type_flag) { xform_printf(OF, "\t\t\t\tbot\n"); } else { xform_printf(OF, "\t\t\t\tplayer\n"); } if (player->connect_time) { xform_printf(OF, "\t\t\t\t\n", xml_escape(play_time(player->connect_time, 2))); } xml_display_player_info_info(player); xform_printf(OF, "\t\t\t\n"); } xform_printf(OF, "\t\t\n"); } void xml_display_player_info(struct qserver *server) { struct player *player; xform_printf(OF, "\t\t\n"); player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t\t\t\n"); xform_printf(OF, "\t\t\t\t%s\n", xml_escape(xform_name(player->name, server))); if (NA_INT != player->ping) { xform_printf(OF, "\t\t\t\t%d\n", player->ping); } if (NA_INT != player->score) { xform_printf(OF, "\t\t\t\t%d\n", player->score); } if (NA_INT != player->deaths) { xform_printf(OF, "\t\t\t\t%d\n", player->deaths); } if (NA_INT != player->frags) { xform_printf(OF, "\t\t\t\t%d\n", player->frags); } if (player->team_name) { xform_printf(OF, "\t\t\t\t%s\n", xml_escape(player->team_name)); } else if (NA_INT != player->team) { xform_printf(OF, "\t\t\t\t%d\n", player->team); } if (player->skin) { xform_printf(OF, "\t\t\t\t%s\n", xml_escape(player->skin)); } if (player->connect_time) { xform_printf(OF, "\t\t\t\t\n", xml_escape(play_time(player->connect_time, 1))); } if (player->address) { xform_printf(OF, "\t\t\t\t
%s
\n", xml_escape(player->address)); } xml_display_player_info_info(player); xform_printf(OF, "\t\t\t
\n"); } xform_printf(OF, "\t\t
\n"); } void xml_display_armyops_player_info(struct qserver *server) { struct player *player; player = server->players; for ( ; player != NULL; player = player->next) { player->score = calculate_armyops_score(player); } xml_display_player_info(server); } void xml_display_ts2_player_info(struct qserver *server) { struct player *player; xform_printf(OF, "\t\t\n"); player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t\t\t\n"); xform_printf(OF, "\t\t\t\t%s\n", xml_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t%d\n", player->ping); if (player->connect_time) { xform_printf(OF, "\t\t\t\t\n", xml_escape(play_time(player->connect_time, 2))); } xml_display_player_info_info(player); xform_printf(OF, "\t\t\t\n"); } xform_printf(OF, "\t\t\n"); } void xml_display_ts3_player_info(struct qserver *server) { struct player *player; xform_printf(OF, "\t\t\n"); player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t\t\t\n"); xform_printf(OF, "\t\t\t\t%s\n", xml_escape(xform_name(player->name, server))); if (player->connect_time) { xform_printf(OF, "\t\t\t\t\n", xml_escape(play_time(player->connect_time, 2))); } xml_display_player_info_info(player); xform_printf(OF, "\t\t\t\n"); } xform_printf(OF, "\t\t\n"); } void xml_display_starmade_player_info(struct qserver *server) { struct player *player; xform_printf(OF, "\t\t\n"); player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t\t\t\n"); xform_printf(OF, "\t\t\t\t%s\n", xml_escape(xform_name(player->name, server))); if (player->connect_time) { xform_printf(OF, "\t\t\t\t\n", xml_escape(play_time(player->connect_time, 2))); } xml_display_player_info_info(player); xform_printf(OF, "\t\t\t\n"); } xform_printf(OF, "\t\t\n"); } void xml_display_bfbc2_player_info(struct qserver *server) { struct player *player; xform_printf(OF, "\t\t\n"); player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t\t\t\n"); xform_printf(OF, "\t\t\t\t%s\n", xml_escape(xform_name(player->name, server))); if (player->connect_time) { xform_printf(OF, "\t\t\t\t\n", xml_escape(play_time(player->connect_time, 2))); } xml_display_player_info_info(player); xform_printf(OF, "\t\t\t\n"); } xform_printf(OF, "\t\t\n"); } void xml_display_wic_player_info(struct qserver *server) { struct player *player; xform_printf(OF, "\t\t\n"); player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t\t\t\n"); xform_printf(OF, "\t\t\t\t%s\n", xml_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t%d\n", player->score); xform_printf(OF, "\t\t\t\t%s\n", player->team_name); xform_printf(OF, "\t\t\t\t%d\n", player->type_flag); if (player->tribe_tag) { xform_printf(OF, "\t\t\t\t%s\n", player->tribe_tag); } xml_display_player_info_info(player); xform_printf(OF, "\t\t\t\n"); } xform_printf(OF, "\t\t\n"); } void xml_display_ventrilo_player_info(struct qserver *server) { struct player *player; xform_printf(OF, "\t\t\n"); player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t\t\t\n"); xform_printf(OF, "\t\t\t\t%s\n", xml_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t%d\n", player->ping); xform_printf(OF, "\t\t\t\t%s\n", xml_escape(player->team_name)); xform_printf(OF, "\t\t\t\t\n", xml_escape(play_time(player->connect_time, 2))); xform_printf(OF, "\t\t\t\n"); } xform_printf(OF, "\t\t\n"); } void xml_display_tm_player_info(struct qserver *server) { struct player *player; xform_printf(OF, "\t\t\n"); player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t\t\t\n"); xform_printf(OF, "\t\t\t\t%s\n", xml_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t%d\n", player->ping); if (player->connect_time) { xform_printf(OF, "\t\t\t\t\n", xml_escape(play_time(player->connect_time, 2))); } xml_display_player_info_info(player); xform_printf(OF, "\t\t\t\n"); } xform_printf(OF, "\t\t\n"); } void xml_display_savage_player_info(struct qserver *server) { struct player *player; xform_printf(OF, "\t\t\n"); player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t\t\t\n"); xform_printf(OF, "\t\t\t\t%s\n", xml_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t%d\n", player->frags); xform_printf(OF, "\t\t\t\t\n", xml_escape(play_time(player->connect_time, 2))); xform_printf(OF, "\t\t\t\n"); } xform_printf(OF, "\t\t\n"); } void xml_display_farcry_player_info(struct qserver *server) { struct player *player; xform_printf(OF, "\t\t\n"); player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t\t\t\n"); xform_printf(OF, "\t\t\t\t%s\n", xml_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t%d\n", player->frags); xform_printf(OF, "\t\t\t\t\n", xml_escape(play_time(player->connect_time, 2))); xform_printf(OF, "\t\t\t\n"); } xform_printf(OF, "\t\t\n"); } void xml_display_tee_player_info(struct qserver *server) { struct player *player; xform_printf(OF, "\t\t\n"); player = server->players; for ( ; player != NULL; player = player->next) { xform_printf(OF, "\t\t\t\n"); xform_printf(OF, "\t\t\t\t%s\n", xml_escape(xform_name(player->name, server))); xform_printf(OF, "\t\t\t\t%d\n", player->score); xform_printf(OF, "\t\t\t\n"); } xform_printf(OF, "\t\t\n"); } void display_progress() { static struct timeval rate_start = { 0, 0 }; char rate[32]; struct timeval now; gettimeofday(&now, NULL); if (!rate_start.tv_sec) { rate_start = now; rate[0] = '\0'; } else { int delta = time_delta(&now, &rate_start); if (delta > 1500) { sprintf(rate, " %d servers/sec ", (num_servers_returned + num_servers_timed_out) * 1000 / delta); } else { rate[0] = '\0'; } } // only print out every 'progress' number of servers. if ((0 != num_servers_returned + num_servers_timed_out) && ((progress == 1) || ((num_servers_returned + num_servers_timed_out) % progress == 0))) { fprintf(stderr, "\r%d/%d (%d timed out, %d down)%s", num_servers_returned + num_servers_timed_out, num_servers_total, num_servers_timed_out, num_servers_down, rate); } } /* ----- END MODIFICATION ----- Don't need to change anything below here. */ void set_non_blocking(int fd); int set_fds(fd_set *fds); void get_next_timeout(struct timeval *timeout); void set_file_descriptors(); int wait_for_file_descriptors(struct timeval *timeout); struct qserver *get_next_ready_server(); /* Misc flags */ struct timeval packet_recv_time; int one_server_type_id = ~MASTER_SERVER; static int one = 1; static int little_endian; static int big_endian; unsigned int swap_long(void *); unsigned short swap_short(void *); float swap_float_from_little(void *f); #define FORCE 1 /* Print an argument definition */ void printf_opt(const char *opt, const char *desc_format, ...) { va_list args; int i; printf(" %s", opt); for (i = strlen(opt); i < 20; i++) { printf(" "); } printf(" "); va_start(args, desc_format); vprintf(desc_format, args); va_end(args); printf("\n"); } /* Print an error message and the program usage notes */ void usage(char *msg, char **argv, char *a1) { int i; server_type *type; server_type **sorted_types; if (msg) { fprintf(stderr, msg, a1); } printf("Usage: %s ", argv[0]); printf("[options] [-default server-type] [-cfg file] [-f file] [host[:port]] ...\n"); printf("Where host is an IP address or host name\n\n"); printf("Configuration options:\n"); printf_opt("-cfg", "Read the extended types from given file not the default one"); printf_opt("-nocfg", "Ignore qstat configuration loaded from any default location. Must be the first option on the command-line."); printf("\n"); printf("Game query options:\n"); sorted_types = (server_type **)malloc(sizeof(server_type *) * n_server_types); type = &types[0]; for (i = 0; type->id != Q_UNKNOWN_TYPE && i < n_server_types; type++, i++) { sorted_types[i] = type; } quicksort((void **)sorted_types, 0, n_server_types - 1, (int (*)(void *, void *))type_option_compare); for (i = 0; i < n_server_types; i++) { type = sorted_types[i]; printf_opt(type->type_option, "Query %s server", type->game_name); } printf_opt("-default", "Set default server type"); printf("\n"); printf("-default\tset default server type:"); for (i = 0; i < n_server_types; i++) { type = sorted_types[i]; printf(" %s", type->type_string); } printf("\n\n"); printf("Content options:\n"); printf_opt("-sort", "Sort servers and/or players"); printf_opt("-u", "Only display servers that are up"); printf_opt("-nf", "Do not display full servers"); printf_opt("-ne", "Do not display empty servers"); printf_opt("-R", "Fetch and display server rules"); printf_opt("-P", "Fetch and display player info"); printf("\n"); printf("Format options:\n"); printf_opt("-cn", "Display color names instead of numbers"); printf_opt("-ncn", "Display color numbers instead of names"); printf_opt("-hc", "Display colors in #rrggbb format"); printf_opt("-htmlmode", "Convert <, >, and & to the equivalent HTML entities"); printf_opt("-htmlnames", "Colorize Quake 3 and Tribes 2 player names using html font tags"); printf_opt("-nohtmlnames", "Do not colorize Quake 3 and Tribes 2 player names even if $HTML is used in an output template."); printf_opt("-carets", "Display carets in Quake 3 player names"); printf_opt("-hpn", "Display player names in hex"); printf_opt("-hsn", "Display server names in hex"); printf_opt("-tc", "Display time in clock format (DhDDmDDs)"); printf_opt("-tsw", "Display time in stop-watch format (DD:DD:DD)"); printf_opt("-ts", "Display time in seconds"); printf_opt("-pa", "Display player address"); printf("\n"); printf("Display options:\n"); printf_opt("-raw ", "Output in raw format using as delimiter"); printf_opt("-raw-arg", "When used with -raw, always display the server address as it appeared in a file or on the command-line."); printf_opt("-old", "Old style display"); printf_opt("-nh", "Do not display header"); printf_opt("-nx", "Enable name transformations, -nx is set by default"); printf_opt("-nnx", "Disable name transformations, -utf8 option implies -nnx"); printf_opt("-xml", "Output status data as an XML document"); printf_opt("-bom", "Output Byte-Order-Mark for XML output."); printf_opt("-utf8", "Use the UTF-8 character encoding for XML output"); printf_opt("-json", "Output status data as an UTF-8 JSON document"); printf_opt("-Th,-Ts,-Tpt", "Output templates: header, server and player"); printf_opt("-Tr,-Tt", "Output templates: rule, and trailer"); printf_opt("-showgameport", "Always display the game port in QStat output."); printf_opt("-stripunprintable", "Disable stripping of unprintable characters."); printf_opt("-errors", "Display errors"); printf_opt("-progress", "Display progress meter on stderr (text only)"); printf("\n"); printf("Output options:\n"); printf_opt("-of", "Output file"); printf_opt("-af", "Like -of, but append to the file"); printf("\n"); printf("Query options:\n"); printf_opt("-f", "Read hosts from file"); printf_opt("-mdelim ", "For rules with multi values use as delimiter"); printf_opt("-retry", "Number of retries, default is %d", DEFAULT_RETRIES); printf_opt("-interval", "Interval between retries, default is %.2f seconds", DEFAULT_RETRY_INTERVAL / 1000.0); printf_opt("-mi", "Interval between master server retries, default is %.2f seconds", (DEFAULT_RETRY_INTERVAL * 4) / 1000.0); printf_opt("-timeout", "Total time in seconds before giving up"); printf_opt("-maxsim", "Set maximum simultaneous queries"); printf_opt("-sendinterval", "Set time in ms between sending packets, default %u", sendinterval); printf_opt("-allowserverdups", "Allow adding multiple servers with same ip:port (needed for ts2)"); printf_opt("-srcport ", "Send packets from these network ports"); printf_opt("-srcip ", "Send packets using this IP address"); printf_opt("-H", "Resolve host names"); printf_opt("-Hcache", "Host name cache file"); printf("\n"); printf("Advanced options:\n"); printf_opt("-d", "Enable debug options. Specify multiple times to increase debug level."); #ifdef ENABLE_DUMP printf_opt("-dump", "Write received raw packets to dumpNNN files which must not exist before"); printf_opt("-pkt ", "Use file as server reply instead of quering the server. Works only with TF_SINGLE_QUERY servers"); #endif printf_opt("-syncconnect", "Process connect initialisation synchronously."); printf_opt("-noportoffset", "Dont use builtin status port offsets (assume query port was specified)."); #ifdef _WIN32 printf_opt("-noconsole", "Free the console"); #endif printf("\n"); printf("Help options:\n"); printf_opt("-h, --help", "Print this help"); printf_opt("-protocols", "Print the protocol list (json display format only)"); printf_opt("-v", "Print qstat version (can be displayed with -json format)"); printf("\n"); printf("Sort keys:\n"); printf(" servers: p=by-ping, g=by-game, i=by-IP-address, h=by-hostname, n=by-#-players, l=by-list-order\n"); printf(" players: P=by-ping, F=by-frags, T=by-team, N=by-name\n"); printf("\nqstat version %s\n", QSTAT_VERSION); exit(0); } struct server_arg { int type_id; server_type *type; char *arg; char *outfilename; char *query_arg; }; server_type * find_server_type_id(int type_id) { server_type *type = &types[0]; for ( ; type->id != Q_UNKNOWN_TYPE; type++) { if (type->id == type_id) { return (type); } } return (NULL); } server_type * find_server_type_string(char *type_string) { server_type *type = &types[0]; char *t = type_string; for ( ; *t; t++) { *t = tolower(*t); } for ( ; type->id != Q_UNKNOWN_TYPE; type++) { if (strcmp(type->type_string, type_string) == 0) { return (type); } } return (NULL); } server_type * find_server_type_option(char *option) { server_type *type = &types[0]; for ( ; type->id != Q_UNKNOWN_TYPE; type++) { if (strcmp(type->type_option, option) == 0) { return (type); } } return (NULL); } server_type * parse_server_type_option(char *option, int *outfile, char **query_arg) { server_type *type = &types[0]; char *comma, *arg; int len; *outfile = 0; *query_arg = 0; comma = strchr(option, ','); if (comma) { *comma++ = '\0'; } for ( ; type->id != Q_UNKNOWN_TYPE; type++) { if (strcmp(type->type_option, option) == 0) { break; } } if (type->id == Q_UNKNOWN_TYPE) { return (NULL); } if (!comma) { return (type); } if (strcmp(comma, "outfile") == 0) { *outfile = 1; comma = strchr(comma, ','); if (!comma) { return (type); } *comma++ = '\0'; } *query_arg = strdup(comma); arg = comma; do { comma = strchr(arg, ','); if (comma) { len = comma - arg; } else { len = strlen(arg); } if (strncmp(arg, "outfile", len) == 0) { *outfile = 1; } arg = comma + 1; } while (comma); return (type); } void add_server_arg(char *arg, int type, char *outfilename, char *query_arg, struct server_arg **args, int *n, int *max) { if (*n == *max) { if (*max == 0) { *max = 4; *args = (struct server_arg *)malloc(sizeof(struct server_arg) * (*max)); } else { (*max) *= 2; *args = (struct server_arg *)realloc(*args, sizeof(struct server_arg) * (*max)); } } (*args)[*n].type_id = type; /* (*args)[*n].type= find_server_type_id( type); */ (*args)[*n].type = NULL; (*args)[*n].arg = arg; (*args)[*n].outfilename = outfilename; (*args)[*n].query_arg = query_arg; (*n)++; } void add_query_param(struct qserver *server, char *arg) { char *equal; struct query_param *param; equal = strchr(arg, '='); *equal++ = '\0'; param = (struct query_param *)malloc(sizeof(struct query_param)); param->key = arg; param->value = equal; sscanf(equal, "%i", ¶m->i_value); sscanf(equal, "%i", ¶m->ui_value); param->next = server->params; server->params = param; } char * get_param_value(struct qserver *server, const char *key, char *default_value) { struct query_param *p = server->params; for ( ; p; p = p->next) { if (strcasecmp(key, p->key) == 0) { return (p->value); } } return (default_value); } int get_param_i_value(struct qserver *server, char *key, int default_value) { struct query_param *p = server->params; for ( ; p; p = p->next) { if (strcasecmp(key, p->key) == 0) { return (p->i_value); } } return (default_value); } unsigned int get_param_ui_value(struct qserver *server, char *key, unsigned int default_value) { struct query_param *p = server->params; for ( ; p; p = p->next) { if (strcasecmp(key, p->key) == 0) { return (p->ui_value); } } return (default_value); } int parse_source_address(char *addr, unsigned int *ip, unsigned short *port) { char *colon; colon = strchr(addr, ':'); if (colon) { *colon = '\0'; *port = atoi(colon + 1); if (colon == addr) { return (0); } } else { *port = 0; } *ip = inet_addr(addr); if ((*ip == INADDR_NONE) && !isdigit((unsigned char)*ip)) { *ip = hcache_lookup_hostname(addr); } if (*ip == INADDR_NONE) { fprintf(stderr, "%s: Not an IP address or unknown host name\n", addr); return (-1); } *ip = ntohl(*ip); return (0); } int parse_source_port(char *port, unsigned short *low, unsigned short *high) { char *dash; *low = atoi(port); dash = strchr(port, '-'); *high = 0; if (dash) { *high = atoi(dash + 1); } if (*high == 0) { *high = *low; } if (*high < *low) { fprintf(stderr, "%s: Invalid port range\n", port); return (-1); } return (0); } void add_config_server_types() { int n_config_types, n_builtin_types, i; server_type **config_types; server_type *new_types, *type; config_types = qsc_get_config_server_types(&n_config_types); if (n_config_types == 0) { return; } n_builtin_types = (sizeof(builtin_types) / sizeof(server_type)) - 1; new_types = (server_type *)malloc(sizeof(server_type) * (n_builtin_types + n_config_types + 1)); memcpy(new_types, &builtin_types[0], n_builtin_types * sizeof(server_type)); type = &new_types[n_builtin_types]; for (i = n_config_types; i; i--, config_types++, type++) { *type = **config_types; } n_server_types = n_builtin_types + n_config_types; new_types[n_server_types].id = Q_UNKNOWN_TYPE; if (types != &builtin_types[0]) { free(types); } types = new_types; } void revert_server_types() { if (types != &builtin_types[0]) { free(types); } n_server_types = (sizeof(builtin_types) / sizeof(server_type)) - 1; types = &builtin_types[0]; } #ifdef ENABLE_DUMP unsigned pkt_dump_pos = 0; const char *pkt_dumps[64] = { 0 }; static void add_pkt_from_file(const char *file) { if (pkt_dump_pos >= sizeof(pkt_dumps) / sizeof(pkt_dumps[0])) { return; } pkt_dumps[pkt_dump_pos++] = file; } static void replay_pkt_dumps() { struct qserver *server = servers; char *pkt = NULL; int fd; int bytes_read = 0; // should be ssize_t but for ease with win32 int i; struct stat statbuf; gettimeofday(&packet_recv_time, NULL); for (i = 0; i < pkt_dump_pos; i++) { if ((fd = open(pkt_dumps[i], O_RDONLY)) == -1) { goto err; } if (fstat(fd, &statbuf) == -1) { goto err; } pkt = malloc(statbuf.st_size); if (NULL == pkt) { goto err; } bytes_read = read(fd, pkt, statbuf.st_size); if (bytes_read != statbuf.st_size) { fprintf(stderr, "Failed to read entire packet from disk got %d of %ld bytes\n", bytes_read, (long)statbuf.st_size); goto err; } close(fd); fd = 0; debug(2, "replay, pre-packet_func"); process_func_ret(server, server->type->packet_func(server, pkt, statbuf.st_size)); debug(2, "replay, post-packet_func"); } goto out; err: perror(__FUNCTION__); close(fd); out: fd = 0; // NOP } #endif // ENABLE_DUMP struct rcv_pkt { struct qserver *server; struct sockaddr_in addr; struct timeval recv_time; char data[PACKET_LEN]; int len; int _errno; }; void do_work(void) { int pktlen, rc; char *pkt = NULL; int bind_retry = 0; struct timeval timeout; struct rcv_pkt *buffer; unsigned buffill = 0, i = 0; unsigned bufsize = max_simultaneous * 2; struct timeval t, ts; gettimeofday(&t, NULL); ts = t; buffer = malloc(sizeof(struct rcv_pkt) * bufsize); if (!buffer) { return; } #ifdef ENABLE_DUMP if (pkt_dump_pos) { replay_pkt_dumps(); } else #endif { bind_retry = bind_sockets(); } send_packets(); debug(2, "connected: %d", connected); while (connected || (!connected && bind_retry == -2)) { if (!connected && (bind_retry == -2)) { rc = wait_for_timeout(60); bind_retry = bind_sockets(); continue; } bind_retry = 0; set_file_descriptors(); if (progress) { display_progress(); } get_next_timeout(&timeout); rc = wait_for_file_descriptors(&timeout); debug(2, "rc %d", rc); if (rc == SOCKET_ERROR) { #ifndef _WIN32 if (errno == EINTR) { continue; } #endif perror("select"); break; } for ( ; rc && buffill < bufsize; rc--) { int addrlen = sizeof(buffer[buffill].addr); struct qserver *server = get_next_ready_server(); if (server == NULL) { break; } gettimeofday(&buffer[buffill].recv_time, NULL); pktlen = recvfrom(server->fd, buffer[buffill].data, sizeof(buffer[buffill].data), 0, (struct sockaddr *)&buffer[buffill].addr, (void *)&addrlen); debug(2, "recvfrom: %d", pktlen); // pktlen == 0 is no error condition! happens on remote tcp socket close if (pktlen == SOCKET_ERROR) { if (connection_would_block()) { malformed_packet(server, "EAGAIN on UDP socket, probably incorrect checksum"); } else if (connection_refused() || connection_reset()) { server->server_name = DOWN; num_servers_down++; cleanup_qserver(server, FORCE); } continue; } debug(1, "recv %3d %3d %d.%d.%d.%d:%hu\n", time_delta(&buffer[buffill].recv_time, &ts), time_delta(&buffer[buffill].recv_time, &t), server->ipaddr & 0xff, (server->ipaddr >> 8) & 0xff, (server->ipaddr >> 16) & 0xff, (server->ipaddr >> 24) & 0xff, server->port ); t = buffer[buffill].recv_time; buffer[buffill].server = server; buffer[buffill].len = pktlen; ++buffill; } debug(2, "fill: %d < %d", buffill, bufsize); for (i = 0; i < buffill; ++i) { struct qserver *server = buffer[i].server; pkt = buffer[i].data; pktlen = buffer[i].len; memcpy(&packet_recv_time, &buffer[i].recv_time, sizeof(packet_recv_time)); if (get_debug_level() > 2) { print_packet(server, pkt, pktlen); } #ifdef ENABLE_DUMP if (do_dump) { dump_packet(pkt, pktlen); } #endif if (server->flags & FLAG_BROADCAST) { struct qserver *broadcast = server; unsigned short port = ntohs(buffer[i].addr.sin_port); /* create new server and init */ if (!(no_port_offset || server->flags & TF_NO_PORT_OFFSET)) { port -= server->type->port_offset; } server = add_qserver_byaddr(ntohl(buffer[i].addr.sin_addr.s_addr), port, server->type, NULL); if (server == NULL) { server = find_server_by_address(buffer[i].addr.sin_addr.s_addr, ntohs(buffer[i].addr.sin_port)); if (server == NULL) { continue; } /* * if ( show_errors) * { * fprintf(stderr, * "duplicate or invalid packet received from 0x%08x:%hu\n", * ntohl(buffer[i].addr.sin_addr.s_addr), ntohs(buffer[i].addr.sin_port)); * print_packet( NULL, pkt, pktlen); * } * continue; */ } else { server->packet_time1 = broadcast->packet_time1; server->packet_time2 = broadcast->packet_time2; server->ping_total = broadcast->ping_total; server->n_requests = broadcast->n_requests; server->n_packets = broadcast->n_packets; broadcast->n_servers++; } } debug(2, "connected, pre-packet_func: %d", connected); process_func_ret(server, server->type->packet_func(server, pkt, pktlen)); debug(2, "connected, post-packet_func: %d", connected); } buffill = 0; if (run_timeout && (time(0) - start_time >= run_timeout)) { debug(2, "run timeout reached"); break; } send_packets(); if (connected < max_simultaneous) { bind_retry = bind_sockets(); } debug(2, "connected: %d", connected); } free(buffer); } int main(int argc, char *argv[]) { int arg, n_files, i; char **files, *outfilename, *query_arg; struct server_arg *server_args = NULL; int n_server_args = 0, max_server_args = 0; int default_server_type_id; #ifdef _WIN32 WORD version = MAKEWORD(1, 1); WSADATA wsa_data; if (WSAStartup(version, &wsa_data) != 0) { fprintf(stderr, "Could not open winsock\n"); exit(1); } #else signal(SIGPIPE, SIG_IGN); #endif types = &builtin_types[0]; n_server_types = (sizeof(builtin_types) / sizeof(server_type)) - 1; i = qsc_load_default_config_files(); if (i == -1) { return (1); } else if (i == 0) { add_config_server_types(); } if (argc == 1) { usage(NULL, argv, NULL); } OF = stdout; files = (char **)malloc(sizeof(char *) * (argc / 2)); n_files = 0; default_server_type_id = Q_SERVER; little_endian = ((char *)&one)[0]; big_endian = !little_endian; for (arg = 1; arg < argc; arg++) { if (argv[arg][0] != '-') { break; } outfilename = NULL; if ((strcmp(argv[arg], "-nocfg") == 0) && (arg == 1)) { revert_server_types(); } else if (strcmp(argv[arg], "--help") == 0) { usage(NULL, argv, NULL); } else if (strcmp(argv[arg], "-h") == 0) { usage(NULL, argv, NULL); } else if (strcmp(argv[arg], "-protocols") == 0) { if (json_display == 1) { json_protocols(); } else { fprintf(stderr, "must be used with -json\n"); exit(1); } } else if (strcmp(argv[arg], "-v") == 0) { if (json_display == 1) { json_version(); } else { printf("%s\n", QSTAT_VERSION); } } else if (strcmp(argv[arg], "-f") == 0) { arg++; if (arg >= argc) { usage("missing argument for -f\n", argv, NULL); } files[n_files++] = argv[arg]; } else if (strcmp(argv[arg], "-retry") == 0) { arg++; if (arg >= argc) { usage("missing argument for -retry\n", argv, NULL); } n_retries = atoi(argv[arg]); if (n_retries <= 0) { fprintf(stderr, "retries must be greater than zero\n"); exit(1); } } else if (strcmp(argv[arg], "-interval") == 0) { double value = 0.0; arg++; if (arg >= argc) { usage("missing argument for -interval\n", argv, NULL); } sscanf(argv[arg], "%lf", &value); if (value < 0.1) { fprintf(stderr, "retry interval must be greater than 0.1\n"); exit(1); } retry_interval = (int)(value * 1000); } else if (strcmp(argv[arg], "-mi") == 0) { double value = 0.0; arg++; if (arg >= argc) { usage("missing argument for -mi\n", argv, NULL); } sscanf(argv[arg], "%lf", &value); if (value < 0.1) { fprintf(stderr, "interval must be greater than 0.1\n"); exit(1); } master_retry_interval = (int)(value * 1000); } else if (strcmp(argv[arg], "-H") == 0) { hostname_lookup = 1; } else if (strcmp(argv[arg], "-u") == 0) { up_servers_only = 1; } else if (strcmp(argv[arg], "-nf") == 0) { no_full_servers = 1; } else if (strcmp(argv[arg], "-ne") == 0) { no_empty_servers = 1; } else if (strcmp(argv[arg], "-nh") == 0) { no_header_display = 1; } else if (strcmp(argv[arg], "-old") == 0) { new_style = 0; } else if (strcmp(argv[arg], "-P") == 0) { get_player_info = 1; } else if (strcmp(argv[arg], "-R") == 0) { get_server_rules = 1; } else if (strncmp(argv[arg], "-raw", 4) == 0) { if (json_display == 1) { usage("cannot specify both -json and -raw\n", argv, NULL); } if (xml_display == 1) { usage("cannot specify both -xml and -raw\n", argv, NULL); } if (argv[arg][4] == ',') { if (strcmp(&argv[arg][5], "game") == 0) { show_game_in_raw = 1; } else { usage("Unknown -raw option\n", argv, NULL); } } arg++; if (arg >= argc) { usage("missing argument for -raw\n", argv, NULL); } raw_delimiter = argv[arg]; // Check the multi rule delimiter isnt the same // If it is fix to maintain backwards compatibility if ((0 == strcmp(raw_delimiter, multi_delimiter)) && (0 == strcmp(raw_delimiter, "|"))) { multi_delimiter = ":"; } raw_display = 1; } else if (strcmp(argv[arg], "-mdelim") == 0) { arg++; if (arg >= argc) { usage("missing argument for -mdelim\n", argv, NULL); } multi_delimiter = argv[arg]; } else if (strcmp(argv[arg], "-xml") == 0) { xml_display = 1; if (raw_display == 1) { usage("cannot specify both -raw and -xml\n", argv, NULL); } if (json_display == 1) { usage("cannot specify both -json and -xml\n", argv, NULL); } } else if (strcmp(argv[arg], "-json") == 0) { json_display = 1; if (raw_display == 1) { usage("cannot specify both -raw and -json\n", argv, NULL); } if (xml_display == 1) { usage("cannot specify both -xml and -json\n", argv, NULL); } } else if (strcmp(argv[arg], "-utf8") == 0) { xml_encoding = ENCODING_UTF_8; xform_names = 0; } else if (strcmp(argv[arg], "-ncn") == 0) { color_names = 0; } else if (strcmp(argv[arg], "-cn") == 0) { color_names = 1; } else if (strcmp(argv[arg], "-hc") == 0) { color_names = 2; } else if (strcmp(argv[arg], "-nx") == 0) { xform_names = 1; } else if (strcmp(argv[arg], "-nnx") == 0) { xform_names = 0; } else if (strcmp(argv[arg], "-tc") == 0) { time_format = CLOCK_TIME; } else if (strcmp(argv[arg], "-tsw") == 0) { time_format = STOPWATCH_TIME; } else if (strcmp(argv[arg], "-ts") == 0) { time_format = SECONDS; } else if (strcmp(argv[arg], "-pa") == 0) { player_address = 1; } else if (strcmp(argv[arg], "-hpn") == 0) { xform_hex_player_names = 1; } else if (strcmp(argv[arg], "-hsn") == 0) { xform_hex_server_names = 1; } else if (strncmp(argv[arg], "-maxsimultaneous", 7) == 0) { arg++; if (arg >= argc) { usage("missing argument for -maxsimultaneous\n", argv, NULL); } max_simultaneous = atoi(argv[arg]); if (max_simultaneous <= 0) { usage("value for -maxsimultaneous must be > 0\n", argv, NULL); } if (max_simultaneous > FD_SETSIZE) { max_simultaneous = FD_SETSIZE; } } else if (strcmp(argv[arg], "-sendinterval") == 0) { arg++; if (arg >= argc) { usage("missing argument for -sendinterval\n", argv, NULL); } sendinterval = atoi(argv[arg]); if (sendinterval < 0) { usage("value for -sendinterval must be >= 0\n", argv, NULL); } } else if (strcmp(argv[arg], "-raw-arg") == 0) { raw_arg = 1000; } else if (strcmp(argv[arg], "-timeout") == 0) { arg++; if (arg >= argc) { usage("missing argument for -timeout\n", argv, NULL); } run_timeout = atoi(argv[arg]); if (run_timeout <= 0) { usage("value for -timeout must be > 0\n", argv, NULL); } } else if (strncmp(argv[arg], "-progress", sizeof("-progress") - 1) == 0) { char *p = argv[arg] + sizeof("-progress") - 1; progress = 1; if (*p == ',') { progress = atoi(p + 1); } } else if (strcmp(argv[arg], "-Hcache") == 0) { arg++; if (arg >= argc) { usage("missing argument for -Hcache\n", argv, NULL); } if (hcache_open(argv[arg], 0) == -1) { return (1); } } else if (strcmp(argv[arg], "-default") == 0) { arg++; if (arg >= argc) { usage("missing argument for -default\n", argv, NULL); } default_server_type = find_server_type_string(argv[arg]); if (default_server_type == NULL) { char opt[256], *o = &opt[0]; sprintf(opt, "-%s", argv[arg]); for ( ; *o; o++) { *o = tolower(*o); } default_server_type = find_server_type_option(opt); } if (default_server_type == NULL) { fprintf(stderr, "unknown server type \"%s\"\n", argv[arg]); usage(NULL, argv, NULL); } default_server_type_id = default_server_type->id; default_server_type = NULL; } else if (strncmp(argv[arg], "-Tserver", 3) == 0) { arg++; if (arg >= argc) { usage("missing argument for %s\n", argv, argv[arg - 1]); } if (read_qserver_template(argv[arg]) == -1) { return (1); } } else if (strncmp(argv[arg], "-Trule", 3) == 0) { arg++; if (arg >= argc) { usage("missing argument for %s\n", argv, argv[arg - 1]); } if (read_rule_template(argv[arg]) == -1) { return (1); } } else if (strncmp(argv[arg], "-Theader", 3) == 0) { arg++; if (arg >= argc) { usage("missing argument for %s\n", argv, argv[arg - 1]); } if (read_header_template(argv[arg]) == -1) { return (1); } } else if (strncmp(argv[arg], "-Ttrailer", 3) == 0) { arg++; if (arg >= argc) { usage("missing argument for %s\n", argv, argv[arg - 1]); } if (read_trailer_template(argv[arg]) == -1) { return (1); } } else if (strncmp(argv[arg], "-Tplayer", 3) == 0) { arg++; if (arg >= argc) { usage("missing argument for %s\n", argv, argv[arg - 1]); } if (read_player_template(argv[arg]) == -1) { return (1); } } else if (strcmp(argv[arg], "-sort") == 0) { size_t pos; arg++; if (arg >= argc) { usage("missing argument for -sort\n", argv, NULL); } strncpy(sort_keys, argv[arg], sizeof(sort_keys) - 1); pos = strspn(sort_keys, SUPPORTED_SORT_KEYS); if (pos != strlen(sort_keys)) { fprintf(stderr, "Unknown sort key \"%c\", valid keys are \"%s\"\n", sort_keys[pos], SUPPORTED_SORT_KEYS); return (1); } server_sort = strpbrk(sort_keys, SUPPORTED_SERVER_SORT) != NULL; if (strchr(sort_keys, 'l')) { server_sort = 1; } player_sort = strpbrk(sort_keys, SUPPORTED_PLAYER_SORT) != NULL; } else if (strcmp(argv[arg], "-errors") == 0) { show_errors++; } else if (strcmp(argv[arg], "-of") == 0) { arg++; if (arg >= argc) { usage("missing argument for %s\n", argv, argv[arg - 1]); } if ((argv[arg][0] == '-') && (argv[arg][1] == '\0')) { OF = stdout; } else { OF = fopen(argv[arg], "w"); } if (OF == NULL) { perror(argv[arg]); return (1); } } else if (strcmp(argv[arg], "-af") == 0) { arg++; if (arg >= argc) { usage("missing argument for %s\n", argv, argv[arg - 1]); } if ((argv[arg][0] == '-') && (argv[arg][1] == '\0')) { OF = stdout; } else { OF = fopen(argv[arg], "a"); } if (OF == NULL) { perror(argv[arg]); return (1); } } else if (strcmp(argv[arg], "-htmlnames") == 0) { xform_html_names = 1; } else if (strcmp(argv[arg], "-nohtmlnames") == 0) { xform_html_names = 0; } else if (strcmp(argv[arg], "-htmlmode") == 0) { html_mode = 1; } else if (strcmp(argv[arg], "-carets") == 0) { xform_strip_carets = 0; } else if (strcmp(argv[arg], "-d") == 0) { set_debug_level(get_debug_level() + 1); } else if (strcmp(argv[arg], "-showgameport") == 0) { show_game_port = 1; } else if (strcmp(argv[arg], "-noportoffset") == 0) { no_port_offset = 1; } else if (strcmp(argv[arg], "-srcip") == 0) { arg++; if (arg >= argc) { usage("missing argument for %s\n", argv, argv[arg - 1]); } if (parse_source_address(argv[arg], &source_ip, &source_port) == -1) { return (1); } if (source_port) { source_port_low = source_port; source_port_high = source_port; } } else if (strcmp(argv[arg], "-syncconnect") == 0) { syncconnect = 1; } else if (strcmp(argv[arg], "-stripunprintable") == 0) { xform_strip_unprintable = 1; } else if (strcmp(argv[arg], "-srcport") == 0) { arg++; if (arg >= argc) { usage("missing argument for %s\n", argv, argv[arg - 1]); } if (parse_source_port(argv[arg], &source_port_low, &source_port_high) == -1) { return (1); } source_port = source_port_low; } else if (strcmp(argv[arg], "-cfg") == 0) { arg++; if (arg >= argc) { usage("missing argument for %s\n", argv, argv[arg - 1]); } if (qsc_load_config_file(argv[arg]) == -1) { return (1); } add_config_server_types(); } else if (strcmp(argv[arg], "-allowserverdups") == 0) { noserverdups = 0; } else if (strcmp(argv[arg], "-bom") == 0) { output_bom = 1; } #ifdef ENABLE_DUMP else if (strcmp(argv[arg], "-dump") == 0) { do_dump = 1; } else if (strcmp(argv[arg], "-pkt") == 0) { arg++; if (arg >= argc) { usage("missing argument for %s\n", argv, argv[arg - 1]); } add_pkt_from_file(argv[arg]); } #endif #ifdef _WIN32 else if (strcmp(argv[arg], "-noconsole") == 0) { FreeConsole(); } #endif else { int outfile; server_type *type; arg++; if (arg >= argc) { fprintf(stderr, "missing argument for \"%s\"\n", argv[arg - 1]); return (1); } type = parse_server_type_option(argv[arg - 1], &outfile, &query_arg); if (type == NULL) { fprintf(stderr, "unknown option \"%s\"\n", argv[arg - 1]); return (1); } outfilename = NULL; if (outfile) { outfilename = strchr(argv[arg], ','); if (outfilename == NULL) { fprintf(stderr, "missing file name for \"%s,outfile\"\n", argv[arg - 1]); return (1); } *outfilename++ = '\0'; } /* * if ( query_arg && !(type->flags & TF_QUERY_ARG)) { * fprintf( stderr, "option flag \"%s\" not allowed for this server type\n", * query_arg); * return 1; * } */ if (type->flags & TF_QUERY_ARG_REQUIRED && !query_arg) { fprintf(stderr, "option flag missing for server type \"%s\"\n", argv[arg - 1]); return (1); } add_server_arg(argv[arg], type->id, outfilename, query_arg, &server_args, &n_server_args, &max_server_args); } } start_time = time(0); default_server_type = find_server_type_id(default_server_type_id); for (i = 0; i < n_files; i++) { add_file(files[i]); } for ( ; arg < argc; arg++) { add_qserver(argv[arg], default_server_type, NULL, NULL); } for (i = 0; i < n_server_args; i++) { server_type *server_type = find_server_type_id(server_args[i].type_id); add_qserver(server_args[i].arg, server_type, server_args[i].outfilename, server_args[i].query_arg); } free(server_args); if (servers == NULL) { exit(1); } max_connmap = max_simultaneous + 10; connmap = (struct qserver **)calloc(1, sizeof(struct qserver *) * max_connmap); if (color_names == -1) { color_names = (raw_display) ? DEFAULT_COLOR_NAMES_RAW : DEFAULT_COLOR_NAMES_DISPLAY; } if (time_format == -1) { time_format = (raw_display) ? DEFAULT_TIME_FMT_RAW : DEFAULT_TIME_FMT_DISPLAY; } if ((one_server_type_id & MASTER_SERVER) || (one_server_type_id == 0)) { display_prefix = 1; } if (xml_display) { xml_header(); } else if (json_display) { json_header(); } else if (new_style && !raw_display && !have_server_template()) { display_header(); } else if (have_header_template()) { template_display_header(); } q_serverinfo.length = htons(q_serverinfo.length); h2_serverinfo.length = htons(h2_serverinfo.length); q_player.length = htons(q_player.length); do_work(); finish_output(); free_server_hash(); free(files); free(connmap); return (0); } void finish_output() { int i; hcache_update_file(); if (progress) { display_progress(); fputs("\n", stderr); } if (server_sort) { struct qserver **array, *server, *next_server; if (strchr(sort_keys, 'l') && (strpbrk(sort_keys, SUPPORTED_SERVER_SORT) == NULL)) { server = servers; for ( ; server; server = next_server) { next_server = server->next; display_server(server); } } else { array = (struct qserver **)malloc(sizeof(struct qserver *) * num_servers_total); server = servers; for (i = 0; server != NULL; i++) { array[i] = server; server = server->next; } sort_servers(array, num_servers_total); if (progress) { fprintf(stderr, "\n"); } for (i = 0; i < num_servers_total; i++) { display_server(array[i]); } free(array); } } else { struct qserver *server, *next_server; server = servers; for ( ; server; server = next_server) { next_server = server->next; if (server->server_name == HOSTNOTFOUND) { display_server(server); } } } if (xml_display) { xml_footer(); } else if (json_display) { json_footer(); } else if (have_trailer_template()) { template_display_trailer(); } if (OF != stdout) { fclose(OF); } } void add_file(char *filename) { FILE *file; char name[200], *comma, *query_arg = NULL; server_type *type; debug(4, "Loading servers from '%s'...\n", filename); if (strcmp(filename, "-") == 0) { file = stdin; current_filename = NULL; } else { file = fopen(filename, "r"); current_filename = filename; } current_fileline = 1; if (file == NULL) { perror(filename); return; } for ( ; fscanf(file, "%s", name) == 1; current_fileline++) { comma = strchr(name, ','); if (comma) { *comma++ = '\0'; query_arg = strdup(comma); } type = find_server_type_string(name); if (type == NULL) { add_qserver(name, default_server_type, NULL, NULL); } else if (fscanf(file, "%s", name) == 1) { if (type->flags & TF_QUERY_ARG && comma && *query_arg) { add_qserver(name, type, NULL, query_arg); } else { add_qserver(name, type, NULL, NULL); } } } if (file != stdin) { fclose(file); } debug(4, "Loaded servers from '%s'\n", filename); current_fileline = 0; } void print_file_location() { if (current_fileline != 0) { fprintf(stderr, "%s:%d: ", current_filename ? current_filename : "", current_fileline); } } void parse_query_params(struct qserver *server, char *params) { char *comma, *arg = params; do { comma = strchr(arg, ','); if (comma) { *comma = '\0'; } if (strchr(arg, '=')) { add_query_param(server, arg); } else if ((strcmp(arg, "noportoffset") == 0) || (strcmp(arg, "qp") == 0)) { server->flags |= TF_NO_PORT_OFFSET; } else if ((strcmp(arg, "showgameport") == 0) || (strcmp(arg, "gp") == 0)) { server->flags |= TF_SHOW_GAME_PORT; } arg = comma + 1; } while (comma); } int add_qserver(char *arg, server_type *type, char *outfilename, char *query_arg) { struct qserver *server, *prev_server; int flags = 0; char *colon = NULL, *arg_copy, *hostname = NULL; unsigned int ipaddr; unsigned short port, port_max; int portrange = 0; unsigned colonpos = 0; debug(4, "%s, %s, %s, %s\n", arg, (NULL != type) ? type->type_string : "unknown", outfilename, query_arg); if (run_timeout && (time(0) - start_time >= run_timeout)) { finish_output(); exit(0); } port = port_max = type->default_port; if (outfilename && (strcmp(outfilename, "-") != 0)) { FILE *outfile = fopen(outfilename, "r+"); if ((outfile == NULL) && ((errno == EACCES) || (errno == EISDIR) || (errno == ENOSPC) || (errno == ENOTDIR))) { perror(outfilename); return (-1); } if (outfile) { fclose(outfile); } } arg_copy = strdup(arg); colon = strchr(arg, ':'); if (colon != NULL) { if (sscanf(colon + 1, "%hu-%hu", &port, &port_max) == 2) { portrange = 1; } else { port_max = port; } *colon = '\0'; colonpos = colon - arg; } if (*arg == '+') { flags |= FLAG_BROADCAST; arg++; } ipaddr = inet_addr(arg); if (ipaddr == INADDR_NONE) { if (strcmp(arg, "255.255.255.255") != 0) { ipaddr = htonl(hcache_lookup_hostname(arg)); } } else if (hostname_lookup && !(flags & FLAG_BROADCAST)) { hostname = hcache_lookup_ipaddr(ntohl(ipaddr)); } if (((ipaddr == INADDR_NONE) || (ipaddr == 0)) && (strcmp(arg, "255.255.255.255") != 0)) { if (show_errors) { print_file_location(); fprintf(stderr, "%s: %s\n", arg, strherror(h_errno)); } server = (struct qserver *)calloc(1, sizeof(struct qserver)); // NOTE: 0 != port to prevent infinite loop due to lack of range on unsigned short for ( ; port <= port_max && 0 != port; ++port) { init_qserver(server, type); if (portrange) { server->arg = (port == port_max) ? arg_copy : strdup(arg_copy); // NOTE: arg_copy and therefore server->arg will always have enough space as it was a port range sprintf(server->arg + colonpos + 1, "%hu", port); } else { server->arg = arg_copy; } server->server_name = HOSTNOTFOUND; server->error = strdup(strherror(h_errno)); server->orig_port = server->query_port = server->port = port; if (last_server != &servers) { prev_server = (struct qserver *)((char *)last_server - ((char *)&server->next - (char *)server)); server->prev = prev_server; } *last_server = server; last_server = &server->next; if (one_server_type_id == ~MASTER_SERVER) { one_server_type_id = type->id; } else if (one_server_type_id != type->id) { one_server_type_id = 0; } } return (-1); } // NOTE: 0 != port to prevent infinite loop due to lack of range on unsigned short for ( ; port <= port_max && 0 != port; ++port) { if (noserverdups && (find_server_by_address(ipaddr, port) != NULL)) { continue; } server = (struct qserver *)calloc(1, sizeof(struct qserver)); server->arg = port == port_max ? arg_copy : strdup(arg_copy); if (portrange) { sprintf(server->arg + colonpos + 1, "%hu", port); } if (hostname && colon) { server->host_name = (char *)malloc(strlen(hostname) + 5 + 2); sprintf(server->host_name, "%s:%hu", hostname, port); } else { server->host_name = strdup((hostname) ? hostname : arg); } server->ipaddr = ipaddr; server->orig_port = server->query_port = server->port = port; server->type = type; server->outfilename = outfilename; server->flags = flags; server->state = STATE_INIT; if (query_arg) { server->query_arg = (port == port_max) ? query_arg : strdup(query_arg); parse_query_params(server, server->query_arg); } else { server->query_arg = NULL; } init_qserver(server, type); if (server->type->master) { waiting_for_masters++; } if (num_servers_total % 10 == 0) { hcache_update_file(); } if (last_server != &servers) { prev_server = (struct qserver *)((char *)last_server - ((char *)&server->next - (char *)server)); server->prev = prev_server; } *last_server = server; last_server = &server->next; add_server_to_hash(server); if (one_server_type_id == ~MASTER_SERVER) { one_server_type_id = type->id; } else if (one_server_type_id != type->id) { one_server_type_id = 0; } ++num_servers; } return (0); } struct qserver * add_qserver_byaddr(unsigned int ipaddr, unsigned short port, server_type *type, int *new_server) { char arg[36]; struct qserver *server, *prev_server; char *hostname = NULL; if (run_timeout && (time(0) - start_time >= run_timeout)) { finish_output(); exit(0); } if (new_server) { *new_server = 0; } ipaddr = htonl(ipaddr); if (ipaddr == 0) { return (0); } // TODO: this prevents servers with the same ip:port being queried // and hence breaks virtual servers support e.g. Teamspeak 2 if (find_server_by_address(ipaddr, port) != NULL) { return (0); } if (new_server) { *new_server = 1; } server = (struct qserver *)calloc(1, sizeof(struct qserver)); server->ipaddr = ipaddr; ipaddr = ntohl(ipaddr); sprintf(arg, "%d.%d.%d.%d:%hu", ipaddr >> 24, (ipaddr >> 16) & 0xff, (ipaddr >> 8) & 0xff, ipaddr & 0xff, port); server->arg = strdup(arg); if (hostname_lookup) { hostname = hcache_lookup_ipaddr(ipaddr); } if (hostname) { server->host_name = (char *)malloc(strlen(hostname) + 6 + 2); sprintf(server->host_name, "%s:%hu", hostname, port); } else { server->host_name = strdup(arg); } server->orig_port = server->query_port = server->port = port; init_qserver(server, type); if (num_servers_total % 10 == 0) { hcache_update_file(); } if (last_server != &servers) { prev_server = (struct qserver *)((char *)last_server - ((char *)&server->next - (char *)server)); server->prev = prev_server; } *last_server = server; last_server = &server->next; add_server_to_hash(server); ++num_servers; return (server); } void add_servers_from_masters() { struct qserver *server; unsigned int ipaddr, i; unsigned short port; int n_servers, new_server, port_adjust = 0; char *pkt; server_type *server_type; FILE *outfile; for (server = servers; server != NULL; server = server->next) { if (!server->type->master || (server->master_pkt == NULL)) { continue; } pkt = server->master_pkt; if (server->query_arg && (server->type->id == GAMESPY_MASTER)) { server_type = find_server_type_string(server->query_arg); if (server_type == NULL) { server_type = find_server_type_id(server->type->master); } } else { server_type = find_server_type_id(server->type->master); } if ((server->type->id == GAMESPY_MASTER) && server_type) { if (server_type->id == UN_SERVER) { port_adjust = -1; } else if (server_type->id == KINGPIN_SERVER) { port_adjust = 10; } } outfile = NULL; if (server->outfilename) { if (strcmp(server->outfilename, "-") == 0) { outfile = stdout; } else { outfile = fopen(server->outfilename, "w"); } if (outfile == NULL) { perror(server->outfilename); continue; } } n_servers = 0; for (i = 0; i < server->master_pkt_len; i += 6) { memcpy(&ipaddr, &pkt[i], 4); memcpy(&port, &pkt[i + 4], 2); ipaddr = ntohl(ipaddr); port = ntohs(port) + port_adjust; new_server = 1; if (outfile) { fprintf(outfile, "%s %d.%d.%d.%d:%hu\n", server_type ? server_type->type_string : "", (ipaddr >> 24) & 0xff, (ipaddr >> 16) & 0xff, (ipaddr >> 8) & 0xff, ipaddr & 0xff, port ); } else if (server_type == NULL) { xform_printf(OF, "%d.%d.%d.%d:%hu\n", (ipaddr >> 24) & 0xff, (ipaddr >> 16) & 0xff, (ipaddr >> 8) & 0xff, ipaddr & 0xff, port); } else { add_qserver_byaddr(ipaddr, port, server_type, &new_server); } n_servers += new_server; } free(server->master_pkt); server->master_pkt = NULL; server->master_pkt_len = 0; server->n_servers = n_servers; if (outfile) { fclose(outfile); } } if (hostname_lookup) { hcache_update_file(); } bind_sockets(); } void init_qserver(struct qserver *server, server_type *type) { server->server_name = NULL; server->map_name = NULL; server->game = NULL; server->num_players = 0; server->num_spectators = 0; server->fd = -1; server->state = STATE_INIT; if (server->flags & FLAG_BROADCAST) { server->retry1 = 1; server->retry2 = 1; } else { server->retry1 = n_retries; server->retry2 = n_retries; } server->n_retries = 0; server->ping_total = 0; server->n_packets = 0; server->n_requests = 0; server->n_servers = 0; server->master_pkt_len = 0; server->master_pkt = NULL; server->error = NULL; server->saved_data.data = NULL; server->saved_data.datalen = 0; server->saved_data.pkt_index = -1; server->saved_data.pkt_max = 0; server->saved_data.next = NULL; server->type = type; server->next_rule = (get_server_rules) ? "" : NO_SERVER_RULES; server->next_player_info = (get_player_info && type->player_packet) ? 0 : NO_PLAYER_INFO; server->n_player_info = 0; server->players = NULL; server->n_rules = 0; server->rules = NULL; server->last_rule = &server->rules; server->missing_rules = 0; num_servers_total++; } // ipaddr should be network byte-order // port should be host byte-order // NOTE: This will return the first matching server, which is not nessacarily correct // depending on if duplicate ports are allowed struct qserver * find_server_by_address(unsigned int ipaddr, unsigned short port) { struct qserver **hashed; unsigned int hash, i; if (!noserverdups && show_errors) { fprintf(stderr, "error: find_server_by_address while duplicates are allowed, this is unsafe!"); } hash = (ipaddr + port) % ADDRESS_HASH_LENGTH; if (ipaddr == 0) { for (hash = 0; hash < ADDRESS_HASH_LENGTH; hash++) { printf("%3d %d\n", hash, server_hash_len[hash]); } return (NULL); } hashed = server_hash[hash]; for (i = server_hash_len[hash]; i; i--, hashed++) { if (*hashed && ((*hashed)->ipaddr == ipaddr) && ((*hashed)->port == port)) { return (*hashed); } } return (NULL); } void add_server_to_hash(struct qserver *server) { unsigned int hash; hash = (server->ipaddr + server->port) % ADDRESS_HASH_LENGTH; if (server_hash_len[hash] % 16 == 0) { server_hash[hash] = (struct qserver **)realloc(server_hash[hash], sizeof(struct qserver **) * (server_hash_len[hash] + 16)); memset(&server_hash[hash][server_hash_len[hash]], 0, sizeof(struct qserver **) * 16); } server_hash[hash][server_hash_len[hash]] = server; server_hash_len[hash]++; } void remove_server_from_hash(struct qserver *server) { struct qserver **hashed; unsigned int hash, i, ipaddr = server->ipaddr; unsigned short port = server->orig_port; hash = (ipaddr + port) % ADDRESS_HASH_LENGTH; hashed = server_hash[hash]; for (i = server_hash_len[hash]; i; i--, hashed++) { // NOTE: we use direct pointer checks here to prevent issues with duplicate port servers e.g. teamspeak 2 and 3 if (*hashed == server) { *hashed = NULL; break; } } } void free_server_hash() { int i; for (i = 0; i < ADDRESS_HASH_LENGTH; i++) { if (server_hash[i]) { free(server_hash[i]); } } } /* * Functions for binding sockets to Quake servers */ int bind_qserver_post(struct qserver *server) { server->state = STATE_CONNECTED; if (server->type->flags & TF_TCP_CONNECT) { int one = 1; if (-1 == setsockopt(server->fd, IPPROTO_TCP, TCP_NODELAY, (char *)&one, sizeof(one))) { perror("Failed to set TCP no delay"); } } if (server->type->id & MASTER_SERVER) { // Use a large buffer so we dont miss packets int sockbuf = RECV_BUF; if (-1 == setsockopt(server->fd, SOL_SOCKET, SO_RCVBUF, (void *)&sockbuf, sizeof(sockbuf))) { perror("Failed to set socket buffer"); } } #ifndef _WIN32 if (server->fd >= max_connmap) { int old_max = max_connmap; max_connmap = server->fd + 32; connmap = (struct qserver **)realloc(connmap, max_connmap * sizeof(struct qserver *)); memset(&connmap[old_max], 0, (max_connmap - old_max) * sizeof(struct qserver *)); } connmap[server->fd] = server; #endif #ifdef _WIN32 { int i; for (i = 0; i < max_connmap; i++) { if (connmap[i] == NULL) { connmap[i] = server; break; } } if (i >= max_connmap) { printf("could not put server in connmap\n"); } } #endif return (0); } void add_ms_to_timeval(struct timeval *a, unsigned long interval_ms, struct timeval *result) { result->tv_sec = a->tv_sec + (interval_ms / 1000); result->tv_usec = a->tv_usec + ((interval_ms % 1000) * 1000); if (result->tv_usec > 1000000) { result->tv_usec -= 1000000; result->tv_sec++; } } void qserver_sockaddr(struct qserver *server, struct sockaddr_in *addr) { addr->sin_family = AF_INET; addr->sin_port = (no_port_offset || server->flags & TF_NO_PORT_OFFSET) ? htons(server->port) : htons((unsigned short)(server->port + server->type->port_offset)); addr->sin_addr.s_addr = server->ipaddr; memset(&(addr->sin_zero), 0, sizeof(addr->sin_zero)); } int connected_qserver(struct qserver *server, int polling) { struct sockaddr_in addr; char error[50]; int ret; struct timeval tv, now, to; fd_set connect_set; error[0] = '\0'; gettimeofday(&now, NULL); add_ms_to_timeval(&server->packet_time1, retry_interval * server->retry1, &to); if (polling) { // No delay tv.tv_sec = 0; tv.tv_usec = 0; } else { // Wait until the server would timeout tv.tv_sec = to.tv_sec; tv.tv_usec = to.tv_usec; } while (1) { FD_ZERO(&connect_set); FD_SET(server->fd, &connect_set); // NOTE: We may need to check exceptfds here on windows instead of writefds ret = select(server->fd + 1, NULL, &connect_set, NULL, &tv); if (0 == ret) { // Time limit expired if (polling) { // Check if we have run out of time to connect gettimeofday(&now, NULL); if (0 < time_delta(&to, &now)) { // We still have time to connect return (-2); } } qserver_sockaddr(server, &addr); sprintf(error, "connect:%s:%u - timeout", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); server->server_name = TIMEOUT; server->state = STATE_TIMEOUT; goto connect_error; } else if (0 < ret) { // Socket selected for write so either connected or error int sockerr, orig_errno; unsigned int lon = sizeof(int); orig_errno = errno; if (0 != getsockopt(server->fd, SOL_SOCKET, SO_ERROR, (void *)(&sockerr), &lon)) { // Restore the original error errno = orig_errno; goto connect_error; } if (sockerr) { // set the real error errno = sockerr; goto connect_error; } // Connection success break; } else { // select failed if (errno != EINTR) { goto connect_error; } } } return (bind_qserver_post(server)); connect_error: if (STATE_CONNECTING == server->state) { // Default error case if (0 == strlen(error)) { qserver_sockaddr(server, &addr); sprintf(error, "connect: %s:%u", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); } server->server_name = SYSERROR; server->state = STATE_SYS_ERROR; } if (show_errors) { perror(error); } close(server->fd); server->fd = -1; connected--; return (-1); } int bind_qserver2(struct qserver *server, int wait) { struct sockaddr_in addr; static int one = 1; debug(1, "start %p @ %d.%d.%d.%d:%hu\n", server, server->ipaddr & 0xff, (server->ipaddr >> 8) & 0xff, (server->ipaddr >> 16) & 0xff, (server->ipaddr >> 24) & 0xff, server->port ); if (server->type->flags & TF_TCP_CONNECT) { server->fd = socket(AF_INET, SOCK_STREAM, 0); } else { server->fd = socket(AF_INET, SOCK_DGRAM, 0); } if (server->fd == INVALID_SOCKET) { if (sockerr() == EMFILE) { // Per process descriptor table is full - retry server->fd = -1; return (-2); } perror("socket"); server->server_name = SYSERROR; server->state = STATE_SYS_ERROR; return (-1); } addr.sin_family = AF_INET; addr.sin_addr.s_addr = htonl(source_ip); if (server->type->id == Q2_MASTER) { addr.sin_port = htons(26500); } else if (source_port == 0) { addr.sin_port = 0; } else { addr.sin_port = htons(source_port); source_port++; if (source_port > source_port_high) { source_port = source_port_low; } } memset(&(addr.sin_zero), 0, sizeof(addr.sin_zero)); if (bind(server->fd, (struct sockaddr *)&addr, sizeof(struct sockaddr)) == SOCKET_ERROR) { perror("bind"); server->server_name = SYSERROR; server->state = STATE_SYS_ERROR; close(server->fd); server->fd = -1; return (-1); } if (server->flags & FLAG_BROADCAST) { if (-1 == setsockopt(server->fd, SOL_SOCKET, SO_BROADCAST, (char *)&one, sizeof(one))) { perror("Failed to set broadcast"); server->server_name = SYSERROR; server->state = STATE_SYS_ERROR; close(server->fd); server->fd = -1; return (-1); } } // we need nonblocking always. poll on an udp socket would wake // up and recv blocks if a packet with incorrect checksum is // received set_non_blocking(server->fd); if ((server->type->id != Q2_MASTER) && !(server->flags & FLAG_BROADCAST)) { if (server->type->flags & TF_TCP_CONNECT) { // TCP set packet_time1 so it can be used for ping calculations for protocols with an initial response gettimeofday(&server->packet_time1, NULL); } qserver_sockaddr(server, &addr); server->state = STATE_CONNECTING; if (connect(server->fd, (struct sockaddr *)&addr, sizeof(addr)) == SOCKET_ERROR) { if (connection_inprogress()) { int ret; // Ensure we don't detect the same error twice, specifically on a different server clear_socketerror(); if (!wait) { debug(2, "connect:%s:%u - in progress", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); return (-3); } ret = connected_qserver(server, 0); if (0 != ret) { return (ret); } } else { if (show_errors) { char error[50]; sprintf(error, "connect:%s:%u", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); perror(error); } server->server_name = SYSERROR; server->state = STATE_SYS_ERROR; close(server->fd); server->fd = -1; return (-1); } } } return (bind_qserver_post(server)); } int bind_qserver(struct qserver *server) { return (bind_qserver2(server, 1)); } static struct timeval t_lastsend = { 0, 0 }; int bind_sockets() { struct qserver *server, *next_server, *first_server, *last_server; struct timeval now; int rc, retry_count = 0, inprogress = 0; gettimeofday(&now, NULL); if (connected && sendinterval && (time_delta(&now, &t_lastsend) < sendinterval)) { server = NULL; } else if (!waiting_for_masters) { if (last_server_bind == NULL) { last_server_bind = servers; } server = last_server_bind; } else { server = servers; } first_server = server; for ( ; server != NULL && connected < max_simultaneous; ) { // note the next server for use as process_func can free the server next_server = server->next; if ((server->server_name == NULL) && (server->fd == -1)) { if (waiting_for_masters && !server->type->master) { server = next_server; continue; } if ((rc = bind_qserver2(server, syncconnect ? 1 : 0)) == 0) { debug(1, "send %d.%d.%d.%d:%hu\n", server->ipaddr & 0xff, (server->ipaddr >> 8) & 0xff, (server->ipaddr >> 16) & 0xff, (server->ipaddr >> 24) & 0xff, server->port ); gettimeofday(&t_lastsend, NULL); debug(2, "calling status_query_func for %p - connect", server); process_func_ret(server, server->type->status_query_func(server)); connected++; if (!waiting_for_masters) { last_server_bind = server; } break; } else if (rc == -3) { // Connect in progress // We add to increment connected as we need to know the total // amount of connections in progress not just those that have // successfuly completed their connection otherwise we could // blow FD_SETSIZE connected++; inprogress++; } else if ((rc == -2) && (++retry_count > 2)) { return (-2); } else if (-1 == rc) { cleanup_qserver(server, FORCE); } } server = next_server; } // Wait for all connections to succeed or fail last_server = server; while (inprogress) { inprogress = 0; server = first_server; for ( ; server != last_server; ) { next_server = server->next; if (STATE_CONNECTING == server->state) { rc = connected_qserver(server, 1); switch (rc) { case 0: // Connected gettimeofday(&t_lastsend, NULL); debug(2, "calling status_query_func for %p - in progress", server); process_func_ret(server, server->type->status_query_func(server)); // NOTE: connected is already incremented if (!waiting_for_masters) { last_server_bind = server; } break; case -2: // In progress inprogress++; break; case -1: cleanup_qserver(server, FORCE); break; } } server = next_server; } } if ((NULL != last_server) || (!connected && retry_count)) { // Retry later, more to process return (-2); } return (0); } int process_func_ret(struct qserver *server, int ret) { debug(3, "%p, %d", server, ret); switch (ret) { case INPROGRESS: return (ret); case DONE_AUTO: cleanup_qserver(server, NO_FORCE); return (ret); case DONE_FORCE: case SYS_ERROR: case MEM_ERROR: case PKT_ERROR: case ORD_ERROR: case REQ_ERROR: cleanup_qserver(server, FORCE); return (ret); } debug(0, "unhandled return code %d, please report!", ret); return (ret); } /* * Functions for sending packets */ // this is so broken, someone please rewrite the timeout handling void send_packets() { struct qserver *server; struct timeval now; int interval, n_sent = 0, prev_n_sent; unsigned i; debug(3, "processing..."); gettimeofday(&now, NULL); if (!t_lastsend.tv_sec) { // nothing } else if (connected && sendinterval && (time_delta(&now, &t_lastsend) < sendinterval)) { return; } for (i = 0; i < max_connmap; ++i) { server = connmap[i]; if (!server) { continue; } if (server->fd == -1) { debug(0, "invalid entry in connmap\n"); } if (server->type->id & MASTER_SERVER) { interval = master_retry_interval; } else { interval = retry_interval; } debug(2, "server %p, name %s, retry1 %d, next_rule %p, next_player_info %d, num_players %d, n_retries %d", server, server->server_name, server->retry1, server->next_rule, server->next_player_info, server->num_players, n_retries ); prev_n_sent = n_sent; if (server->server_name == NULL) { // We havent seen the server yet? if ((server->retry1 != n_retries) && (time_delta(&now, &server->packet_time1) < (interval * (n_retries - server->retry1 + 1)))) { continue; } if (server->retry1 < 1) { // No more retries cleanup_qserver(server, FORCE); continue; } if ((qserver_get_timeout(server, &now) <= 0) && !(server->type->flags & TF_TCP_CONNECT)) { // Query status debug(2, "calling status_query_func for %p", server); process_func_ret(server, server->type->status_query_func(server)); gettimeofday(&t_lastsend, NULL); n_sent++; continue; } } if (server->next_rule != NO_SERVER_RULES) { // We want server rules if ((server->retry1 != n_retries) && (time_delta(&now, &server->packet_time1) < (interval * (n_retries - server->retry1 + 1)))) { continue; } if (server->retry1 < 1) { // no more retries server->next_rule = NULL; server->missing_rules = 1; cleanup_qserver(server, NO_FORCE); continue; } debug(3, "send_rule_request_packet1"); send_rule_request_packet(server); gettimeofday(&t_lastsend, NULL); n_sent++; } if (server->next_player_info < server->num_players) { // Expecting player details if ((server->retry2 != n_retries) && (time_delta(&now, &server->packet_time2) < (interval * (n_retries - server->retry2 + 1)))) { continue; } if (!server->retry2) { server->next_player_info++; if (server->next_player_info >= server->num_players) { // no more retries cleanup_qserver(server, FORCE); continue; } server->retry2 = n_retries; } send_player_request_packet(server); gettimeofday(&t_lastsend, NULL); n_sent++; } if (prev_n_sent == n_sent) { // we didnt send any additional queries debug(2, "no queries sent: %d %d", time_delta(&now, &server->packet_time1), (interval * (n_retries + 1))); if (server->retry1 < 1) { // no retries left if (time_delta(&now, &server->packet_time1) > (interval * (n_retries + 1))) { cleanup_qserver(server, FORCE); } } else { // decrement as we didnt send any packets server->retry1--; } } } debug(3, "done"); } /* server starts sending data immediately, so we need not do anything */ query_status_t send_bfris_request_packet(struct qserver *server) { return (register_send(server)); } /* First packet for a normal Quake server */ query_status_t send_qserver_request_packet(struct qserver *server) { return (send_packet(server, server->type->status_packet, server->type->status_len)); } /* First packet for a QuakeWorld server */ query_status_t send_qwserver_request_packet(struct qserver *server) { int rc; if (server->flags & FLAG_BROADCAST) { rc = send_broadcast(server, server->type->status_packet, server->type->status_len); } else if (server->server_name == NULL) { rc = send(server->fd, server->type->status_packet, server->type->status_len, 0); } else if ((server->server_name != NULL) && server->type->rule_packet) { rc = send(server->fd, server->type->rule_packet, server->type->rule_len, 0); } else { rc = SOCKET_ERROR; } if (rc == SOCKET_ERROR) { return (send_error(server, rc)); } if ((server->retry1 == n_retries) || server->flags & FLAG_BROADCAST) { gettimeofday(&server->packet_time1, NULL); server->n_requests++; } else if (server->server_name == NULL) { server->n_retries++; } server->retry1--; if (server->server_name == NULL) { server->n_packets++; } server->next_player_info = NO_PLAYER_INFO; // we don't have a player packet return (INPROGRESS); } // First packet for an Unreal Tournament 2003 server query_status_t send_ut2003_request_packet(struct qserver *server) { int ret = send_packet(server, server->type->status_packet, server->type->status_len); server->next_player_info = NO_PLAYER_INFO; return (ret); } // First packet for an Half-Life 2 server query_status_t send_hl2_request_packet(struct qserver *server) { return (send_packet(server, server->type->status_packet, server->type->status_len)); } /* First packet for an Unreal master */ query_status_t send_unrealmaster_request_packet(struct qserver *server) { return (send_packet(server, server->type->status_packet, server->type->status_len)); } static const char *steam_region[] = { "US East Coast", "US West Coast", "South America", "Europe", "Asia", "Australia", "Middle East", "Africa", NULL }; char * build_hlmaster_packet(struct qserver *server, int *len) { static char packet[1600]; char *pkt, *r, *sep = ""; char *gamedir, *map, *flags; int flen; pkt = &packet[0]; memcpy(pkt, server->type->master_packet, *len); if (server->type->flags & TF_MASTER_STEAM) { // default the region to 0xff const char *regionstring = get_param_value(server, "region", NULL); int region = 0xFF; if (regionstring) { char *tmp = NULL; region = strtol(regionstring, &tmp, 10); if (tmp == regionstring) { int i = 0; region = 0xFF; for ( ; steam_region[i]; ++i) { if (!strcmp(regionstring, steam_region[i])) { region = i; break; } } } } *(pkt + 1) = region; } pkt += *len; gamedir = get_param_value(server, "game", NULL); if (gamedir) { pkt += sprintf(pkt, "\\gamedir\\%s", gamedir); } // not valid for steam? map = get_param_value(server, "map", NULL); if (map) { pkt += sprintf(pkt, "\\map\\%s", map); } // steam flags = get_param_value(server, "napp", NULL); if (flags) { pkt += sprintf(pkt, "\\napp\\%s", flags); } // not valid for steam? flags = get_param_value(server, "status", NULL); r = flags; while (flags && sep) { sep = strchr(r, ':'); if (sep) { flen = sep - r; } else { flen = strlen(r); } if (strncmp(r, "notempty", flen) == 0) { pkt += sprintf(pkt, "\\empty\\1"); } else if (strncmp(r, "notfull", flen) == 0) { pkt += sprintf(pkt, "\\full\\1"); } else if (strncmp(r, "dedicated", flen) == 0) { if (server->type->flags & TF_MASTER_STEAM) { pkt += sprintf(pkt, "\\type\\d"); } else { pkt += sprintf(pkt, "\\dedicated\\1"); } } else if (strncmp(r, "linux", flen) == 0) { pkt += sprintf(pkt, "\\linux\\1"); } else if (strncmp(r, "proxy", flen) == 0) { // steam pkt += sprintf(pkt, "\\proxy\\1"); } else if (strncmp(r, "secure", flen) == 0) { // steam pkt += sprintf(pkt, "\\secure\\1"); } r = sep + 1; } // always need null terminator *pkt = 0x00; pkt++; *len = pkt - packet; return (packet); } /* First packet for a QuakeWorld master server */ query_status_t send_qwmaster_request_packet(struct qserver *server) { int rc = 0; server->next_player_info = NO_PLAYER_INFO; if (server->type->id == Q2_MASTER) { struct sockaddr_in addr; addr.sin_family = AF_INET; if (no_port_offset || server->flags & TF_NO_PORT_OFFSET) { addr.sin_port = htons(server->port); } else { addr.sin_port = htons((unsigned short)(server->port + server->type->port_offset)); } addr.sin_addr.s_addr = server->ipaddr; memset(&(addr.sin_zero), 0, sizeof(addr.sin_zero)); rc = sendto(server->fd, server->type->master_packet, server->type->master_len, 0, (struct sockaddr *)&addr, sizeof(addr)); } else { char *packet; int packet_len; char query_buf[4096] = { 0 }; packet = server->type->master_packet; packet_len = server->type->master_len; if (server->type->id == HL_MASTER) { memcpy(server->type->master_packet + 1, server->master_query_tag, 3); if (server->query_arg) { packet_len = server->type->master_len; packet = build_hlmaster_packet(server, &packet_len); } } else if (server->type->flags & TF_MASTER_STEAM) { // region int tag_len = strlen(server->master_query_tag); if (tag_len < 9) { // initial case tag_len = 9; strcpy(server->master_query_tag, "0.0.0.0:0"); } // 1 byte packet id // 1 byte region // ip:port // 1 byte null packet_len = 2 + tag_len + 1; if (server->query_arg) { // build_hlmaster_packet uses server->type->master_packet // as the basis so copy from server->master_query_tag strcpy(server->type->master_packet + 2, server->master_query_tag); packet = build_hlmaster_packet(server, &packet_len); } else { // default region *(packet + 1) = 0xff; memcpy(packet + 2, server->master_query_tag, tag_len); // filter null *(packet + packet_len) = 0x00; packet_len++; } } else if (server->type->flags & TF_QUERY_ARG) { // fill in the master protocol details char *master_protocol = server->query_arg; if (master_protocol == NULL) { master_protocol = server->type->master_protocol; } packet_len = sprintf(query_buf, server->type->master_packet, master_protocol ? master_protocol : "", server->type->master_query ? server->type->master_query : "" ); packet = query_buf; } rc = send(server->fd, packet, packet_len, 0); } if (rc == SOCKET_ERROR) { return (send_error(server, rc)); } if (server->retry1 == n_retries) { gettimeofday(&server->packet_time1, NULL); server->n_requests++; } else { server->n_retries++; } server->retry1--; server->n_packets++; return (INPROGRESS); } query_status_t send_tribes_request_packet(struct qserver *server) { return (send_packet(server, server->type->player_packet, server->type->player_len)); } query_status_t send_tribes2_request_packet(struct qserver *server) { int rc; if (server->flags & FLAG_BROADCAST && (server->server_name == NULL)) { rc = send_broadcast(server, server->type->status_packet, server->type->status_len); } else if (server->server_name == NULL) { rc = send(server->fd, server->type->status_packet, server->type->status_len, 0); } else { rc = send(server->fd, server->type->player_packet, server->type->player_len, 0); } if (rc == SOCKET_ERROR) { return (send_error(server, rc)); } register_send(server); return (rc); } query_status_t send_savage_request_packet(struct qserver *server) { int len; char *pkt; if (get_player_info) { pkt = server->type->player_packet; len = server->type->player_len; } else { pkt = server->type->status_packet; len = server->type->status_len; } return (send_packet(server, pkt, len)); } query_status_t send_farcry_request_packet(struct qserver *server) { int len; char *pkt; if (get_player_info) { pkt = server->type->player_packet; len = server->type->player_len; } else { pkt = server->type->status_packet; len = server->type->status_len; } return (send_packet(server, pkt, len)); } query_status_t send_tribes2master_request_packet(struct qserver *server) { int rc; unsigned char packet[1600], *pkt; unsigned int len, min_players, max_players, region_mask = 0; unsigned int build_version, max_bots, min_cpu, status; char *game, *mission, *buddies; static char *region_list[] = { "naeast", "nawest", "sa", "aus", "asia", "eur", NULL }; static char *status_list[] = { "dedicated", "nopassword", "linux" }; if (strcmp(get_param_value(server, "query", ""), "types") == 0) { rc = send(server->fd, tribes2_game_types_request, sizeof(tribes2_game_types_request), 0); goto send_done; } memcpy(packet, server->type->master_packet, server->type->master_len); pkt = packet + 7; game = get_param_value(server, "game", "any"); len = strlen(game); if (len > 255) { len = 255; } *pkt++ = len; memcpy(pkt, game, len); pkt += len; mission = get_param_value(server, "mission", "any"); len = strlen(mission); if (len > 255) { len = 255; } *pkt++ = len; memcpy(pkt, mission, len); pkt += len; min_players = get_param_ui_value(server, "minplayers", 0); max_players = get_param_ui_value(server, "maxplayers", 255); *pkt++ = min_players; *pkt++ = max_players; region_mask = get_param_ui_value(server, "regions", 0xffffffff); if (region_mask == 0) { char *regions = get_param_value(server, "regions", ""); char *r = regions; char **list, *sep; do { sep = strchr(r, ':'); if (sep) { len = sep - r; } else { len = strlen(r); } for (list = region_list; *list; list++) { if (strncasecmp(r, *list, len) == 0) { break; } } if (*list) { region_mask |= 1 << (list - region_list); } r = sep + 1; } while (sep); } if (little_endian) { memcpy(pkt, ®ion_mask, 4); } else { pkt[0] = region_mask & 0xff; pkt[1] = (region_mask >> 8) & 0xff; pkt[2] = (region_mask >> 16) & 0xff; pkt[3] = (region_mask >> 24) & 0xff; } pkt += 4; build_version = get_param_ui_value(server, "build", 0); /* * if ( build_version && build_version < 22337) { * packet[1]= 0; * build_version= 0; * } */ if (little_endian) { memcpy(pkt, &build_version, 4); } else { pkt[0] = build_version & 0xff; pkt[1] = (build_version >> 8) & 0xff; pkt[2] = (build_version >> 16) & 0xff; pkt[3] = (build_version >> 24) & 0xff; } pkt += 4; status = get_param_ui_value(server, "status", -1); if (status == 0) { char *flags = get_param_value(server, "status", ""); char *r = flags; char **list, *sep; do { sep = strchr(r, ':'); if (sep) { len = sep - r; } else { len = strlen(r); } for (list = status_list; *list; list++) { if (strncasecmp(r, *list, len) == 0) { break; } } if (*list) { status |= 1 << (list - status_list); } r = sep + 1; } while (sep); } else if (status == -1) { status = 0; } *pkt++ = status; max_bots = get_param_ui_value(server, "maxbots", 255); *pkt++ = max_bots; min_cpu = get_param_ui_value(server, "mincpu", 0); if (little_endian) { memcpy(pkt, &min_cpu, 2); } else { pkt[0] = min_cpu & 0xff; pkt[1] = (min_cpu >> 8) & 0xff; } pkt += 2; buddies = get_param_value(server, "buddies", NULL); if (buddies) { char *b = buddies, *sep; unsigned int buddy, n_buddies = 0; unsigned char *n_loc = pkt++; do { sep = strchr(b, ':'); if (sscanf(b, "%u", &buddy)) { n_buddies++; if (little_endian) { memcpy(pkt, &buddy, 4); } else { pkt[0] = buddy & 0xff; pkt[1] = (buddy >> 8) & 0xff; pkt[2] = (buddy >> 16) & 0xff; pkt[3] = (buddy >> 24) & 0xff; } pkt += 4; } b = sep + 1; } while (sep && n_buddies < 255); *n_loc = n_buddies; } else { *pkt++ = 0; } rc = send(server->fd, (char *)packet, pkt - packet, 0); send_done: if (rc == SOCKET_ERROR) { return (send_error(server, rc)); } if (server->retry1 == n_retries) { gettimeofday(&server->packet_time1, NULL); server->n_requests++; } else { server->n_retries++; } server->retry1--; server->n_packets++; return (INPROGRESS); } static struct _gamespy_query_map { char *qstat_type; char *gamespy_type; } gamespy_query_map[] = { { "qws", "quakeworld" }, { "q2s", "quake2" }, { "q3s", "quake3" }, { "tbs", "tribes" }, { "uns", "ut" }, { "sgs", "shogo" }, { "hls", "halflife" }, { "kps", "kingpin" }, { "hrs", "heretic2" }, { "sfs", "sofretail" }, { NULL, NULL } }; query_status_t send_gamespy_master_request(struct qserver *server) { int rc, i; char request[1024]; if (server->n_packets) { return (DONE_AUTO); } // WARNING: This only works for masters which don't check the value of validate // e.g. unreal.epicgames.com // // What we should be doing here is recieving the challenge from the master // processing the secure key and then using that as the value for validate. // // The details of this can be seen in gslist: // http://aluigi.altervista.org/papers.htm#gslist rc = send(server->fd, server->type->master_packet, server->type->master_len, 0); if (rc != server->type->master_len) { return (send_error(server, rc)); } strcpy(request, server->type->status_packet); for (i = 0; gamespy_query_map[i].qstat_type; i++) { if (strcasecmp(server->query_arg, gamespy_query_map[i].qstat_type) == 0) { break; } } if (gamespy_query_map[i].gamespy_type == NULL) { strcat(request, server->query_arg); } else { strcat(request, gamespy_query_map[i].gamespy_type); } strcat(request, "\\final\\"); assert(strlen(request) < sizeof(request)); rc = send(server->fd, request, strlen(request), 0); if (rc != strlen(request)) { return (send_error(server, rc)); } if (server->retry1 == n_retries) { gettimeofday(&server->packet_time1, NULL); server->n_requests++; } server->n_packets++; return (INPROGRESS); } query_status_t send_rule_request_packet(struct qserver *server) { int rc, len; debug(3, "send_rule_request_packet: %p", server); /* Server created via broadcast, so bind it */ if (server->fd == -1) { if (bind_qserver(server) < 0) { goto setup_retry; } } if (server->type->rule_query_func && (server->type->rule_query_func != send_rule_request_packet)) { return (server->type->rule_query_func(server)); } if (server->type->id == Q_SERVER) { strcpy((char *)q_rule.data, server->next_rule); len = Q_HEADER_LEN + strlen((char *)q_rule.data) + 1; q_rule.length = htons((short)len); } else { len = server->type->rule_len; } rc = send(server->fd, (const char *)server->type->rule_packet, len, 0); if (rc == SOCKET_ERROR) { return (send_error(server, rc)); } setup_retry: if (server->retry1 == n_retries) { gettimeofday(&server->packet_time1, NULL); server->n_requests++; } else if (server->server_name == NULL) { server->n_retries++; } server->retry1--; if (server->server_name == NULL) { server->n_packets++; } return (DONE_AUTO); } query_status_t send_player_request_packet(struct qserver *server) { int rc; debug(3, "send_player_request_packet %p", server); if (!server->type->player_packet) { return (0); } /* Server created via broadcast, so bind it */ if (server->fd == -1) { if (bind_qserver(server) < 0) { goto setup_retry; } } if (server->type->player_query_func && (server->type->player_query_func != send_player_request_packet)) { return (server->type->player_query_func(server)); } if (!server->type->player_packet) { debug(0, "error: server %p has no player_packet", server); return (0); } if (server->type->id == Q_SERVER) { q_player.data[0] = server->next_player_info; } rc = send(server->fd, (const char *)server->type->player_packet, server->type->player_len, 0); if (rc == SOCKET_ERROR) { return (send_error(server, rc)); } setup_retry: if (server->retry2 == n_retries) { gettimeofday(&server->packet_time2, NULL); server->n_requests++; } else { server->n_retries++; } server->retry2--; server->n_packets++; return (1); } void qserver_disconnect(struct qserver *server) { #ifdef _WIN32 int i; #endif if (server->fd != -1) { close(server->fd); #ifndef _WIN32 connmap[server->fd] = NULL; #else for (i = 0; i < max_connmap; i++) { if (connmap[i] == server) { connmap[i] = NULL; break; } } #endif server->fd = -1; connected--; } } /* Functions for figuring timeouts and when to give up * Returns 1 if the query is done (server may be freed) and 0 if not. */ int cleanup_qserver(struct qserver *server, int force) { int close_it = force; debug(3, "cleanup_qserver %p, %d", server, force); if (server->server_name == NULL) { debug(3, "server has no name, forcing close"); close_it = 1; if (server->type->id & MASTER_SERVER && (server->master_pkt != NULL)) { server->server_name = MASTER; } else { server->server_name = TIMEOUT; num_servers_timed_out++; } } else if (server->type->flags & TF_SINGLE_QUERY) { debug(3, "TF_SINGLE_QUERY, forcing close"); close_it = 1; } else if ((server->next_rule == NO_SERVER_RULES) && (server->next_player_info >= server->num_players)) { debug(3, "no server rules and next_player_info >= num_players, forcing close"); close_it = 1; } debug(3, "close_it %d", close_it); if (close_it) { if (server->saved_data.data) { SavedData *sdata = server->saved_data.next; free(server->saved_data.data); server->saved_data.data = NULL; while (sdata != NULL) { SavedData *next; free(sdata->data); next = sdata->next; free(sdata); sdata = next; } server->saved_data.next = NULL; } qserver_disconnect(server); if (server->server_name != TIMEOUT) { num_servers_returned++; if (server->server_name != DOWN) { num_players_total += server->num_players; max_players_total += server->max_players; } } if ((server->server_name == TIMEOUT) || (server->server_name == DOWN)) { server->ping_total = 999999; } if (server->type->master) { waiting_for_masters--; if (waiting_for_masters == 0) { add_servers_from_masters(); } } if (!server_sort) { display_server(server); } return (1); } return (0); } /** must be called only on connected servers * @returns time in ms until server needs timeout handling. timeout handling is needed if <= zero */ static int qserver_get_timeout(struct qserver *server, struct timeval *now) { int diff, diff1, diff2, interval; if (server->type->id & MASTER_SERVER) { interval = master_retry_interval; } else { interval = retry_interval; } diff2 = 0xffff; diff1 = interval * (n_retries - server->retry1 + 1) - time_delta(now, &server->packet_time1); if (server->next_player_info < server->num_players) { diff2 = interval * (n_retries - server->retry2 + 1) - time_delta(now, &server->packet_time2); } debug(2, "timeout for %p is diff1 %d diff2 %d", server, diff1, diff2); diff = (diff1 < diff2) ? diff1 : diff2; return (diff); } void get_next_timeout(struct timeval *timeout) { struct qserver *server = servers; struct timeval now; int diff, smallest = retry_interval + master_retry_interval; int bind_count = 0; if (first_server_bind == NULL) { first_server_bind = servers; } server = first_server_bind; for ( ; server != NULL && server->fd == -1; server = server->next) { } /* if there are unconnected servers and slots left we retry in 10ms */ if ((server == NULL) || ((num_servers > connected) && (connected < max_simultaneous))) { timeout->tv_sec = 0; timeout->tv_usec = 10 * 1000; return; } first_server_bind = server; gettimeofday(&now, NULL); for ( ; server != NULL && bind_count < connected; server = server->next) { if (server->fd == -1) { continue; } diff = qserver_get_timeout(server, &now); if (diff < smallest) { smallest = diff; } bind_count++; } if (smallest < 10) { smallest = 10; } timeout->tv_sec = smallest / 1000; timeout->tv_usec = (smallest % 1000) * 1000; } #ifdef USE_SELECT static fd_set select_read_fds; static int select_maxfd; static int select_cursor; int set_fds(fd_set *fds) { int maxfd = -1, i; for (i = 0; i < max_connmap; i++) { if (connmap[i] != NULL) { FD_SET(connmap[i]->fd, fds); if (connmap[i]->fd > maxfd) { maxfd = connmap[i]->fd; } } } return (maxfd); } void set_file_descriptors() { FD_ZERO(&select_read_fds); select_maxfd = set_fds(&select_read_fds); } int wait_for_file_descriptors(struct timeval *timeout) { select_cursor = 0; return (select(select_maxfd + 1, &select_read_fds, NULL, NULL, timeout)); } struct qserver * get_next_ready_server() { while (select_cursor < max_connmap && (connmap[select_cursor] == NULL || !FD_ISSET(connmap[select_cursor]->fd, &select_read_fds))) { select_cursor++; } if (select_cursor >= max_connmap) { return (NULL); } return (connmap[select_cursor++]); } int wait_for_timeout(unsigned int ms) { struct timeval timeout; timeout.tv_sec = ms / 1000; timeout.tv_usec = (ms % 1000) * 1000; return (select(0, 0, NULL, NULL, &timeout)); } #endif /* USE_SELECT */ #ifdef USE_POLL static struct pollfd *pollfds; static int n_pollfds; static int max_pollfds = 0; static int poll_cursor; void set_file_descriptors() { struct pollfd *p; int i; if (max_connmap > max_pollfds) { max_pollfds = max_connmap; pollfds = (struct pollfd *)realloc(pollfds, max_pollfds * sizeof(struct pollfd)); } p = pollfds; for (i = 0; i < max_connmap; i++) { if (connmap[i] != NULL) { p->fd = connmap[i]->fd; p->events = POLLIN; p->revents = 0; p++; } } n_pollfds = p - pollfds; } int wait_for_file_descriptors(struct timeval *timeout) { poll_cursor = 0; return (poll(pollfds, n_pollfds, timeout->tv_sec * 1000 + timeout->tv_usec / 1000)); } struct qserver * get_next_ready_server() { for ( ; poll_cursor < n_pollfds; poll_cursor++) { if (pollfds[poll_cursor].revents) { break; } } if (poll_cursor >= n_pollfds) { return (NULL); } return (connmap[pollfds[poll_cursor++].fd]); } int wait_for_timeout(unsigned int ms) { return (poll(0, 0, ms)); } #endif /* USE_POLL */ void free_server(struct qserver *server) { struct player *player, *next_player; struct rule *rule, *next_rule; /* remove from servers list */ if (server == servers) { servers = server->next; if (servers) { servers->prev = NULL; } } if ((void *)&server->next == (void *)last_server) { if (server->prev) { last_server = &server->prev->next; } else { last_server = &servers; } } if (server == first_server_bind) { first_server_bind = server->next; } if (server == last_server_bind) { last_server_bind = server->next; } if (server->prev) { server->prev->next = server->next; } if (server->next) { server->next->prev = server->prev; } /* remove from server hash table */ remove_server_from_hash(server); /* free all the data */ for (player = server->players; player; player = next_player) { next_player = player->next; free_player(player); } for (rule = server->rules; rule; rule = next_rule) { next_rule = rule->next; free_rule(rule); } if (server->arg) { free(server->arg); } if (server->host_name) { free(server->host_name); } if (server->error) { free(server->error); } if (server->address) { free(server->address); } if (server->map_name) { free(server->map_name); } if (!(server->flags & FLAG_DO_NOT_FREE_GAME) && server->game) { free(server->game); } if (server->master_pkt) { free(server->master_pkt); } if (server->query_arg) { free(server->query_arg); } if (server->challenge_string) { free(server->challenge_string); } /* These fields are never malloc'd: outfilename */ if ( (server->server_name != NULL) && (server->server_name != DOWN) && (server->server_name != HOSTNOTFOUND) && (server->server_name != SYSERROR) && (server->server_name != MASTER) && (server->server_name != SERVERERROR) && (server->server_name != TIMEOUT) && (server->server_name != GAMESPY_MASTER_NAME) && (server->server_name != BFRIS_SERVER_NAME) ) { free(server->server_name); } /* * params ... * saved_data ... */ free(server); --num_servers; } void free_player(struct player *player) { if (player->name) { free(player->name); } if (!(player->flags & PLAYER_FLAG_DO_NOT_FREE_TEAM) && player->team_name) { free(player->team_name); } if (player->address) { free(player->address); } if (player->tribe_tag) { free(player->tribe_tag); } if (player->skin) { free(player->skin); } if (player->mesh) { free(player->mesh); } if (player->face) { free(player->face); } free(player); } void free_rule(struct rule *rule) { if (rule->name) { free(rule->name); } if (rule->value) { free(rule->value); } free(rule); } /* Functions for handling response packets */ /* Packet from normal Quake server */ query_status_t deal_with_q_packet(struct qserver *server, char *rawpkt, int pktlen) { struct q_packet *pkt = (struct q_packet *)rawpkt; int rc; debug(2, "deal_with_q_packet %p, %d", server, pktlen); if (ntohs(pkt->length) != pktlen) { fprintf(stderr, "%s Ignoring bogus packet; length %d != %d\n", server->arg, ntohs(pkt->length), pktlen); return (PKT_ERROR); } rawpkt[pktlen] = '\0'; switch (pkt->op_code) { case Q_CCREP_ACCEPT: case Q_CCREP_REJECT: return (0); case Q_CCREP_SERVER_INFO: server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); rc = server_info_packet(server, pkt, pktlen - Q_HEADER_LEN); break; case Q_CCREP_PLAYER_INFO: server->ping_total += time_delta(&packet_recv_time, &server->packet_time2); rc = player_info_packet(server, pkt, pktlen - Q_HEADER_LEN); break; case Q_CCREP_RULE_INFO: server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); rc = rule_info_packet(server, pkt, pktlen - Q_HEADER_LEN); break; case Q_CCREQ_CONNECT: case Q_CCREQ_SERVER_INFO: case Q_CCREQ_PLAYER_INFO: case Q_CCREQ_RULE_INFO: default: return (0); } if (SOCKET_ERROR == rc) { fprintf(stderr, "%s error on packet opcode %x\n", server->arg, (int)pkt->op_code); } return (rc); } /* Packet from QuakeWorld server */ query_status_t deal_with_qw_packet(struct qserver *server, char *rawpkt, int pktlen) { debug(2, "deal_with_qw_packet %p, %d", server, pktlen); if (server->server_name == NULL) { server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); } if ((((rawpkt[0] != '\377') && (rawpkt[0] != '\376')) || (rawpkt[1] != '\377') || (rawpkt[2] != '\377') || (rawpkt[3] != '\377')) && show_errors) { unsigned int ipaddr = ntohl(server->ipaddr); fprintf(stderr, "Odd packet from server %d.%d.%d.%d:%hu, processing ...\n", (ipaddr >> 24) & 0xff, (ipaddr >> 16) & 0xff, (ipaddr >> 8) & 0xff, ipaddr & 0xff, ntohs(server->port) ); print_packet(server, rawpkt, pktlen); } rawpkt[pktlen] = '\0'; if (rawpkt[4] == 'n') { if (server->type->id != QW_SERVER) { server->type = find_server_type_id(QW_SERVER); } return (deal_with_q1qw_packet(server, rawpkt, pktlen)); } else if ((rawpkt[4] == '\377') && (rawpkt[5] == 'n')) { if (server->type->id != HW_SERVER) { server->type = find_server_type_id(HW_SERVER); } return (deal_with_q1qw_packet(server, rawpkt, pktlen)); } else if (strncmp(&rawpkt[4], "print\n\\", 7) == 0) { return (deal_with_q2_packet(server, rawpkt + 10, pktlen - 10)); } else if (strncmp(&rawpkt[4], "print\n", 6) == 0) { /* work-around for occasional bug in Quake II status packets */ char *c, *p; p = c = &rawpkt[10]; while (*p != '\\' && (c = strchr(p, '\n'))) { p = c + 1; } if ((*p == '\\') && (c != NULL)) { return (deal_with_q2_packet(server, p, pktlen - (p - rawpkt))); } } else if ((strncmp(&rawpkt[4], "infoResponse", 12) == 0) || ((rawpkt[4] == '\001') && (strncmp(&rawpkt[5], "infoResponse", 12) == 0))) { /* quake3 info response */ int ret; if (rawpkt[4] == '\001') { rawpkt++; pktlen--; } rawpkt += 12; pktlen -= 12; for ( ; pktlen && *rawpkt != '\\'; pktlen--, rawpkt++) { } if (!pktlen) { return (INPROGRESS); } if (rawpkt[pktlen - 1] == '"') { rawpkt[pktlen - 1] = '\0'; pktlen--; } if (get_player_info || get_server_rules) { server->next_rule = ""; } ret = deal_with_q2_packet(server, rawpkt, pktlen); if ((DONE_AUTO == ret) && (get_player_info || get_server_rules)) { debug(3, "send_rule_request_packet2"); send_rule_request_packet(server); server->retry1 = n_retries - 1; return (INPROGRESS); } return (ret); } else if ((strncmp(&rawpkt[4], "statusResponse\n", 15) == 0) || ((rawpkt[4] == '\001') && (strncmp(&rawpkt[5], "statusResponse\n", 15) == 0))) { /* quake3 status response */ server->next_rule = NO_SERVER_RULES; server->retry1 = 0; if (rawpkt[4] == '\001') { rawpkt++; pktlen--; } server->flags |= CHECK_DUPLICATE_RULES; return (deal_with_q2_packet(server, rawpkt + 19, pktlen - 19)); } else if (strncmp(&rawpkt[4], "infostringresponse", 19) == 0) { return (deal_with_q2_packet(server, rawpkt + 23, pktlen - 23)); } if (show_errors) { unsigned int ipaddr = ntohl(server->ipaddr); fprintf(stderr, "Odd packet from server %d.%d.%d.%d:%hu, ignoring ...\n", (ipaddr >> 24) & 0xff, (ipaddr >> 16) & 0xff, (ipaddr >> 8) & 0xff, ipaddr & 0xff, ntohs(server->port) ); print_packet(server, rawpkt, pktlen); return (PKT_ERROR); } return (DONE_AUTO); } query_status_t deal_with_q1qw_packet(struct qserver *server, char *rawpkt, int pktlen) { char *key, *value, *end, *users; struct player *player = NULL, **last_player = &server->players; int len, rc, complete = 0; int number, frags, connect_time, ping; char *pkt = &rawpkt[5]; debug(2, "deal_with_q1qw_packet %p, %d", server, pktlen); if (server->type->id == HW_SERVER) { pkt = &rawpkt[6]; } *(users = strchr(pkt, '\n')) = '\0'; while (*pkt && pkt - rawpkt < pktlen) { if (*pkt == '\\') { pkt++; end = strchr(pkt, '\\'); if (end == NULL) { break; } *end = '\0'; key = pkt; pkt += strlen(pkt) + 1; end = strchr(pkt, '\\'); if (end == NULL) { end = users; } value = (char *)malloc(end - pkt + 1); memcpy(value, pkt, end - pkt); value[end - pkt] = '\0'; pkt = end; if (strcmp(key, "hostname") == 0) { server->server_name = value; } else if (strcmp(key, "map") == 0) { server->map_name = value; } else if (strcmp(key, "maxclients") == 0) { server->max_players = atoi(value); free(value); } else if (strcmp(key, "maxspectators") == 0) { server->max_spectators = atoi(value); free(value); } else if (get_server_rules || (strncmp(key, "*game", 5) == 0)) { add_rule(server, key, value, NO_VALUE_COPY); if (strcmp(key, "*gamedir") == 0) { server->game = value; server->flags |= FLAG_DO_NOT_FREE_GAME; } } } else { pkt++; } complete = 1; } *pkt = '\n'; while (*pkt && pkt - rawpkt < pktlen) { if (*pkt == '\n') { pkt++; if ((pkt - rawpkt >= pktlen) || (*pkt == '\0')) { break; } rc = sscanf(pkt, "%d %d %d %d %n", &number, &frags, &connect_time, &ping, &len); if (rc != 4) { char *nl; /* assume it's an error packet */ server->error = (char *)malloc(pktlen + 1); nl = strchr(pkt, '\n'); if (nl != NULL) { strncpy(server->error, pkt, nl - pkt); server->error[nl - pkt] = '\0'; } else { strcpy(server->error, pkt); } server->server_name = SERVERERROR; complete = 1; break; } if (get_player_info) { player = (struct player *)calloc(1, sizeof(struct player)); player->number = number; player->frags = frags; player->connect_time = connect_time * 60; player->ping = ping > 0 ? ping : -ping; } else { player = NULL; } pkt += len; if (*pkt != '"') { break; } pkt += ping > 0 ? 1 : 4;// if 4 then no "\s\" in spectators name // protocol "under construction" end = strchr(pkt, '"'); if (end == NULL) { break; } if (player != NULL) { player->name = (char *)malloc(end - pkt + 1); memcpy(player->name, pkt, end - pkt); player->name[end - pkt] = '\0'; } pkt = end + 2; if (*pkt != '"') { break; } pkt++; end = strchr(pkt, '"'); if (end == NULL) { break; } if (player != NULL) { player->skin = (char *)malloc(end - pkt + 1); memcpy(player->skin, pkt, end - pkt); player->skin[end - pkt] = '\0'; } pkt = end + 2; if (player != NULL) { sscanf(pkt, "%d %d%n", &player->shirt_color, &player->pants_color, &len); *last_player = player; last_player = &player->next; } else { sscanf(pkt, "%*d %*d%n", &len); } pkt += len; if ((pkt + 3 < rawpkt + pktlen) && (*pkt == ' ')) { // mvdsv is at last rev 377, 23.06.2006 pkt++; if (*pkt != '"') { break; } pkt++; end = strchr(pkt, '"'); if (end == NULL) { break; } if (player != NULL) { player->team_name = (char *)malloc(end - pkt + 1); memcpy(player->team_name, pkt, end - pkt); player->team_name[end - pkt] = '\0'; } pkt = end + 1; } if (ping > 0) { server->num_players++; } else { server->num_spectators++; } } else { pkt++; } complete = 1; } if (!complete) { if ((rawpkt[4] != 'n') || (rawpkt[5] != '\0')) { fprintf(stderr, "Odd packet from QW server %d.%d.%d.%d:%hu ...\n", (server->ipaddr >> 24) & 0xff, (server->ipaddr >> 16) & 0xff, (server->ipaddr >> 8) & 0xff, server->ipaddr & 0xff, ntohs(server->port) ); print_packet(server, rawpkt, pktlen); } } else if (server->server_name == NULL) { server->server_name = strdup(""); } return (DONE_AUTO); } query_status_t deal_with_q2_packet(struct qserver *server, char *rawpkt, int pktlen) { char *key, *value, *end; struct player *player = NULL; struct player **last_player = &server->players; int len, rc, complete = 0; int frags = 0, ping = 0, num_players = 0; char *pkt = rawpkt; debug(2, "deal_with_q2_packet %p, %d", server, pktlen); while (*pkt && pkt - rawpkt < pktlen) { // we have variable, value pairs separated by slash if (*pkt == '\\') { pkt++; if ((*pkt == '\n') && (server->type->id == SOF_SERVER)) { goto player_info; } // Find the key end = strchr(pkt, '\\'); if (NULL == end) { break; } *end = '\0'; key = pkt; pkt += strlen(key) + 1; // Find the value end = strpbrk(pkt, "\\\n"); if (NULL == end) { // Last value end = rawpkt + pktlen; } // Make a copy of the value value = (char *)malloc(end - pkt + 1); memcpy(value, pkt, end - pkt); value[end - pkt] = '\0'; pkt = end; debug(3, "%s = %s", key, value); if ((server->server_name == NULL) && ((strcmp(key, "hostname") == 0) || (strcmp(key, "sv_hostname") == 0))) { // Server name server->server_name = value; } else if ((strcmp(key, "mapname") == 0) || (strcmp(key, "map_name") == 0) || ((strcmp(key, "map") == 0) && (server->map_name == NULL))) { // Map name if (NULL != server->map_name) { free(server->map_name); } server->map_name = value; } else if ((strcmp(key, "maxclients") == 0) || (strcmp(key, "sv_maxclients") == 0) || (strcmp(key, "sv_max_clients") == 0) || (strcmp(key, "max") == 0)) { // Max Players server->max_players = atoi(value); // Note: COD 4 infoResponse returns max as sv_maxclients - sv_privateClients where as statusResponse returns the true max // MOHAA Q3 protocol max players is always 0 if (0 == server->max_players) { server->max_players = -1; } free(value); } else if ((strcmp(key, "clients") == 0) || (strcmp(key, "players") == 0)) { // Num Players server->num_players = atoi(value); free(value); } else if ((server->server_name == NULL) && (strcmp(key, "pure") == 0)) { add_rule(server, key, value, NO_VALUE_COPY); } else if (get_server_rules || (strncmp(key, "game", 4) == 0)) { int dofree = 0; int flags = (server->flags & CHECK_DUPLICATE_RULES) ? CHECK_DUPLICATE_RULES | NO_VALUE_COPY : NO_VALUE_COPY; if (add_rule(server, key, value, flags) == NULL) { // duplicate, so free value dofree = 1; } if ((server->game == NULL) && (strcmp(key, server->type->game_rule) == 0)) { server->game = value; if (0 == dofree) { server->flags |= FLAG_DO_NOT_FREE_GAME; } } else if (1 == dofree) { free(value); } } else { free(value); } } else if (*pkt == '\n') { player_info: debug(3, "player info"); pkt++; if (*pkt == '\0') { break; } if (0 == strncmp(pkt, "\\challenge\\", 11)) { // qfusion // This doesnt support getstatus looking at warsow source: // server/sv_main.c: SV_ConnectionlessPacket server->next_rule = NO_SERVER_RULES; debug(3, "no more server rules"); break; } // Detect if we have a leading float or int? rc = sscanf(pkt, "%d.%d %n", &frags, &ping, &len); ping = 0; // Just a temp variable so reset. if (rc == 2) { // Xonotic in CA mode shows damage (float) instead of frags (int) // 1.0 == 100 dmg. float frags_f; if ((rc = sscanf(pkt, "%f %n", &frags_f, &len)) == 1) { frags = (int)(frags_f*100); } } else { // Just an int. rc = sscanf(pkt, "%d %n", &frags, &len); } if ((rc == 1) && (pkt[len] != '"')) { pkt += len; rc = sscanf(pkt, "%d %n", &ping, &len); } else if (rc == 1) { /* MOHAA Q3 protocol only provides player ping */ ping = frags; frags = 0; } if (rc != 1) { char *nl; /* assume it's an error packet */ server->error = (char *)malloc(pktlen + 1); nl = strchr(pkt, '\n'); if (nl != NULL) { strncpy(server->error, pkt, nl - pkt); } else { strcpy(server->error, pkt); } server->server_name = SERVERERROR; complete = 1; break; } if (get_player_info) { player = (struct player *)calloc(1, sizeof(struct player)); player->number = 0; player->connect_time = -1; player->frags = frags; player->ping = ping; } else { player = NULL; } pkt += len; if (isdigit((unsigned char)*pkt)) { /* probably an SOF2 1.01 server, includes team # */ int team; rc = sscanf(pkt, "%d %n", &team, &len); if (rc == 1) { pkt += len; if (player) { player->team = team; server->flags |= FLAG_PLAYER_TEAMS; } } } if (*pkt != '"') { break; } pkt++; end = strchr(pkt, '"'); if (end == NULL) { break; } if (player != NULL) { player->name = (char *)malloc(end - pkt + 1); memcpy(player->name, pkt, end - pkt); player->name[end - pkt] = '\0'; } pkt = end + 1; //WarSoW team number if (*pkt != '\n') { int team; rc = sscanf(pkt, "%d%n", &team, &len); if (rc == 1) { pkt += len; if (player) { player->team = team; server->flags |= FLAG_PLAYER_TEAMS; } } } if (player != NULL) { player->skin = NULL; player->shirt_color = -1; player->pants_color = -1; *last_player = player; last_player = &player->next; } num_players++; } else { pkt++; } complete = 1; } if ((server->num_players == 0) || (num_players > server->num_players)) { server->num_players = num_players; } if (!complete) { return (PKT_ERROR); } else if (server->server_name == NULL) { server->server_name = strdup(""); } return (DONE_AUTO); } int ack_descent3master_packet(struct qserver *server, char *curtok) { int rc; char packet[0x1e]; memcpy(packet, descent3_masterquery, 0x1a); packet[1] = 0x1d; packet[0x16] = 1; memcpy(packet + 0x1a, curtok, 4); rc = send(server->fd, packet, sizeof(packet), 0); if (rc == SOCKET_ERROR) { return (send_error(server, rc)); } return (rc); } /* Packet from Descent3 master server (PXO) */ query_status_t deal_with_descent3master_packet(struct qserver *server, char *rawpkt, int pktlen) { int i = 0, lastpacket = 0; char *names = rawpkt + 0x1f; char *ips = rawpkt + 0x29f; char *ports = rawpkt + 0x2ef; debug(2, "deal_with_descent3master_packet %p, %d", server, pktlen); while (i < 20) { if (*names) { char *c; server->master_pkt_len += 6; server->master_pkt = (char *)realloc(server->master_pkt, server->master_pkt_len); c = server->master_pkt + server->master_pkt_len - 6; memcpy(c, ips, 4); memcpy(c + 4, ports, 2); } else if (i > 0) { lastpacket = 1; } names += 0x20; ips += 4; ports += 2; i++; } ack_descent3master_packet(server, rawpkt + 0x1a); server->n_servers = server->master_pkt_len / 6; server->next_player_info = -1; server->retry1 = 0; if (lastpacket) { return (DONE_AUTO); } return (INPROGRESS); } /* Packet from QuakeWorld master server */ query_status_t deal_with_qwmaster_packet(struct qserver *server, char *rawpkt, int pktlen) { int ret = 0; debug(2, "deal_with_qwmaster_packet %p, %d", server, pktlen); server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); if (rawpkt[0] == QW_NACK) { server->error = strdup(&rawpkt[2]); server->server_name = SERVERERROR; return (PKT_ERROR); } if (*((unsigned int *)rawpkt) == 0xffffffff) { rawpkt += 4;/* QW 1.5 */ pktlen -= 4; if ((rawpkt[0] == '\377') && (rawpkt[1] == QW_SERVERS)) { rawpkt++; /* hwmaster */ pktlen--; } } if ((rawpkt[0] == QW_SERVERS) && (rawpkt[1] == QW_NEWLINE)) { rawpkt += 2; pktlen -= 2; } else if ((rawpkt[0] == HL_SERVERS) && (rawpkt[1] == 0x0d)) { // 2 byte id + 4 byte sequence memcpy(server->master_query_tag, rawpkt + 2, 3); rawpkt += 6; pktlen -= 6; } else if ((rawpkt[0] == HL_SERVERS) && (rawpkt[1] == 0x0a)) { // no sequence id for steam // instead we use the ip:port of the last recieved server struct in_addr *sin_addr = (struct in_addr *)(rawpkt + pktlen - 6); char *ip = inet_ntoa(*sin_addr); unsigned short port = htons(*((unsigned short *)(rawpkt + pktlen - 2))); //fprintf( stderr, "NEXT IP=%s:%u\n", ip, port ); sprintf(server->master_query_tag, "%s:%u", ip, port); // skip over the 2 byte id rawpkt += 2; pktlen -= 2; } else if (strncmp(rawpkt, "servers", 7) == 0) { rawpkt += 8; pktlen -= 8; } else if (strncmp(rawpkt, "getserversResponse", 18) == 0) { rawpkt += 18; pktlen -= 18; for ( ; *rawpkt != '\\' && pktlen; pktlen--, rawpkt++) { } if (!pktlen) { return (1); } rawpkt++; pktlen--; debug(2, "q3m pktlen %d lastchar %x\n", pktlen, (unsigned int)rawpkt[pktlen - 1]); server->master_pkt = (char *)realloc(server->master_pkt, server->master_pkt_len + pktlen + 1); if (server->type->id == STEF_MASTER) { ret = decode_stefmaster_packet(server, rawpkt, pktlen); } else { ret = decode_q3master_packet(server, rawpkt, pktlen); } debug(2, "q3m %d servers\n", server->n_servers); return (ret); } else if (show_errors) { unsigned int ipaddr = ntohl(server->ipaddr); fprintf(stderr, "Odd packet from QW master %d.%d.%d.%d, processing ...\n", (ipaddr >> 24) & 0xff, (ipaddr >> 16) & 0xff, (ipaddr >> 8) & 0xff, ipaddr & 0xff ); print_packet(server, rawpkt, pktlen); } server->master_pkt = (char *)realloc(server->master_pkt, server->master_pkt_len + pktlen + 1); rawpkt[pktlen] = '\0'; memcpy(server->master_pkt + server->master_pkt_len, rawpkt, pktlen + 1); server->master_pkt_len += pktlen; server->n_servers = server->master_pkt_len / 6; if (server->type->flags & TF_MASTER_MULTI_RESPONSE) { server->next_player_info = -1; server->retry1 = 0; } else if (server->type->id == HL_MASTER) { if ((server->master_query_tag[0] == 0) && (server->master_query_tag[1] == 0) && (server->master_query_tag[2] == 0)) { // all done server->server_name = MASTER; bind_sockets(); ret = DONE_FORCE; } else { // more to come server->retry1++; send_qwmaster_request_packet(server); } } else if (server->type->flags & TF_MASTER_STEAM) { // should the HL_MASTER be the same as this? int i; for (i = pktlen - 6; i < pktlen && 0x00 == rawpkt[i]; i++) { } if (i == pktlen) { // last 6 bytes where 0x00 so we have reached the last packet server->n_servers--; server->master_pkt_len -= 6; server->server_name = MASTER; bind_sockets(); ret = DONE_FORCE; } else { // more to come server->retry1++; send_qwmaster_request_packet(server); } } else { server->server_name = MASTER; ret = DONE_AUTO; bind_sockets(); } return (ret); } int decode_q3master_packet(struct qserver *server, char *pkt, int pktlen) { char *p; char *end = pkt + pktlen; char *last = end - 6; pkt[pktlen] = 0; p = pkt; while (p < last) { // IP & Port memcpy(server->master_pkt + server->master_pkt_len, &p[0], 6); server->master_pkt_len += 6; p += 6; // Sometimes we get some bad IP's so we search for the entry terminator '\' to avoid issues with this while (p < end && *p != '\\') { p++; } if (p < end) { // Skip over the '\' p++; } if (*p && (p + 3 == end) && (0 == strncmp("EOF", p, 3))) { // Last packet ID ( seen in COD4 ) server->n_servers = server->master_pkt_len / 6; server->retry1 = 0; // received at least one packet so no need to retry return (DONE_FORCE); } } server->n_servers = server->master_pkt_len / 6; // server->next_player_info= -1; evil, causes busy loop! server->retry1 = 0; // received at least one packet so no need to retry return (INPROGRESS); } int decode_stefmaster_packet(struct qserver *server, char *pkt, int pktlen) { unsigned char *p, *m, *end; unsigned int i, b; pkt[pktlen] = 0; p = (unsigned char *)pkt; m = (unsigned char *)server->master_pkt + server->master_pkt_len; end = (unsigned char *)&pkt[pktlen - 12]; while (*p && p < end) { for (i = 6; i; i--) { sscanf((char *)p, "%2x", &b); p += 2; *m++ = b; } server->master_pkt_len += 6; while (*p && *p == '\\') { p++; } } server->n_servers = server->master_pkt_len / 6; server->next_player_info = -1; server->retry1 = 0; return (1); } /* Packet from Tribes master server */ query_status_t deal_with_tribesmaster_packet(struct qserver *server, char *rawpkt, int pktlen) { unsigned char *upkt = (unsigned char *)rawpkt; int packet_number = upkt[2]; int n_packets = upkt[3]; unsigned char *p; char *mpkt; int len; unsigned int ipaddr; debug(2, "deal_with_tribesmaster_packet %p, %d", server, pktlen); if (memcmp(rawpkt, tribes_master_response, sizeof(tribes_master_response)) != 0) { fprintf(stderr, "Odd packet from Tribes master server\n"); print_packet(server, rawpkt, pktlen); } /* 0x1006 * 01 packet number * 08 # packets * 02 * 0000 * 66 * 0d length of following string * "Tribes Master" * 3c length of following string * "Check out the Starsiege demo now! www.starsiegeplayers.com" * 0035 * 06 d143 4764 812c * 06 d1e2 8df3 616d * 06 1804 6d50 616d * 06 d81c 6dc0 616d */ /* 0x1006 * 02 * 08 * 02 0000 * 66 * 00 3f * 06 cf88 344c 1227 */ /* printf( "packet_number %d n_packets %d\n", packet_number, n_packets); */ len = upkt[8]; if (len > 0) { p = (unsigned char *)rawpkt + 9; // printf( "%.*s\n", len, p); p += len; len = upkt[8 + len + 1]; // printf( "%.*s\n", len, p+1); p += len + 1; p += 2; } else { p = (unsigned char *)rawpkt + 10; } if (server->master_pkt == NULL) { server->master_pkt = (char *)malloc(n_packets * 64 * 6); mpkt = server->master_pkt; } else { mpkt = server->master_pkt + server->n_servers * 6; } while ((char *)p < rawpkt + pktlen) { if (*p != 0x6) { printf("*p %u\n", (unsigned)*p); } memcpy(mpkt, p + 1, sizeof(ipaddr)); if (0) { mpkt[4] = p[5]; mpkt[5] = p[6]; } else { mpkt[5] = p[5]; mpkt[4] = p[6]; } //printf( "%08x:%hu %u.%u.%u.%u:%hu\n", ipaddr, port, ipaddr>>24, (ipaddr>>16)&0xff, (ipaddr>>8)&0xff, ipaddr&0xff, port); p += 7; mpkt += 6; } /* * if ( (char*)p != rawpkt+pktlen) * printf( "%x %x\n", p, rawpkt+pktlen); */ server->master_pkt_len = mpkt - server->master_pkt; server->n_servers = server->master_pkt_len / 6; server->server_name = MASTER; server->next_player_info = -1; if (packet_number >= n_packets) { return (DONE_FORCE); } else { return (DONE_AUTO); } } char * display_tribes2_string_list(unsigned char *pkt) { char *delim = ""; unsigned int count, len; count = *pkt; pkt++; for ( ; count; count--) { len = *pkt; pkt++; if (len > 0) { if (raw_display) { xform_printf(OF, "%s%.*s", delim, (int)len, pkt); delim = raw_delimiter; } else { xform_printf(OF, "%.*s\n", (int)len, pkt); } } pkt += len; } if (raw_display) { fputs("\n", OF); } return ((char *)pkt); } query_status_t deal_with_tribes2master_packet(struct qserver *server, char *pkt, int pktlen) { unsigned int n_servers, index, total, server_limit; char *p, *mpkt; debug(2, "deal_with_tribes2master_packet %p, %d", server, pktlen); if (pkt[0] == TRIBES2_RESPONSE_GAME_TYPES) { pkt += 6; if (raw_display) { xform_printf(OF, "%s%s%s%s", server->type->type_prefix, raw_delimiter, server->arg, raw_delimiter); } else { xform_printf(OF, "Game Types\n"); xform_printf(OF, "----------\n"); } pkt = display_tribes2_string_list((unsigned char *)pkt); if (raw_display) { xform_printf(OF, "%s%s%s%s", server->type->type_prefix, raw_delimiter, server->arg, raw_delimiter); } else { xform_printf(OF, "\nMission Types\n"); xform_printf(OF, "-------------\n"); } display_tribes2_string_list((unsigned char *)pkt); server->master_pkt_len = 0; server->n_servers = 0; server->server_name = MASTER; server->next_player_info = -1; return (DONE_FORCE); } if (pkt[0] != TRIBES2_RESPONSE_MASTER) { /* error */ return (PKT_ERROR); } server_limit = get_param_ui_value(server, "limit", ~0); n_servers = little_endian ? *(unsigned short *)(pkt + 8) : swap_short(pkt + 8); index = *(unsigned char *)(pkt + 6); total = *(unsigned char *)(pkt + 7); if (server->master_pkt == NULL) { server->master_pkt = (char *)malloc(total * n_servers * 6); mpkt = server->master_pkt; } else { mpkt = server->master_pkt + server->n_servers * 6; } p = pkt + 10; for ( ; n_servers && ((char *)mpkt - server->master_pkt) / 6 < server_limit; n_servers--, p += 6, mpkt += 6) { memcpy(mpkt, p, 4); mpkt[4] = p[5]; mpkt[5] = p[4]; } server->master_pkt_len = (char *)mpkt - server->master_pkt; server->n_servers = server->master_pkt_len / 6; server->server_name = MASTER; server->next_player_info = -1; if ((index >= total - 1) || (server->n_servers >= server_limit)) { return (PKT_ERROR); } return (DONE_AUTO); } int server_info_packet(struct qserver *server, struct q_packet *pkt, int datalen) { int off = 0; /* ignore duplicate packets */ if (server->server_name != NULL) { return (0); } server->address = strdup((char *)&pkt->data[off]); off += strlen(server->address) + 1; if (off >= datalen) { return (-1); } server->server_name = strdup((char *)&pkt->data[off]); off += strlen(server->server_name) + 1; if (off >= datalen) { return (-1); } server->map_name = strdup((char *)&pkt->data[off]); off += strlen(server->map_name) + 1; if (off > datalen) { return (-1); } server->num_players = pkt->data[off++]; server->max_players = pkt->data[off++]; server->protocol_version = pkt->data[off++]; server->retry1 = n_retries; if (get_server_rules) { debug(3, "send_rule_request_packet3"); send_rule_request_packet(server); } if (get_player_info) { send_player_request_packet(server); } return (0); } int player_info_packet(struct qserver *server, struct q_packet *pkt, int datalen) { char *name, *address; int off, colors, frags, connect_time, player_number; struct player *player, *last; off = 0; player_number = pkt->data[off++]; name = (char *)&pkt->data[off]; off += strlen(name) + 1; if (off >= datalen) { return (-1); } colors = pkt->data[off + 3]; colors = (colors << 8) + pkt->data[off + 2]; colors = (colors << 8) + pkt->data[off + 1]; colors = (colors << 8) + pkt->data[off]; off += sizeof(colors); frags = pkt->data[off + 3]; frags = (frags << 8) + pkt->data[off + 2]; frags = (frags << 8) + pkt->data[off + 1]; frags = (frags << 8) + pkt->data[off]; off += sizeof(frags); connect_time = pkt->data[off + 3]; connect_time = (connect_time << 8) + pkt->data[off + 2]; connect_time = (connect_time << 8) + pkt->data[off + 1]; connect_time = (connect_time << 8) + pkt->data[off]; off += sizeof(connect_time); address = (char *)&pkt->data[off]; off += strlen(address) + 1; if (off > datalen) { return (-1); } last = server->players; while (last != NULL && last->next != NULL) { if (last->number == player_number) { return (0); } last = last->next; } if ((last != NULL) && (last->number == player_number)) { return (0); } player = (struct player *)calloc(1, sizeof(struct player)); player->number = player_number; player->name = strdup(name); player->address = strdup(address); player->connect_time = connect_time; player->frags = frags; player->shirt_color = colors >> 4; player->pants_color = colors & 0xf; player->next = NULL; if (last == NULL) { server->players = player; } else { last->next = player; } server->next_player_info++; server->retry2 = n_retries; if (server->next_player_info < server->num_players) { send_player_request_packet(server); } return (0); } int rule_info_packet(struct qserver *server, struct q_packet *pkt, int datalen) { int off = 0; struct rule *rule, *last; char *name, *value; /* Straggler packet after we've already given up fetching rules */ if (server->next_rule == NULL) { return (0); } if (ntohs(pkt->length) == Q_HEADER_LEN) { server->next_rule = NULL; return (0); } name = (char *)&pkt->data[off]; off += strlen(name) + 1; if (off >= datalen) { return (-1); } value = (char *)&pkt->data[off]; off += strlen(value) + 1; if (off > datalen) { return (-1); } last = server->rules; while (last != NULL && last->next != NULL) { if (strcmp(last->name, name) == 0) { return (0); } last = last->next; } if ((last != NULL) && (strcmp(last->name, name) == 0)) { return (0); } rule = (struct rule *)malloc(sizeof(struct rule)); rule->name = strdup(name); rule->value = strdup(value); rule->next = NULL; if (last == NULL) { server->rules = rule; } else { last->next = rule; } server->n_rules++; server->next_rule = rule->name; server->retry1 = n_retries; debug(3, "send_rule_request_packet4"); send_rule_request_packet(server); return (0); } struct info * player_add_info(struct player *player, char *key, char *value, int flags) { struct info *info; if (flags & OVERWITE_DUPLICATES) { for (info = player->info; info; info = info->next) { if (0 == strcmp(info->name, key)) { // We should be able to free this free(info->value); if (flags & NO_VALUE_COPY) { info->value = value; } else { info->value = strdup(value); } return (info); } } } if (flags & CHECK_DUPLICATE_RULES) { for (info = player->info; info; info = info->next) { if (0 == strcmp(info->name, key)) { return (NULL); } } } if (flags & COMBINE_VALUES) { for (info = player->info; info; info = info->next) { if (0 == strcmp(info->name, key)) { char *full_value = (char *)calloc(sizeof(char), strlen(info->value) + strlen(value) + 2); if (NULL == full_value) { fprintf(stderr, "Failed to malloc combined value\n"); exit(1); } sprintf(full_value, "%s%s%s", info->value, multi_delimiter, value); // We should be able to free this free(info->value); info->value = full_value; return (info); } } } info = (struct info *)malloc(sizeof(struct info)); if (flags & NO_KEY_COPY) { info->name = key; } else { info->name = strdup(key); } if (flags & NO_VALUE_COPY) { info->value = value; } else { info->value = strdup(value); } info->next = NULL; if (NULL == player->info) { player->info = info; } else { *player->last_info = info; } player->last_info = &info->next; player->n_info++; return (info); } struct rule * add_rule(struct qserver *server, char *key, char *value, int flags) { struct rule *rule; debug(3, "key: %s, value: %s, flags: %d", key, value, flags); if (flags & OVERWITE_DUPLICATES) { for (rule = server->rules; rule; rule = rule->next) { if (0 == strcmp(rule->name, key)) { // We should be able to free this free(rule->value); if (flags & NO_VALUE_COPY) { rule->value = value; } else { rule->value = strdup(value); } return (rule); } } } if (flags & CHECK_DUPLICATE_RULES) { for (rule = server->rules; rule; rule = rule->next) { if (0 == strcmp(rule->name, key)) { return (NULL); } } } if (flags & COMBINE_VALUES) { for (rule = server->rules; rule; rule = rule->next) { if (0 == strcmp(rule->name, key)) { char *full_value = (char *)calloc(sizeof(char), strlen(rule->value) + strlen(value) + strlen(multi_delimiter) + 1); if (NULL == full_value) { fprintf(stderr, "Failed to malloc combined value\n"); exit(1); } sprintf(full_value, "%s%s%s", rule->value, multi_delimiter, value); // We should be able to free this free(rule->value); rule->value = full_value; return (rule); } } } rule = (struct rule *)malloc(sizeof(struct rule)); if (flags & NO_KEY_COPY) { rule->name = key; } else { rule->name = strdup(key); } if (flags & NO_VALUE_COPY) { rule->value = value; } else { rule->value = strdup(value); } rule->next = NULL; *server->last_rule = rule; server->last_rule = &rule->next; server->n_rules++; return (rule); } void add_nrule(struct qserver *server, char *key, char *value, int len) { struct rule *rule; for (rule = server->rules; rule; rule = rule->next) { if (strcmp(rule->name, key) == 0) { return; } } rule = (struct rule *)malloc(sizeof(struct rule)); rule->name = strdup(key); rule->value = strndup(value, len); rule->next = NULL; *server->last_rule = rule; server->last_rule = &rule->next; server->n_rules++; } struct player * add_player(struct qserver *server, int player_number) { struct player *player; for (player = server->players; player; player = player->next) { if (player->number == player_number) { return (NULL); } } player = (struct player *)calloc(1, sizeof(struct player)); player->number = player_number; player->next = server->players; player->n_info = 0; player->ping = NA_INT; player->team = NA_INT; player->score = NA_INT; player->deaths = NA_INT; player->frags = NA_INT; player->last_info = NULL; server->players = player; server->n_player_info++; return (player); } STATIC struct player * get_player_by_number(struct qserver *server, int player_number) { struct player *player; for (player = server->players; player; player = player->next) { if (player->number == player_number) { return (player); } } return (NULL); } // Updates a servers port information. // Sets the rules: // _queryport // hostport void change_server_port(struct qserver *server, unsigned short port, int force) { if ((port > 0) && (port != server->port)) { // valid port and changing char arg[64]; unsigned int ipaddr = ntohl(server->ipaddr); // Update the servers hostname as required sprintf(arg, "%d.%d.%d.%d:%hu", ipaddr >> 24, (ipaddr >> 16) & 0xff, (ipaddr >> 8) & 0xff, ipaddr & 0xff, port); if (show_game_port || force || server->flags & TF_SHOW_GAME_PORT) { // Update the server arg free(server->arg); server->arg = strdup(arg); // Add a rule noting the previous query port sprintf(arg, "%hu", server->port); add_rule(server, "_queryport", arg, NO_FLAGS); // Update the servers port server->port = port; } if (0 != strcmp(server->arg, server->host_name)) { // hostname isnt the query arg char *colon = strchr(server->host_name, ':'); // dns hostname or hostname:port char *hostname = malloc(strlen(server->host_name) + 7); if (NULL == hostname) { fprintf(stderr, "Failed to malloc hostname memory\n"); } else { if (colon) { *colon = '\0'; } sprintf(hostname, "%s:%hu", server->host_name, port); free(server->host_name); server->host_name = hostname; } } // Add a rule noting the servers hostport sprintf(arg, "%hu", port); add_rule(server, "hostport", arg, OVERWITE_DUPLICATES); } } STATIC void players_set_teamname(struct qserver *server, int teamid, char *teamname) { struct player *player; for (player = server->players; player; player = player->next) { if (player->team == teamid) { player->team_name = strdup(teamname); } } } STATIC char * dup_nstring(const char *pkt, const char *end, char **next) { char *pt = (char *)pkt; int len = ((unsigned char *)pkt)[0]; pt++; if (*pt == '\1') { len++; } if (pt + len > end) { return (NULL); } *next = pt + len; return (strndup(pt, len)); } STATIC char * dup_n1string(char *pkt, char *end, char **next) { unsigned len; if (!pkt || (pkt >= end)) { return (NULL); } len = (unsigned char)pkt[0] - 1; pkt++; if (pkt + len > end) { return (NULL); } *next = pkt + len; return (strndup(pkt, len)); } STATIC int pariah_basic_packet(struct qserver *server, char *rawpkt, char *end) { char *next; char *string; change_server_port(server, swap_short_from_little(&rawpkt[14]), 0); if (NULL == (string = ut2003_strdup(&rawpkt[18], end, &next))) { return (-1); } if (server->server_name == NULL) { server->server_name = string; } else { free(string); } if (NULL == (string = ut2003_strdup(next, end, &next))) { return (-1); } if (server->map_name == NULL) { server->map_name = string; } else { free(string); } if (NULL == (string = ut2003_strdup(next, end, &next))) { return (-1); } if (server->game == NULL) { server->game = string; add_rule(server, "gametype", server->game, NO_FLAGS | CHECK_DUPLICATE_RULES); } else { free(string); } server->num_players = (unsigned char)next[0]; server->max_players = (unsigned char)next[1]; return (0); } STATIC int ut2003_basic_packet(struct qserver *server, char *rawpkt, char *end) { char *next; char *string; change_server_port(server, swap_short_from_little(&rawpkt[6]), 0); if (NULL == (string = ut2003_strdup(&rawpkt[14], end, &next))) { return (-1); } if (server->server_name == NULL) { server->server_name = string; } else { free(string); } if (NULL == (string = ut2003_strdup(next, end, &next))) { return (-1); } if (server->map_name == NULL) { server->map_name = string; } else { free(string); } if (NULL == (string = ut2003_strdup(next, end, &next))) { return (-1); } if (server->game == NULL) { server->game = string; add_rule(server, "gametype", server->game, NO_FLAGS | CHECK_DUPLICATE_RULES); } else { free(string); } server->num_players = swap_long_from_little(next); next += 4; server->max_players = swap_long_from_little(next); return (0); } STATIC int pariah_rule_packet(struct qserver *server, char *rawpkt, char *end) { char *key, *value; unsigned char no_rules = (unsigned char)rawpkt[1]; unsigned char seen = 0; // type + no_rules rawpkt += 2; // we get size encoded key = value pairs while (rawpkt < end && no_rules > seen) { // first byte is the rule count seen = (unsigned char)rawpkt[0]; rawpkt++; if (NULL == (key = ut2003_strdup(rawpkt, end, &rawpkt))) { break; } if ('\0' == rawpkt[0]) { value = strdup(""); rawpkt++; } else if (NULL == (value = ut2003_strdup(rawpkt, end, &rawpkt))) { break; } if (NULL == add_rule(server, key, value, NO_KEY_COPY | NO_VALUE_COPY | COMBINE_VALUES)) { /* duplicate, so free key and value */ free(value); free(key); } seen++; } if (no_rules == seen) { // all done server->next_rule = NULL; return (1); } return (0); } STATIC int ut2003_rule_packet(struct qserver *server, char *rawpkt, char *end) { char *key, *value; int result = 0; // Packet Type rawpkt++; // we get size encoded key = value pairs while (rawpkt < end) { if (NULL == (key = ut2003_strdup(rawpkt, end, &rawpkt))) { break; } if (NULL == (value = ut2003_strdup(rawpkt, end, &rawpkt))) { break; } if (strcmp(key, "minplayers") == 0) { result = atoi(value); } if (NULL == add_rule(server, key, value, NO_KEY_COPY | NO_VALUE_COPY | COMBINE_VALUES)) { /* duplicate, so free key and value */ free(value); free(key); } } return (result); } char * ut2003_strdup(const char *string, const char *end, char **next) { unsigned char len = string[0]; char *result = NULL; if (len < 128) { // type 1 string //fprintf( stderr, "Type 1:" ); result = dup_nstring(string, end, next); } else { // type 2 string //fprintf( stderr, "Type 2:\n" ); const char *last; char *resp, *pos; // minus indicator len -= 128; // double byte chars so * 2 len = len * 2; last = string + len; if (last > end) { *next = (char *)end; fprintf(stderr, "Type 2 string format error ( too short )\n"); return (NULL); } *next = (char *)last + 1; if (NULL == (result = (char *)calloc(last - string, sizeof(char)))) { fprintf(stderr, "Failed to malloc string memory\n"); return (NULL); } resp = result; pos = (char *)string + 1; while (pos <= last) { // check for a color code if ((pos + 6 <= last) && (0 == memcmp(pos, "^\0#\0", 4))) { // we have a color code //fprintf( stderr, "color:%02hhx%02hhx\n", pos[4], pos[5] ); // indicator transformed to ^\1 *resp = *pos; resp++; pos++; *resp = '\1'; resp++; pos += 3; // color byte *resp = *pos; resp++; pos += 2; //pos += 6; } // standard char //fprintf( stderr, "char: %02hhx\n", *pos ); *resp = *pos; resp++; pos += 2; } } //fprintf( stderr, "'%s'\n", result ); return (result); } STATIC int pariah_player_packet(struct qserver *server, char *rawpkt, char *end) { unsigned char no_players = rawpkt[1]; unsigned char seen = 0; /* XXX: cannot work this way, it takes only * this packet into consideration. What if * player info is spread across multiple * packets? */ // type + no_players + some unknown preamble rawpkt += 3; while (rawpkt < end && seen < no_players) { struct player *player; // Player Number rawpkt += 4; // Create a player if (NULL == (player = add_player(server, server->n_player_info))) { return (0); } // Name ( min 3 bytes ) player->name = ut2003_strdup(rawpkt, end, &rawpkt); // Ping player->ping = swap_long_from_little(rawpkt); rawpkt += 4; // Frags player->frags = (unsigned char)rawpkt[0]; rawpkt++; // unknown rawpkt++; seen++; } if (no_players == seen) { // all done server->num_players = server->n_player_info; return (1); } // possibly more to come return (0); } STATIC int ut2003_player_packet(struct qserver *server, char *rawpkt, char *end) { // skip type rawpkt++; switch (server->protocol_version) { case 0x7e: // XMP packet //fprintf( stderr, "XMP packet\n" ); while (rawpkt < end) { struct player *player; char *var, *val; unsigned char no_props; if (rawpkt + 24 > end) { malformed_packet(server, "player info too short"); rawpkt = end; return (1); } // Player Number never set rawpkt += 4; // Player ID never set rawpkt += 4; if (NULL == (player = add_player(server, server->n_player_info))) { return (0); } // Name ( min 3 bytes ) player->name = ut2003_strdup(rawpkt, end, &rawpkt); // Ping player->ping = swap_long_from_little(rawpkt); rawpkt += 4; // Frags player->frags = swap_long_from_little(rawpkt); rawpkt += 4; // Stat ID never set rawpkt += 4; // Player properties no_props = rawpkt[0]; //fprintf( stderr, "noprops %d\n", no_props ); rawpkt++; while (rawpkt < end && no_props > 0) { if (NULL == (var = ut2003_strdup(rawpkt, end, &rawpkt))) { break; } if (NULL == (val = ut2003_strdup(rawpkt, end, &rawpkt))) { break; } //fprintf( stderr, "attrib: %s = %s\n", var, val ); // Things we can use if (0 == strcmp(var, "team")) { player->team_name = val; } else if (0 == strcmp(var, "class")) { player->skin = val; } else { free(val); } free(var); no_props--; } } break; default: while (rawpkt < end) { struct player *player; if (rawpkt + 4 > end) { malformed_packet(server, "player packet too short"); return (1); } if (NULL == (player = add_player(server, swap_long_from_little(rawpkt)))) { return (0); } player->name = ut2003_strdup(rawpkt + 4, end, &rawpkt); if (rawpkt + 8 > end) { malformed_packet(server, "player packet too short"); return (1); } player->ping = swap_long_from_little(rawpkt); rawpkt += 4; player->frags = swap_long_from_little(rawpkt); rawpkt += 4; { unsigned team = swap_long_from_little(rawpkt); rawpkt += 4; player->flags |= PLAYER_FLAG_DO_NOT_FREE_TEAM; if (team & 1 << 29) { player->team_name = "red"; } else if (team & 1 << 30) { player->team_name = "blue"; } } } } return (0); } char * get_rule(struct qserver *server, char *name) { struct rule *rule; rule = server->rules; for ( ; rule != NULL; rule = rule->next) { if (strcmp(name, rule->name) == 0) { return (rule->value); } } return (NULL); } query_status_t deal_with_ut2003_packet(struct qserver *server, char *rawpkt, int pktlen) { // For protocol spec see: // http://unreal.student.utwente.nl/UT2003-queryspec.html char *end; int error = 0, before; unsigned int packet_header; debug(2, "deal_with_ut2003_packet %p, %d", server, pktlen); rawpkt[pktlen] = '\0'; end = &rawpkt[pktlen]; packet_header = swap_long_from_little(&rawpkt[0]); rawpkt += 4; server->protocol_version = packet_header; if ( (packet_header != 0x77) && // Pariah Demo? (packet_header != 0x78) && // UT2003 Demo (packet_header != 0x79) && // UT2003 Retail (packet_header != 0x7e) && // Unreal2 XMP (packet_header != 0x7f) && // UT2004 Demo (packet_header != 0x80) // UT2004 Retail ) { malformed_packet(server, "Unknown type 0x%x", packet_header); } switch (rawpkt[0]) { case 0x00: // Server info if (server->server_name == NULL) { server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); } error = ut2003_basic_packet(server, rawpkt, end); if (!error) { if (get_server_rules || get_player_info) { int requests = server->n_requests; server->next_rule = ""; server->retry1 = n_retries; server->retry2 = 0; // don't wait for player packet debug(3, "send_rule_request_packet5"); send_rule_request_packet(server); server->n_requests = requests; // would produce wrong ping } } break; case 0x01: // Game info ut2003_rule_packet(server, rawpkt, end); server->next_rule = ""; server->retry1 = 0; /* we received at least one rule packet so * no need to retry. We'd get double * entries otherwise. */ break; case 0x02: // Player info before = server->n_player_info; error = ut2003_player_packet(server, rawpkt, end); if (before == server->n_player_info) { error = 1; } break; case 0x10: // Pariah Server info if (server->server_name == NULL) { server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); } error = pariah_basic_packet(server, rawpkt, end); if (!error) { // N.B. pariah always sends a rules and players packet int requests = server->n_requests; server->next_rule = ""; server->retry1 = n_retries; server->retry2 = 0; server->n_requests = requests; // would produce wrong ping } break; case 0x11: // Game info pariah_rule_packet(server, rawpkt, end); server->retry1 = 0; /* we received at least one rule packet so * no need to retry. We'd get double * entries otherwise. */ break; case 0x12: // Player info before = server->n_player_info; pariah_player_packet(server, rawpkt, end); if (before == server->n_player_info) { error = 1; } break; default: malformed_packet(server, "Unknown packet type 0x%x", (unsigned)rawpkt[0]); break; } /* don't cleanup if we fetch server rules. We would lose * rule packets as we don't know how many we get * We do clean up if we don't fetch server rules so we don't * need to wait for timeout. */ if ( error || (!get_server_rules && !get_player_info) || (!get_server_rules && (server->num_players == server->n_player_info)) || ((server->next_rule == NULL) && (server->num_players == server->n_player_info)) ) { return (DONE_FORCE); } return (INPROGRESS); } int deal_with_unrealmaster_packet(struct qserver *server, char *rawpkt, int pktlen) { debug(2, "deal_with_unrealmaster_packet %p, %d", server, pktlen); if (pktlen == 0) { return (PKT_ERROR); } print_packet(server, rawpkt, pktlen); puts("--"); return (0); } /* Returns 1 if the query is done (server may be freed) and 0 if not. */ query_status_t deal_with_halflife_packet(struct qserver *server, char *rawpkt, int pktlen) { char *pkt; char *end = &rawpkt[pktlen]; int pkt_index = 0, pkt_max = 0; char number[16]; short pkt_id; debug(2, "deal_with_halflife_packet %p, %d", server, pktlen); if (server->server_name == NULL) { server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); } if (pktlen < 5) { return (PKT_ERROR); } if ((((rawpkt[0] != '\377') && (rawpkt[0] != '\376')) || (rawpkt[1] != '\377') || (rawpkt[2] != '\377') || (rawpkt[3] != '\377')) && show_errors) { unsigned int ipaddr = ntohl(server->ipaddr); fprintf(stderr, "Odd packet from server %d.%d.%d.%d:%hu, processing ...\n", (ipaddr >> 24) & 0xff, (ipaddr >> 16) & 0xff, (ipaddr >> 8) & 0xff, ipaddr & 0xff, ntohs(server->port) ); print_packet(server, rawpkt, pktlen); } if (((unsigned char *)rawpkt)[0] == 0xfe) { SavedData *sdata; pkt_index = ((unsigned char *)rawpkt)[8] >> 4; pkt_max = ((unsigned char *)rawpkt)[8] & 0xf; memcpy(&pkt_id, &rawpkt[4], 2); if (server->saved_data.data == NULL) { sdata = &server->saved_data; } else { sdata = (SavedData *)calloc(1, sizeof(SavedData)); sdata->next = server->saved_data.next; server->saved_data.next = sdata; } sdata->pkt_index = pkt_index; sdata->pkt_max = pkt_max; sdata->pkt_id = pkt_id; sdata->datalen = pktlen - 9; sdata->data = (char *)malloc(pktlen - 9); memcpy(sdata->data, &rawpkt[9], pktlen - 9); /* combine_packets will call us recursively */ return (combine_packets(server)); /* * fprintf( OF, "pkt_index %d pkt_max %d\n", pkt_index, pkt_max); * rawpkt+= 9; * pktlen-= 9; */ } /* 'info' response */ if ((rawpkt[4] == 'C') || (rawpkt[4] == 'm')) { if (server->server_name != NULL) { return (0); } pkt = &rawpkt[5]; server->address = strdup(pkt); pkt += strlen(pkt) + 1; server->server_name = strdup(pkt); pkt += strlen(pkt) + 1; server->map_name = strdup(pkt); pkt += strlen(pkt) + 1; if (*pkt) { add_rule(server, "gamedir", pkt, NO_FLAGS); } if (*pkt && (strcmp(pkt, "valve") != 0)) { server->game = add_rule(server, "game", pkt, NO_FLAGS)->value; server->flags |= FLAG_DO_NOT_FREE_GAME; } pkt += strlen(pkt) + 1; if (*pkt) { add_rule(server, "gamename", pkt, NO_FLAGS); } pkt += strlen(pkt) + 1; server->num_players = (unsigned int)pkt[0]; server->max_players = (unsigned int)pkt[1]; pkt += 2; if (pkt < end) { int protocol = *((unsigned char *)pkt); sprintf(number, "%d", protocol); add_rule(server, "protocol", number, NO_FLAGS); pkt++; } if (rawpkt[4] == 'm') { if (*pkt == 'd') { add_rule(server, "sv_type", "dedicated", NO_FLAGS); } else if (*pkt == 'l') { add_rule(server, "sv_type", "listen", NO_FLAGS); } else { add_rule(server, "sv_type", "?", NO_FLAGS); } pkt++; if (*pkt == 'w') { add_rule(server, "sv_os", "windows", NO_FLAGS); } else if (*pkt == 'l') { add_rule(server, "sv_os", "linux", NO_FLAGS); } else { char str[2] = "\0"; str[0] = *pkt; add_rule(server, "sv_os", str, NO_FLAGS); } pkt++; add_rule(server, "sv_password", *pkt ? "1" : "0", NO_FLAGS); pkt++; add_rule(server, "mod", *pkt ? "1" : "0", NO_FLAGS); if (*pkt) { int n; /* pull out the mod infomation */ pkt++; add_rule(server, "mod_info_url", pkt, NO_FLAGS); pkt += strlen(pkt) + 1; if (*pkt) { add_rule(server, "mod_download_url", pkt, NO_FLAGS); } pkt += strlen(pkt) + 1; if (*pkt) { add_rule(server, "mod_detail", pkt, NO_FLAGS); } pkt += strlen(pkt) + 1; n = swap_long_from_little(pkt); sprintf(number, "%d", n); add_rule(server, "modversion", number, NO_FLAGS); pkt += 4; n = swap_long_from_little(pkt); sprintf(number, "%d", n); add_rule(server, "modsize", number, NO_FLAGS); pkt += 4; add_rule(server, "svonly", *pkt ? "1" : "0", NO_FLAGS); pkt++; add_rule(server, "cldll", *pkt ? "1" : "0", NO_FLAGS); pkt++; if (pkt < end) { add_rule(server, "secure", *pkt ? "1" : "0", NO_FLAGS); } } } if (get_player_info && server->num_players) { int requests = server->n_requests; server->next_player_info = server->num_players - 1; send_player_request_packet(server); server->n_requests = requests; // prevent wrong ping } if (get_server_rules) { int requests = server->n_requests; server->next_rule = ""; server->retry1 = n_retries; debug(3, "send_rule_request_packet6"); send_rule_request_packet(server); server->n_requests = requests; // prevent wrong ping } } /* 'players' response */ else if ((rawpkt[4] == 'D') && (server->players == NULL)) { unsigned int n = 0, temp; struct player *player; struct player **last_player = &server->players; if ((unsigned int)rawpkt[5] > server->num_players) { server->num_players = (unsigned int)rawpkt[5]; } pkt = &rawpkt[6]; rawpkt[pktlen] = '\0'; while (1) { if (*pkt != n + 1) { break; } n++; pkt++; player = (struct player *)calloc(1, sizeof(struct player)); player->name = strdup(pkt); pkt += strlen(pkt) + 1; memcpy(&player->frags, pkt, 4); pkt += 4; memcpy(&temp, pkt, 4); pkt += 4; if (big_endian) { player->frags = swap_long(&player->frags); } player->connect_time = swap_float_from_little(&temp); *last_player = player; last_player = &player->next; } if (n > server->num_players) { server->num_players = n; } server->next_player_info = server->num_players; } /* 'rules' response */ else if ((rawpkt[4] == 'E') && (server->next_rule != NULL)) { int n = 0; n = ((unsigned char *)rawpkt)[5] + ((unsigned char *)rawpkt)[6] * 256; pkt = &rawpkt[7]; while (n) { char *key = pkt; char *value; pkt += strlen(pkt) + 1; if (pkt > end) { break; } value = pkt; pkt += strlen(pkt) + 1; if (pkt > end) { break; } if ((key[0] == 's') && (strcmp(key, "sv_password") == 0)) { add_rule(server, key, value, CHECK_DUPLICATE_RULES); } else { add_rule(server, key, value, NO_FLAGS); } n--; } server->next_rule = NULL; } else if ((rawpkt[4] != 'E') && (rawpkt[4] != 'D') && (rawpkt[4] != 'm') && (rawpkt[4] != 'C') && show_errors) { /* if ( pkt_count) { rawpkt-= 9; pktlen+= 9; } */ fprintf(stderr, "Odd packet from HL server %s (packet len %d)\n", server->arg, pktlen); print_packet(server, rawpkt, pktlen); } return (DONE_AUTO); } query_status_t deal_with_tribes_packet(struct qserver *server, char *rawpkt, int pktlen) { unsigned char *pkt, *end; int len, pnum, ping, packet_loss, n_teams, t; struct player *player; struct player **teams = NULL; struct player **last_player = &server->players; char buf[24]; debug(2, "deal_with_tribes_packet %p, %d", server, pktlen); if (server->server_name == NULL) { server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); } else { gettimeofday(&server->packet_time1, NULL); } if (pktlen < sizeof(tribes_info_reponse)) { return (PKT_ERROR); } if (strncmp(rawpkt, tribes_players_reponse, sizeof(tribes_players_reponse)) != 0) { return (PKT_ERROR); } pkt = (unsigned char *)&rawpkt[sizeof(tribes_info_reponse)]; len = *pkt; /* game name: "Tribes" */ add_nrule(server, "gamename", (char *)pkt + 1, len); pkt += len + 1; len = *pkt; /* version */ add_nrule(server, "version", (char *)pkt + 1, len); pkt += len + 1; len = *pkt; /* server name */ server->server_name = strndup((char *)pkt + 1, len); pkt += len + 1; add_rule(server, "dedicated", *pkt ? "1" : "0", NO_FLAGS); pkt++; /* flag: dedicated server */ add_rule(server, "needpass", *pkt ? "1" : "0", NO_FLAGS); pkt++; /* flag: password on server */ server->num_players = *pkt++; server->max_players = *pkt++; sprintf(buf, "%u", (unsigned int)pkt[0] + (unsigned int)pkt[1] * 256); add_rule(server, "cpu", buf, NO_FLAGS); pkt++; /* cpu speed, lsb */ pkt++; /* cpu speed, msb */ len = *pkt; /* Mod (game) */ add_nrule(server, "mods", (char *)pkt + 1, len); pkt += len + 1; len = *pkt; /* game (mission): "C&H" */ add_nrule(server, "game", (char *)pkt + 1, len); pkt += len + 1; len = *pkt; /* Mission (map) */ server->map_name = strndup((char *)pkt + 1, len); pkt += len + 1; len = *pkt; /* description (contains Admin: and Email: ) */ debug(2, "%.*s\n", len, pkt + 1); pkt += len + 1; n_teams = *pkt++; /* number of teams */ if (n_teams == 255) { return (PKT_ERROR); } sprintf(buf, "%d", n_teams); add_rule(server, "numteams", buf, NO_FLAGS); len = *pkt; /* first title */ debug(2, "%.*s\n", len, pkt + 1); pkt += len + 1; len = *pkt; /* second title */ debug(2, "%.*s\n", len, pkt + 1); pkt += len + 1; if (n_teams > 1) { teams = (struct player **)calloc(1, sizeof(struct player *) * n_teams); for (t = 0; t < n_teams; t++) { teams[t] = (struct player *)calloc(1, sizeof(struct player)); teams[t]->number = TRIBES_TEAM; teams[t]->team = t; len = *pkt; /* team name */ teams[t]->name = strndup((char *)pkt + 1, len); debug(2, "team#0 <%.*s>\n", len, pkt + 1); pkt += len + 1; len = *pkt; /* team score */ if (len > 2) { strncpy(buf, (char *)pkt + 1 + 3, len - 3); buf[len - 3] = '\0'; } else { debug(2, "%s score len %d\n", server->arg, len); buf[0] = '\0'; } teams[t]->frags = atoi(buf); debug(2, "team#0 <%.*s>\n", len - 3, pkt + 1 + 3); pkt += len + 1; } } else { len = *pkt; /* DM team? */ debug(2, "%.*s\n", len, pkt + 1); pkt += len + 1; pkt++; n_teams = 0; } pnum = 0; while ((char *)pkt < (rawpkt + pktlen)) { ping = (unsigned int)*pkt << 2; pkt++; packet_loss = *pkt; pkt++; debug(2, "player#%d, team #%d\n", pnum, (int)*pkt); pkt++; len = *pkt; if ((char *)pkt + len > (rawpkt + pktlen)) { break; } player = (struct player *)calloc(1, sizeof(struct player)); player->team = pkt[-1]; if (n_teams && (player->team < n_teams)) { player->team_name = teams[player->team]->name; } else if ((player->team == 255) && n_teams) { player->team_name = "Unknown"; } player->flags |= PLAYER_FLAG_DO_NOT_FREE_TEAM; player->ping = ping; player->packet_loss = packet_loss; player->name = strndup((char *)pkt + 1, len); debug(2, "player#%d, name %.*s\n", pnum, len, pkt + 1); pkt += len + 1; len = *pkt; debug(2, "player#%d, info <%.*s>\n", pnum, len, pkt + 1); end = (unsigned char *)strchr((char *)pkt + 9, 0x9); if (end) { strncpy(buf, (char *)pkt + 9, end - (pkt + 9)); buf[end - (pkt + 9)] = '\0'; player->frags = atoi(buf); debug(2, "player#%d, score <%.*s>\n", pnum, (unsigned)(end - (pkt + 9)), pkt + 9); } *last_player = player; last_player = &player->next; pkt += len + 1; pnum++; } for (t = n_teams; t; ) { t--; teams[t]->next = server->players; server->players = teams[t]; } free(teams); return (DONE_AUTO); } void get_tribes2_player_type(struct player *player) { char *name = player->name; for ( ; *name; name++) { switch (*name) { case 0x8: player->type_flag = PLAYER_TYPE_NORMAL; continue; case 0xc: player->type_flag = PLAYER_TYPE_ALIAS; continue; case 0xe: player->type_flag = PLAYER_TYPE_BOT; continue; case 0xb: break; default: continue; } name++; if (isprint(*name)) { char *n = name; for ( ; isprint(*n); n++) { } player->tribe_tag = strndup(name, n - name); name = n; } if (!*name) { break; } } } query_status_t deal_with_tribes2_packet(struct qserver *server, char *pkt, int pktlen) { char str[256], *pktstart = pkt, *term, *start; unsigned int minimum_net_protocol, build_version, i, t, len, s, status; unsigned int net_protocol; unsigned short cpu_speed; int n_teams = 0, n_players; struct player **teams = NULL, *player; struct player **last_player = &server->players; int query_version; debug(2, "deal_with_tribes2_packet %p, %d", server, pktlen); pkt[pktlen] = '\0'; if (server->server_name == NULL) { server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); } /* * else * gettimeofday( &server->packet_time1, NULL); */ if (pkt[0] == TRIBES2_RESPONSE_PING) { if ((pkt[6] < 4) || (pkt[6] > 12) || (strncmp(pkt + 7, "VER", 3) != 0)) { return (PKT_ERROR); } strncpy(str, pkt + 10, pkt[6] - 3); str[pkt[6] - 3] = '\0'; query_version = atoi(str); add_nrule(server, "queryversion", pkt + 7, pkt[6]); pkt += 7 + pkt[6]; server->protocol_version = query_version; if ((query_version != 3) && (query_version != 5)) { server->server_name = strdup("Unknown query version"); return (PKT_ERROR); } if (query_version == 5) { net_protocol = swap_long_from_little(pkt); sprintf(str, "%u", net_protocol); add_rule(server, "net_protocol", str, NO_FLAGS); pkt += 4; } minimum_net_protocol = swap_long_from_little(pkt); sprintf(str, "%u", minimum_net_protocol); add_rule(server, "minimum_net_protocol", str, NO_FLAGS); pkt += 4; build_version = swap_long_from_little(pkt); sprintf(str, "%u", build_version); add_rule(server, "build_version", str, NO_FLAGS); pkt += 4; server->server_name = strndup(pkt + 1, *(unsigned char *)(pkt)); /* Always send the player request because the ping packet * contains very little information */ send_player_request_packet(server); return (0); } else if (pkt[0] != TRIBES2_RESPONSE_INFO) { return (PKT_ERROR); } pkt += 6; for (i = 0; i < *(unsigned char *)pkt; i++) { if (!isprint(pkt[i + 1])) { return (PKT_ERROR); } } add_nrule(server, server->type->game_rule, pkt + 1, *(unsigned char *)pkt); server->game = strndup(pkt + 1, *(unsigned char *)pkt); pkt += *pkt + 1; add_nrule(server, "mission", pkt + 1, *(unsigned char *)pkt); pkt += *pkt + 1; server->map_name = strndup(pkt + 1, *(unsigned char *)pkt); pkt += *pkt + 1; status = *(unsigned char *)pkt; sprintf(str, "%u", status); add_rule(server, "status", str, NO_FLAGS); if (status & TRIBES2_STATUS_DEDICATED) { add_rule(server, "dedicated", "1", NO_FLAGS); } if (status & TRIBES2_STATUS_PASSWORD) { add_rule(server, "password", "1", NO_FLAGS); } if (status & TRIBES2_STATUS_LINUX) { add_rule(server, "linux", "1", NO_FLAGS); } if (status & TRIBES2_STATUS_TEAMDAMAGE) { add_rule(server, "teamdamage", "1", NO_FLAGS); } if (server->protocol_version == 3) { if (status & TRIBES2_STATUS_TOURNAMENT_VER3) { add_rule(server, "tournament", "1", NO_FLAGS); } if (status & TRIBES2_STATUS_NOALIAS_VER3) { add_rule(server, "no_aliases", "1", NO_FLAGS); } } else { if (status & TRIBES2_STATUS_TOURNAMENT) { add_rule(server, "tournament", "1", NO_FLAGS); } if (status & TRIBES2_STATUS_NOALIAS) { add_rule(server, "no_aliases", "1", NO_FLAGS); } } pkt++; server->num_players = *(unsigned char *)pkt; pkt++; server->max_players = *(unsigned char *)pkt; pkt++; sprintf(str, "%u", *(unsigned char *)pkt); add_rule(server, "bot_count", str, NO_FLAGS); pkt++; cpu_speed = swap_short_from_little(pkt); sprintf(str, "%hu", cpu_speed); add_rule(server, "cpu_speed", str, NO_FLAGS); pkt += 2; if (strcmp(server->server_name, "VER3") == 0) { free(server->server_name); server->server_name = strndup(pkt + 1, *(unsigned char *)pkt); } else { add_nrule(server, "info", pkt + 1, *(unsigned char *)pkt); } pkt += *(unsigned char *)pkt + 1; len = swap_short_from_little(pkt); pkt += 2; start = pkt; if (len + (pkt - pktstart) > pktlen) { len -= (len + (pkt - pktstart)) - pktlen; } if ((len == 0) || (pkt - pktstart >= pktlen)) { goto info_done; } term = strchr(pkt, 0xa); if (!term) { goto info_done; } *term = '\0'; n_teams = atoi(pkt); sprintf(str, "%d", n_teams); add_rule(server, "numteams", str, NO_FLAGS); pkt = term + 1; if (pkt - pktstart >= pktlen) { goto info_done; } teams = (struct player **)calloc(1, sizeof(struct player *) * n_teams); for (t = 0; t < n_teams; t++) { teams[t] = (struct player *)calloc(1, sizeof(struct player)); teams[t]->number = TRIBES_TEAM; teams[t]->team = t; /* team name */ term = strchr(pkt, 0x9); if (!term) { n_teams = t; goto info_done; } teams[t]->name = strndup(pkt, term - pkt); pkt = term + 1; term = strchr(pkt, 0xa); if (!term) { n_teams = t; goto info_done; } *term = '\0'; teams[t]->frags = atoi(pkt); pkt = term + 1; if (pkt - pktstart >= pktlen) { goto info_done; } } term = strchr(pkt, 0xa); if (!term || (term - start >= len)) { goto info_done; } *term = '\0'; n_players = atoi(pkt); pkt = term + 1; for (i = 0; i < n_players && pkt - start < len; i++) { pkt++; /* skip first byte (0x10) */ if (pkt - start >= len) { break; } player = (struct player *)calloc(1, sizeof(struct player)); term = strchr(pkt, 0x11); if (!term || (term - start >= len)) { free(player); break; } player->name = strndup(pkt, term - pkt); get_tribes2_player_type(player); pkt = term + 1; pkt++; /* skip 0x9 */ if (pkt - start >= len) { break; } term = strchr(pkt, 0x9); if (!term || (term - start >= len)) { free(player->name); free(player); break; } for (t = 0; t < n_teams; t++) { if ((term - pkt == strlen(teams[t]->name)) && (strncmp(pkt, teams[t]->name, term - pkt) == 0)) { break; } } if (t == n_teams) { player->team = -1; player->team_name = "Unassigned"; } else { player->team = t; player->team_name = teams[t]->name; } player->flags |= PLAYER_FLAG_DO_NOT_FREE_TEAM; pkt = term + 1; for (s = 0; *pkt != 0xa && pkt - start < len; pkt++) { str[s++] = *pkt; } str[s] = '\0'; player->frags = atoi(str); if (*pkt == 0xa) { pkt++; } *last_player = player; last_player = &player->next; } info_done: for (t = n_teams; t; ) { t--; teams[t]->next = server->players; server->players = teams[t]; } if (teams) { free(teams); } return (DONE_FORCE); } static const char GrPacketHead[] = { '\xc0', '\xde', '\xf1', '\x11' }; static char Dat2Reply1_2_10[] = { '\xf4', '\x03', '\x14', '\x02', '\x0a', '\x41', '\x02', '\x0a', '\x41', '\x00', '\x00', '\x78', '\x30', '\x63' }; static char Dat2Reply1_3[] = { '\xf4', '\x03', '\x14', '\x03', '\x05', '\x41', '\x03', '\x05', '\x41', '\x00', '\x00', '\x78', '\x30', '\x63' }; static char Dat2Reply1_4[] = { '\xf4', '\x03', '\x14', '\x04', '\x00', '\x41', '\x04', '\x00', '\x41', '\x00', '\x00', '\x78', '\x30', '\x63' }; //static char HDat2[]={'\xea','\x03','\x02','\x00','\x14'}; #define SHORT_GR_LEN 75 #define LONG_GR_LEN 500 #define UNKNOWN_VERSION 0 #define VERSION_1_2_10 1 #define VERSION_1_3 2 #define VERSION_1_4 3 query_status_t deal_with_ghostrecon_packet(struct qserver *server, char *pkt, int pktlen) { char str[256], StartFlag, *lpszIgnoreServerPlayer; char *lpszMission; unsigned int iIgnoreServerPlayer, iDedicatedServer, iUseStartTimer; unsigned short GrPayloadLen; int i; struct player *player; int iLen, iTemp; short sLen; int iSecsPlayed; long iSpawnType; int ServerVersion = UNKNOWN_VERSION; float flStartTimerSetPoint; debug(2, "deal_with_ghostrecon_packet %p, %d", server, pktlen); pkt[pktlen] = '\0'; /* * This function walks a packet that is recieved from a ghost recon server - default from port 2348. It does quite a few * sanity checks along the way as the structure is not documented. The packet is mostly binary in nature with many string * fields being variable in length, ie the length is listed foloowed by that many bytes. There are two structure arrays * that have an array size followed by structure size * number of elements (player name and player data). This routine * walks this packet and increments a pointer "pkt" to extract the info. */ if (server->server_name == NULL) { server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); } /* sanity check against packet */ if (memcmp(pkt, GrPacketHead, sizeof(GrPacketHead)) != 0) { server->server_name = strdup("Unknown Packet Header"); return (PKT_ERROR); } pkt += sizeof(GrPacketHead); StartFlag = pkt[0]; pkt += 1; if (StartFlag != 0x42) { server->server_name = strdup("Unknown Start Flag"); return (PKT_ERROR); } /* compare packet length recieved to included size - header info */ sLen = swap_short_from_little(pkt); pkt += 2; GrPayloadLen = pktlen - sizeof(GrPacketHead) - 3; // 3 = size slen + size start flag if (sLen != GrPayloadLen) { server->server_name = strdup("Packet Size Mismatch"); return (PKT_ERROR); } /* * Will likely need to verify and add to this "if" construct with every patch / add-on. */ if (memcmp(pkt, Dat2Reply1_2_10, sizeof(Dat2Reply1_2_10)) == 0) { ServerVersion = VERSION_1_2_10; } else if (memcmp(pkt, Dat2Reply1_3, sizeof(Dat2Reply1_3)) == 0) { ServerVersion = VERSION_1_3; } else if (memcmp(pkt, Dat2Reply1_4, sizeof(Dat2Reply1_4)) == 0) { ServerVersion = VERSION_1_4; } if (ServerVersion == UNKNOWN_VERSION) { server->server_name = strdup("Unknown GR Version"); return (PKT_ERROR); } switch (ServerVersion) { case VERSION_1_2_10: strcpy(str, "1.2.10"); pkt += sizeof(Dat2Reply1_2_10); break; case VERSION_1_3: strcpy(str, "1.3"); pkt += sizeof(Dat2Reply1_3); break; case VERSION_1_4: strcpy(str, "1.4"); pkt += sizeof(Dat2Reply1_4); break; } add_rule(server, "patch", str, NO_FLAGS); /* have player packet */ // Ghost recon has one of the player slots filled up with the server program itself. By default we will // drop the first player listed. This causes a bit of a mess here and below but makes for the best display // a user can specify -grs,ignoreserverplayer=no to override this behaviour. lpszIgnoreServerPlayer = get_param_value(server, "ignoreserverplayer", "yes"); for (i = 0; i < 4; i++) { str[i] = tolower(lpszIgnoreServerPlayer[i]); } if (strcmp(str, "yes") == 0) { iIgnoreServerPlayer = 1; } else { iIgnoreServerPlayer = 0; } pkt += 4; /* unknown */ // this is the first of many variable strings. get the length, // increment pointer over length, check for sanity, // get the string, increment the pointer over string (using length) iLen = swap_long_from_little(pkt); pkt += 4; if ((iLen < 1) || (iLen > SHORT_GR_LEN)) { server->server_name = strdup("Server Name too Long"); return (PKT_ERROR); } server->server_name = strndup(pkt, iLen); pkt += iLen; iLen = swap_long_from_little(pkt); pkt += 4; if ((iLen < 1) || (iLen > SHORT_GR_LEN)) { add_rule(server, "error", "Map Name too Long", NO_FLAGS); return (PKT_ERROR); } server->map_name = strndup(pkt, iLen); pkt += iLen; iLen = swap_long_from_little(pkt); pkt += 4; if ((iLen < 1) || (iLen > SHORT_GR_LEN)) { add_rule(server, "error", "Mission Name too Long", NO_FLAGS); return (PKT_ERROR); } /* mission does not make sense unless a coop game type. Since * we dont know that now, we will save the mission and set * the rule and free memory below when we know game type */ lpszMission = strndup(pkt, iLen); pkt += iLen; iLen = swap_long_from_little(pkt); pkt += 4; if ((iLen < 1) || (iLen > SHORT_GR_LEN)) { add_rule(server, "error", "Mission Type too Long", NO_FLAGS); return (PKT_ERROR); } add_nrule(server, "missiontype", pkt, iLen); pkt += iLen; if (pkt[1]) { add_rule(server, "password", "Yes", NO_FLAGS); } else { add_rule(server, "password", "No", NO_FLAGS); } pkt += 2; server->max_players = swap_long_from_little(pkt); pkt += 4; if (server->max_players > 36) { add_rule(server, "error", "Max players more then 36", NO_FLAGS); return (PKT_ERROR); } server->num_players = swap_long_from_little(pkt); pkt += 4; if (server->num_players > server->max_players) { add_rule(server, "error", "More then MAX Players", NO_FLAGS); return (PKT_ERROR); } if (iIgnoreServerPlayer) { // skip past first player server->num_players--; server->max_players--; iLen = swap_long_from_little(pkt); pkt += 4; pkt += iLen; } for (i = 0; i < server->num_players; i++) { // read each player name iLen = swap_long_from_little(pkt); pkt += 4; player = (struct player *)calloc(1, sizeof(struct player)); if ((iLen < 1) || (iLen > SHORT_GR_LEN)) { add_rule(server, "error", "Player Name too Long", NO_FLAGS); return (PKT_ERROR); } player->name = strndup(pkt, iLen); pkt += iLen; /* player name */ player->team = i; // tag so we can find this record when we have player dat. player->team_name = "Unassigned"; player->flags |= PLAYER_FLAG_DO_NOT_FREE_TEAM; player->frags = 0; player->next = server->players; server->players = player; } pkt += 17; iLen = swap_long_from_little(pkt); pkt += 4; if ((iLen < 1) || (iLen > SHORT_GR_LEN)) { add_rule(server, "error", "Version too Long", NO_FLAGS); return (PKT_ERROR); } strncpy(str, pkt, iLen); add_rule(server, "version", str, NO_FLAGS); pkt += iLen;/* version */ iLen = swap_long_from_little(pkt); pkt += 4; if ((iLen < 1) || (iLen > LONG_GR_LEN)) { add_rule(server, "error", "Mods too Long", NO_FLAGS); return (PKT_ERROR); } server->game = strndup(pkt, iLen); for (i = 0; i < (int)strlen(server->game) - 5; i++) { // clean the "/mods/" part from every entry if (memcmp(&server->game[i], "\\mods\\", 6) == 0) { server->game[i] = ' '; strcpy(&server->game[i + 1], &server->game[i + 6]); } } add_rule(server, "game", server->game, NO_FLAGS); pkt += iLen;/* mods */ iDedicatedServer = pkt[0]; if (iDedicatedServer) { add_rule(server, "dedicated", "Yes", NO_FLAGS); } else { add_rule(server, "dedicated", "No", NO_FLAGS); } pkt += 1; /* unknown */ iSecsPlayed = swap_float_from_little(pkt); add_rule(server, "timeplayed", play_time(iSecsPlayed, 2), NO_FLAGS); pkt += 4; /* time played */ switch (pkt[0]) { case 3: strcpy(str, "Joining"); break; case 4: strcpy(str, "Playing"); break; case 5: strcpy(str, "Debrief"); break; default: strcpy(str, "Undefined"); } add_rule(server, "status", str, NO_FLAGS); pkt += 1; pkt += 3; /* unknown */ switch (pkt[0]) { case 2: strcpy(str, "COOP"); break; case 3: strcpy(str, "SOLO"); break; case 4: strcpy(str, "TEAM"); break; default: sprintf(str, "UNKOWN %u", pkt[0]); break; } add_rule(server, "gamemode", str, NO_FLAGS); if (pkt[0] == 2) { add_rule(server, "mission", lpszMission, NO_FLAGS); } else { add_rule(server, "mission", "No Mission", NO_FLAGS); } free(lpszMission); pkt += 1; /* Game Mode */ pkt += 3; /* unknown */ iLen = swap_long_from_little(pkt); pkt += 4; if ((iLen < 1) || (iLen > LONG_GR_LEN)) { add_rule(server, "error", "MOTD too Long", NO_FLAGS); return (PKT_ERROR); } strncpy(str, pkt, sizeof(str)); str[sizeof(str) - 1] = 0; add_rule(server, "motd", str, NO_FLAGS); pkt += iLen;/* MOTD */ iSpawnType = swap_long_from_little(pkt); switch (iSpawnType) { case 0: strcpy(str, "None"); break; case 1: strcpy(str, "Individual"); break; case 2: strcpy(str, "Team"); break; case 3: strcpy(str, "Infinite"); break; default: strcpy(str, "Unknown"); } add_rule(server, "spawntype", str, NO_FLAGS); pkt += 4; /* spawn type */ iTemp = swap_float_from_little(pkt); add_rule(server, "gametime", play_time(iTemp, 2), NO_FLAGS); iTemp = iTemp - iSecsPlayed; if (iTemp <= 0) { iTemp = 0; } add_rule(server, "remainingtime", play_time(iTemp, 2), NO_FLAGS); pkt += 4; /* Game time */ iTemp = swap_long_from_little(pkt); if (iIgnoreServerPlayer) { iTemp--; } if (iTemp != server->num_players) { add_rule(server, "error", "Number of Players Mismatch", NO_FLAGS); } pkt += 4; /* player count 2 */ if (iIgnoreServerPlayer) { pkt += 5; // skip first player data } for (i = 0; i < server->num_players; i++) { // for each player get binary data player = server->players; // first we must find the player - lets look for the tag while (player && (player->team != i)) { player = player->next; } /* get to player - linked list is in reverse order */ if (player) { player->team = pkt[2]; switch (player->team) { case 1: player->team_name = "Red"; break; case 2: player->team_name = "Blue"; break; case 3: player->team_name = "Yellow"; break; case 4: player->team_name = "Green"; break; case 5: player->team_name = "Unassigned"; break; default: player->team_name = "Not Known"; break; } player->flags |= PLAYER_FLAG_DO_NOT_FREE_TEAM; player->deaths = pkt[1]; } pkt += 5; /* player data*/ } for (i = 0; i < 5; i++) { pkt += 8; /* team data who knows what they have in here */ } pkt += 1; iUseStartTimer = pkt[0];// UseStartTimer pkt += 1; iTemp = flStartTimerSetPoint = swap_float_from_little(pkt); // Start Timer Set Point pkt += 4; if (iUseStartTimer) { add_rule(server, "usestarttime", "Yes", NO_FLAGS); add_rule(server, "starttimeset", play_time(iTemp, 2), NO_FLAGS); } else { add_rule(server, "usestarttime", "No", NO_FLAGS); add_rule(server, "starttimeset", play_time(0, 2), NO_FLAGS); } if ((ServerVersion == VERSION_1_3) || // stuff added in patch 1.3 (ServerVersion == VERSION_1_4)) { iTemp = swap_float_from_little(pkt); // Debrief Time add_rule(server, "debrieftime", play_time(iTemp, 2), NO_FLAGS); pkt += 4; iTemp = swap_float_from_little(pkt);// Respawn Min add_rule(server, "respawnmin", play_time(iTemp, 2), NO_FLAGS); pkt += 4; iTemp = swap_float_from_little(pkt);// Respawn Max add_rule(server, "respawnmax", play_time(iTemp, 2), NO_FLAGS); pkt += 4; iTemp = swap_float_from_little(pkt);// Respawn Invulnerable add_rule(server, "respawnsafe", play_time(iTemp, 2), NO_FLAGS); pkt += 4; } else { add_rule(server, "debrieftime", "Undefined", NO_FLAGS); add_rule(server, "respawnmin", "Undefined", NO_FLAGS); add_rule(server, "respawnmax", "Undefined", NO_FLAGS); add_rule(server, "respawnsafe", "Undefined", NO_FLAGS); } pkt += 4; // 4 iTemp = pkt[0]; // Spawn Count if ((iSpawnType == 1) || (iSpawnType == 2)) { /* Individual or team */ sprintf(str, "%u", iTemp); } else { /* else not used */ sprintf(str, "%u", 0); } add_rule(server, "spawncount", str, NO_FLAGS); pkt += 1; // 5 pkt += 4; // 9 iTemp = pkt[0]; // Allow Observers if (iTemp) { strcpy(str, "Yes"); } else { /* else not used */ strcpy(str, "No"); } add_rule(server, "allowobservers", str, NO_FLAGS); pkt += 1; // 10 pkt += 3; // 13 // pkt += 13; if (iUseStartTimer) { iTemp = swap_float_from_little(pkt);// Start Timer Count add_rule(server, "startwait", play_time(iTemp, 2), NO_FLAGS); } else { add_rule(server, "startwait", play_time(0, 2), NO_FLAGS); } pkt += 4; //17 iTemp = pkt[0]; // IFF switch (iTemp) { case 0: strcpy(str, "None"); break; case 1: strcpy(str, "Reticule"); break; case 2: strcpy(str, "Names"); break; default: strcpy(str, "Unknown"); break; } add_rule(server, "iff", str, NO_FLAGS); pkt += 1; // 18 iTemp = pkt[0]; // Threat Indicator if (iTemp) { add_rule(server, "ti", "ON ", NO_FLAGS); } else { add_rule(server, "ti", "OFF", NO_FLAGS); } pkt += 1; // 19 pkt += 5; // 24 iLen = swap_long_from_little(pkt); pkt += 4; if ((iLen < 1) || (iLen > SHORT_GR_LEN)) { add_rule(server, "error", "Restrictions too Long", NO_FLAGS); return (PKT_ERROR); } add_rule(server, "restrict", pkt, NO_FLAGS); pkt += iLen;/* restrictions */ pkt += 23; /* * if ( ghostrecon_debug) print_packet( pkt, GrPayloadLen); */ return (DONE_FORCE); } char * find_ravenshield_game(char *gameno) { switch (atoi(gameno)) { case 8: return (strdup("Team Deathmatch")); break; case 13: return (strdup("Deathmatch")); break; case 14: return (strdup("Team Deathmatch")); break; case 15: return (strdup("Bomb")); break; case 16: return (strdup("Escort Pilot")); break; default: // 1.50 and above actually uses a string so // return that return (strdup(gameno)); break; } } char * find_savage_game(char *gametype) { if (0 == strcmp("RTSS", gametype)) { return (strdup("RTSS")); } else { return (strdup("Unknown")); } } query_status_t deal_with_ravenshield_packet(struct qserver *server, char *rawpkt, int pktlen) { char *s, *key, *value; debug(2, "deal_with_ravenshield_packet %p, %d", server, pktlen); server->n_servers++; if (NULL == server->server_name) { server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); } else { gettimeofday(&server->packet_time1, NULL); } rawpkt[pktlen] = '\0'; s = rawpkt; while (*s) { // Find the seperator while (*s && *s != '\xB6') { s++; } if (!*s) { // Hit the end no more break; } // key start key = ++s; while (*s && *s != ' ') { s++; } if (*s != ' ') { // malformed break; } *s++ = '\0'; // key end // value start value = s; while (*s && *s != '\xB6') { s++; } if (*s == '\xB6') { *(s - 1) = '\0'; } // Decode current key par if (0 == strcmp("A1", key)) { // Max players server->max_players = atoi(value); } else if (0 == strcmp("A2", key)) { // TeamKillerPenalty add_rule(server, "TeamKillerPenalty", value, NO_FLAGS); } else if (0 == strcmp("B1", key)) { // Current players server->num_players = atoi(value); } else if (0 == strcmp("B2", key)) { // AllowRadar add_rule(server, "AllowRadar", value, NO_FLAGS); } else if (0 == strcmp("D2", key)) { // Version info add_rule(server, "Version", value, NO_FLAGS); } else if (0 == strcmp("E1", key)) { // Current map server->map_name = strdup(value); } else if (0 == strcmp("E2", key)) { // Unknown } else if (0 == strcmp("F1", key)) { // Game type server->game = find_ravenshield_game(value); add_rule(server, server->type->game_rule, server->game, NO_FLAGS); } else if (0 == strcmp("F2", key)) { // Unknown } else if (0 == strcmp("G1", key)) { // Password add_rule(server, "Password", value, NO_FLAGS); } else if (0 == strcmp("G2", key)) { // Query port } else if (0 == strcmp("H1", key)) { // Unknown } else if (0 == strcmp("H2", key)) { // Number of Terrorists add_rule(server, "nbTerro", value, NO_FLAGS); } else if (0 == strcmp("I1", key)) { // Server name server->server_name = strdup(value); } else if (0 == strcmp("I2", key)) { // Unknown } else if (0 == strcmp("J1", key)) { // Game Type Order // Not pretty ignore for now //add_rule( server, "Game Type Order", value, NO_FLAGS ); } else if (0 == strcmp("J2", key)) { // RotateMap add_rule(server, "RotateMap", value, NO_FLAGS); } else if (0 == strcmp("K1", key)) { // Map Cycle // Not pretty ignore for now //add_rule( server, "Map Cycle", value, NO_FLAGS ); } else if (0 == strcmp("K2", key)) { // Force First Person Weapon add_rule(server, "ForceFPersonWeapon", value, NO_FLAGS); } else if (0 == strcmp("L1", key)) { // Players names int player_number = 0; char *n = value; if (*n == '/') { // atleast 1 player n++; while (*n && *n != '\xB6') { char *player_name = n; while (*n && *n != '/' && *n != '\xB6') { n++; } if (*n == '/') { *n++ = '\0'; } else if (*n == '\xB6') { *(n - 1) = '\0'; } if (0 != strlen(player_name)) { struct player *player = add_player(server, player_number); if (NULL != player) { player->name = strdup(player_name); } player_number++; } } } } else if (0 == strcmp("L3", key)) { // PunkBuster state add_rule(server, "PunkBuster", value, NO_FLAGS); } else if (0 == strcmp("M1", key)) { // Players times int player_number = 0; char *n = value; if (*n == '/') { // atleast 1 player n++; while (*n && *n != '\xB6') { char *time = n; while (*n && *n != '/' && *n != '\xB6') { n++; } if (*n == '/') { *n++ = '\0'; } else if (*n == '\xB6') { *(n - 1) = '\0'; } if (0 != strlen(time)) { int mins, seconds; if (2 == sscanf(time, "%d:%d", &mins, &seconds)) { struct player *player = get_player_by_number(server, player_number); if (NULL != player) { player->connect_time = mins * 60 + seconds; } } player_number++; } } } } else if (0 == strcmp("N1", key)) { // Players ping int player_number = 0; char *n = value; if (*n == '/') { // atleast 1 player n++; while (*n && *n != '\xB6') { char *ping = n; while (*n && *n != '/' && *n != '\xB6') { n++; } if (*n == '/') { *n++ = '\0'; } else if (*n == '\xB6') { *(n - 1) = '\0'; } if (0 != strlen(ping)) { struct player *player = get_player_by_number(server, player_number); if (NULL != player) { player->ping = atoi(ping); } player_number++; } } } } else if (0 == strcmp("O1", key)) { // Players fags int player_number = 0; char *n = value; if (*n == '/') { // atleast 1 player n++; while (*n && *n != '\xB6') { char *frags = n; while (*n && *n != '/' && *n != '\xB6') { n++; } if (*n == '/') { *n++ = '\0'; } else if (*n == '\xB6') { *(n - 1) = '\0'; } if (0 != strlen(frags)) { struct player *player = get_player_by_number(server, player_number); if (NULL != player) { player->frags = atoi(frags); } player_number++; } } } } else if (0 == strcmp("P1", key)) { // Game port // Not pretty ignore for now /* * change_server_port( server, atoi( value ), 0 ); */ } else if (0 == strcmp("Q1", key)) { // RoundsPerMatch add_rule(server, "RoundsPerMatch", value, NO_FLAGS); } else if (0 == strcmp("R1", key)) { // RoundTime add_rule(server, "RoundTime", value, NO_FLAGS); } else if (0 == strcmp("S1", key)) { // BetweenRoundTime add_rule(server, "BetweenRoundTime", value, NO_FLAGS); } else if (0 == strcmp("T1", key)) { // BombTime add_rule(server, "BombTime", value, NO_FLAGS); } else if (0 == strcmp("W1", key)) { // ShowNames add_rule(server, "ShowNames", value, NO_FLAGS); } else if (0 == strcmp("X1", key)) { // InternetServer add_rule(server, "InternetServer", value, NO_FLAGS); } else if (0 == strcmp("Y1", key)) { // FriendlyFire add_rule(server, "FriendlyFire", value, NO_FLAGS); } else if (0 == strcmp("Z1", key)) { // Autobalance add_rule(server, "Autobalance", value, NO_FLAGS); } } return (DONE_FORCE); } query_status_t deal_with_savage_packet(struct qserver *server, char *rawpkt, int pktlen) { char *s, *key, *value, *end; debug(2, "deal_with_savage_packet %p, %d", server, pktlen); server->n_servers++; if (NULL == server->server_name) { server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); } else { gettimeofday(&server->packet_time1, NULL); } rawpkt[pktlen] = '\0'; end = s = rawpkt; end += pktlen; while (*s) { // Find the seperator while (s <= end && *s != '\xFF') { s++; } if (s >= end) { // Hit the end no more break; } // key start key = ++s; while (s < end && *s != '\xFE') { s++; } if (*s != '\xFE') { // malformed break; } *s++ = '\0'; // key end // value start value = s; while (s < end && *s != '\xFF') { s++; } if (*s == '\xFF') { *s = '\0'; } //fprintf( stderr, "'%s' = '%s'\n", key, value ); // Decode current key par if (0 == strcmp("cmax", key)) { // Max players server->max_players = atoi(value); } else if (0 == strcmp("cnum", key)) { // Current players server->num_players = atoi(value); } else if (0 == strcmp("bal", key)) { // Balance add_rule(server, "Balance", value, NO_FLAGS); } else if (0 == strcmp("world", key)) { // Current map server->map_name = strdup(value); } else if (0 == strcmp("gametype", key)) { // Game type server->game = find_savage_game(value); add_rule(server, server->type->game_rule, server->game, NO_FLAGS); } else if (0 == strcmp("pure", key)) { // Pure add_rule(server, "Pure", value, NO_FLAGS); } else if (0 == strcmp("time", key)) { // Current game time add_rule(server, "Time", value, NO_FLAGS); } else if (0 == strcmp("notes", key)) { // Notes add_rule(server, "Notes", value, NO_FLAGS); } else if (0 == strcmp("needcmdr", key)) { // Need Commander add_rule(server, "Need Commander", value, NO_FLAGS); } else if (0 == strcmp("name", key)) { // Server name server->server_name = strdup(value); } else if (0 == strcmp("fw", key)) { // Firewalled add_rule(server, "Firewalled", value, NO_FLAGS); } else if (0 == strcmp("players", key)) { // Players names int player_number = 0; int team_number = 1; char *team_name, *player_name, *n; n = team_name = value; // team name n++; while (*n && *n != '\x0a') { n++; } if (*n != '\x0a') { // Broken data break; } *n = '\0'; player_name = ++n; while (*n) { while (*n && *n != '\x0a') { n++; } if (*n != '\x0a') { // Broken data break; } *n = '\0'; n++; if (0 == strncmp("Team ", player_name, 5)) { team_name = player_name; team_number++; } else { if (0 != strlen(player_name)) { struct player *player = add_player(server, player_number); if (NULL != player) { player->name = strdup(player_name); player->team = team_number; player->team_name = strdup(team_name); } player_number++; } } player_name = n; } } *s = '\xFF'; } return (DONE_FORCE); } query_status_t deal_with_farcry_packet(struct qserver *server, char *rawpkt, int pktlen) { char *s, *key, *value, *end; debug(2, "deal_with_farcry_packet %p, %d", server, pktlen); server->n_servers++; if (NULL == server->server_name) { server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); } else { gettimeofday(&server->packet_time1, NULL); } rawpkt[pktlen] = '\0'; end = s = rawpkt; end += pktlen; while (*s) { // Find the seperator while (s <= end && *s != '\xFF') { s++; } if (s >= end) { // Hit the end no more break; } // key start key = ++s; while (s < end && *s != '\xFE') { s++; } if (*s != '\xFE') { // malformed break; } *s++ = '\0'; // key end // value start value = s; while (s < end && *s != '\xFF') { s++; } if (*s == '\xFF') { *s = '\0'; } //fprintf( stderr, "'%s' = '%s'\n", key, value ); // Decode current key par if (0 == strcmp("cmax", key)) { // Max players server->max_players = atoi(value); } else if (0 == strcmp("cnum", key)) { // Current players server->num_players = atoi(value); } else if (0 == strcmp("bal", key)) { // Balance add_rule(server, "Balance", value, NO_FLAGS); } else if (0 == strcmp("world", key)) { // Current map server->map_name = strdup(value); } else if (0 == strcmp("gametype", key)) { // Game type server->game = find_savage_game(value); add_rule(server, server->type->game_rule, server->game, NO_FLAGS); } else if (0 == strcmp("pure", key)) { // Pure add_rule(server, "Pure", value, NO_FLAGS); } else if (0 == strcmp("time", key)) { // Current game time add_rule(server, "Time", value, NO_FLAGS); } else if (0 == strcmp("notes", key)) { // Notes add_rule(server, "Notes", value, NO_FLAGS); } else if (0 == strcmp("needcmdr", key)) { // Need Commander add_rule(server, "Need Commander", value, NO_FLAGS); } else if (0 == strcmp("name", key)) { // Server name server->server_name = strdup(value); } else if (0 == strcmp("fw", key)) { // Firewalled add_rule(server, "Firewalled", value, NO_FLAGS); } else if (0 == strcmp("players", key)) { // Players names int player_number = 0; int team_number = 1; char *team_name, *player_name, *n; n = team_name = value; // team name n++; while (*n && *n != '\x0a') { n++; } if (*n != '\x0a') { // Broken data break; } *n = '\0'; player_name = ++n; while (*n) { while (*n && *n != '\x0a') { n++; } if (*n != '\x0a') { // Broken data break; } *n = '\0'; n++; if (0 == strncmp("Team ", player_name, 5)) { team_name = player_name; team_number++; } else { if (0 != strlen(player_name)) { struct player *player = add_player(server, player_number); if (NULL != player) { player->name = strdup(player_name); player->team = team_number; player->team_name = strdup(team_name); } player_number++; } } player_name = n; } } *s = '\xFF'; } return (DONE_FORCE); } /* postions of map name, player name (in player substring), zero-based */ #define BFRIS_MAP_POS 18 #define BFRIS_PNAME_POS 11 query_status_t deal_with_bfris_packet(struct qserver *server, char *rawpkt, int pktlen) { int i, player_data_pos, nplayers; SavedData *sdata; unsigned char *saved_data; int saved_data_size; debug(2, "deal_with_bfris_packet %p, %d", server, pktlen); server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); /* add to the data previously saved */ sdata = &server->saved_data; if (!sdata->data) { sdata->data = (char *)malloc(pktlen); } else { sdata->data = (char *)realloc(sdata->data, sdata->datalen + pktlen); } memcpy(sdata->data + sdata->datalen, rawpkt, pktlen); sdata->datalen += pktlen; saved_data = (unsigned char *)sdata->data; saved_data_size = sdata->datalen; /* after we get the server portion of the data, server->game != NULL */ if (!server->game) { /* server data goes up to map name */ if (sdata->datalen <= BFRIS_MAP_POS) { return (INPROGRESS); } /* see if map name is complete */ player_data_pos = 0; for (i = BFRIS_MAP_POS; i < saved_data_size; i++) { if (saved_data[i] == '\0') { player_data_pos = i + 1; /* data must extend beyond map name */ if (saved_data_size <= player_data_pos) { return (INPROGRESS); } break; } } /* did we find beginning of player data? */ if (!player_data_pos) { return (INPROGRESS); } /* now we can go ahead and fill in server data */ server->map_name = strdup((char *)saved_data + BFRIS_MAP_POS); server->max_players = saved_data[12]; server->protocol_version = saved_data[11]; /* save game type */ switch (saved_data[13] & 15) { case 0: server->game = "FFA"; break; case 5: server->game = "Rover"; break; case 6: server->game = "Occupation"; break; case 7: server->game = "SPAAL"; break; case 8: server->game = "CTF"; break; default: server->game = "unknown"; break; } server->flags |= FLAG_DO_NOT_FREE_GAME; add_rule(server, server->type->game_rule, server->game, NO_FLAGS); if (get_server_rules) { char buf[24]; /* server revision */ sprintf(buf, "%d", (unsigned int)saved_data[11]); add_rule(server, "Revision", buf, NO_FLAGS); /* latency */ sprintf(buf, "%d", (unsigned int)saved_data[10]); add_rule(server, "Latency", buf, NO_FLAGS); /* player allocation */ add_rule(server, "Allocation", saved_data[13] & 16 ? "Automatic" : "Manual", NO_FLAGS); } } /* If we got this far, we know the data saved goes at least to the start of * the player information, and that the server data is taken care of. */ /* start of player data */ player_data_pos = BFRIS_MAP_POS + strlen((char *)saved_data + BFRIS_MAP_POS) + 1; /* ensure all player data have arrived */ nplayers = 0; while (saved_data[player_data_pos] != '\0') { player_data_pos += BFRIS_PNAME_POS; /* does player data extend to player name? */ if (saved_data_size <= player_data_pos + 1) { return (INPROGRESS); } /* does player data extend to end of player name? */ for (i = 0; player_data_pos + i < saved_data_size; i++) { if (saved_data_size == player_data_pos + i + 1) { return (INPROGRESS); } if (saved_data[player_data_pos + i] == '\0') { player_data_pos += i + 1; nplayers++; break; } } } /* all player data are complete */ server->num_players = nplayers; if (get_player_info) { /* start of player data */ player_data_pos = BFRIS_MAP_POS + strlen((char *)saved_data + BFRIS_MAP_POS) + 1; for (i = 0; i < nplayers; i++) { struct player *player; player = add_player(server, saved_data[player_data_pos]); player->ship = saved_data[player_data_pos + 1]; player->ping = saved_data[player_data_pos + 2]; player->frags = saved_data[player_data_pos + 3]; player->team = saved_data[player_data_pos + 4]; switch (player->team) { case 0: player->team_name = "silver"; break; case 1: player->team_name = "red"; break; case 2: player->team_name = "blue"; break; case 3: player->team_name = "green"; break; case 4: player->team_name = "purple"; break; case 5: player->team_name = "yellow"; break; case 6: player->team_name = "cyan"; break; default: player->team_name = "unknown"; break; } player->flags |= PLAYER_FLAG_DO_NOT_FREE_TEAM; player->room = saved_data[player_data_pos + 5]; /* score is little-endian integer */ player->score = saved_data[player_data_pos + 7] + (saved_data[player_data_pos + 8] << 8) + (saved_data[player_data_pos + 9] << 16) + (saved_data[player_data_pos + 10] << 24); /* for archs with > 4-byte int */ if (player->score & 0x80000000) { player->score = -(~(player->score)) - 1; } player_data_pos += BFRIS_PNAME_POS; player->name = strdup((char *)saved_data + player_data_pos); player_data_pos += strlen(player->name) + 1; } } server->server_name = BFRIS_SERVER_NAME; return (DONE_FORCE); } struct rule * add_uchar_rule(struct qserver *server, char *key, unsigned char value) { char buf[24]; sprintf(buf, "%u", (unsigned)value); return (add_rule(server, key, buf, NO_FLAGS)); } query_status_t deal_with_descent3_packet(struct qserver *server, char *rawpkt, int pktlen) { char *pkt; char buf[24]; debug(2, "deal_with_descent3_packet %p, %d", server, pktlen); if (server->server_name == NULL) { server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); } if (pktlen < 4) { fprintf(stderr, "short descent3 packet\n"); print_packet(server, rawpkt, pktlen); return (PKT_ERROR); } /* 'info' response */ if (rawpkt[1] == 0x1f) { if (server->server_name != NULL) { return (PKT_ERROR); } pkt = &rawpkt[0x15]; server->server_name = strdup(pkt); pkt += strlen(pkt) + 2; server->map_name = strdup(pkt); /* mission name (blah.mn3) */ pkt += strlen(pkt) + 2; add_rule(server, "level_name", pkt, NO_FLAGS); pkt += strlen(pkt) + 2; add_rule(server, "gametype", pkt, NO_FLAGS); pkt += strlen(pkt) + 1; sprintf(buf, "%hu", swap_short_from_little(pkt)); add_rule(server, "level_num", buf, NO_FLAGS); pkt += 2; server->num_players = swap_short_from_little(pkt); pkt += 2; server->max_players = swap_short_from_little(pkt); pkt += 2; /* unknown/undecoded fields.. stuff like permissible, banned items/ships, etc */ add_uchar_rule(server, "u0", pkt[0]); add_uchar_rule(server, "u1", pkt[1]); add_uchar_rule(server, "u2", pkt[2]); add_uchar_rule(server, "u3", pkt[3]); add_uchar_rule(server, "u4", pkt[4]); add_uchar_rule(server, "u5", pkt[5]); add_uchar_rule(server, "u6", pkt[6]); add_uchar_rule(server, "u7", pkt[7]); add_uchar_rule(server, "u8", pkt[8]); add_uchar_rule(server, "randpowerup", (unsigned char)!(pkt[4] & 1));/* * randomize powerup spawn */ add_uchar_rule(server, "acccollisions", (unsigned char)((pkt[5] & 4) > 0)); /* accurate collision detection */ add_uchar_rule(server, "brightships", (unsigned char)((pkt[5] & 16) > 0)); /* bright player ships */ add_uchar_rule(server, "mouselook", (unsigned char)((pkt[6] & 1) > 0)); /* * mouselook enabled */ sprintf(buf, "%s%s", (pkt[4] & 16) ? "PP" : "CS", (pkt[6] & 1) ? "-ML" : ""); add_rule(server, "servertype", buf, NO_FLAGS); sprintf(buf, "%hhu", pkt[9]); add_rule(server, "difficulty", buf, NO_FLAGS); /* unknown/undecoded fields after known flags removed */ add_uchar_rule(server, "x4", (unsigned char)(pkt[4] & ~(1 + 16))); add_uchar_rule(server, "x5", (unsigned char)(pkt[5] & ~(4 + 16))); add_uchar_rule(server, "x6", (unsigned char)(pkt[6] & ~1)); if (get_player_info && server->num_players) { server->next_player_info = 0; send_player_request_packet(server); return (INPROGRESS); } } /* MP_PLAYERLIST_DATA */ else if (rawpkt[1] == 0x73) { struct player *player; struct player **last_player = &server->players; if (server->players != NULL) { return (PKT_ERROR); } pkt = &rawpkt[0x4]; while (*pkt) { player = (struct player *)calloc(1, sizeof(struct player)); player->name = strdup(pkt); pkt += strlen(pkt) + 1; *last_player = player; last_player = &player->next; } server->next_player_info = NO_PLAYER_INFO; } else { fprintf(stderr, "unknown d3 packet\n"); print_packet(server, rawpkt, pktlen); } return (DONE_FORCE); } #define EYE_NAME_MASK 1 #define EYE_TEAM_MASK 2 #define EYE_SKIN_MASK 4 #define EYE_SCORE_MASK 8 #define EYE_PING_MASK 16 #define EYE_TIME_MASK 32 query_status_t deal_with_eye_packet(struct qserver *server, char *rawpkt, int pktlen) { char *next, *end, *value, *key; struct player **last_player; unsigned char pkt_index, pkt_max; unsigned int pkt_id; debug(2, "deal_with_eye_packet %p, %d", server, pktlen); if (pktlen < 4) { return (PKT_ERROR); } if ((rawpkt[0] != 'E') || (rawpkt[1] != 'Y') || (rawpkt[2] != 'E')) { return (PKT_ERROR); } server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); end = rawpkt + pktlen; pkt_index = rawpkt[3] - '0'; if ((pktlen == 1364) || (pkt_index != 1)) { /* fragmented packet */ SavedData *sdata; /* EYE doesn't tell us how many packets to expect. Two packets * is enough for 100+ players on a BF1942 server with standard * server rules. */ pkt_max = 2; memcpy(&pkt_id, &rawpkt[pktlen - 4], 4); if (server->saved_data.data == NULL) { sdata = &server->saved_data; } else { sdata = (SavedData *)calloc(1, sizeof(SavedData)); sdata->next = server->saved_data.next; server->saved_data.next = sdata; } sdata->pkt_index = pkt_index - 1; sdata->pkt_max = pkt_max; sdata->pkt_id = pkt_id; if (pkt_index == 1) { sdata->datalen = pktlen - 4; } else { sdata->datalen = pktlen - 8; } sdata->data = (char *)malloc(sdata->datalen); if (NULL == sdata->data) { return (MEM_ERROR); } if (pkt_index == 1) { memcpy(sdata->data, &rawpkt[0], sdata->datalen); } else { memcpy(sdata->data, &rawpkt[4], sdata->datalen); } /* combine_packets will call us recursively */ return (combine_packets(server)); } value = dup_n1string(&rawpkt[4], end, &next); if (value == NULL) { return (MEM_ERROR); } add_rule(server, "gamename", value, NO_VALUE_COPY); value = dup_n1string(next, end, &next); if (value == NULL) { return (MEM_ERROR); } add_rule(server, "hostport", value, NO_VALUE_COPY); value = dup_n1string(next, end, &next); if (value == NULL) { return (MEM_ERROR); } server->server_name = value; value = dup_n1string(next, end, &next); if (value == NULL) { return (MEM_ERROR); } server->game = value; add_rule(server, server->type->game_rule, value, NO_FLAGS); value = dup_n1string(next, end, &next); if (value == NULL) { return (MEM_ERROR); } server->map_name = value; value = dup_n1string(next, end, &next); if (value == NULL) { return (MEM_ERROR); } add_rule(server, "_version", value, NO_VALUE_COPY); value = dup_n1string(next, end, &next); if (value == NULL) { return (MEM_ERROR); } add_rule(server, "_password", value, NO_VALUE_COPY); value = dup_n1string(next, end, &next); if (value == NULL) { return (MEM_ERROR); } server->num_players = atoi(value); free(value); value = dup_n1string(next, end, &next); if (value == NULL) { return (MEM_ERROR); } server->max_players = atoi(value); free(value); /* rule1,value1,rule2,value2, ... empty string */ do { key = dup_n1string(next, end, &next); if (key == NULL) { break; } else if (key[0] == '\0') { free(key); break; } value = dup_n1string(next, end, &next); if (value == NULL) { free(key); break; } add_rule(server, key, value, NO_VALUE_COPY | NO_KEY_COPY); } while (1); /* [mask1][mask2]... */ last_player = &server->players; while (next && next < end) { struct player *player; unsigned mask = *((unsigned char *)next); next++; if (next >= end) { break; } if (mask == 0) { break; } player = (struct player *)calloc(1, sizeof(struct player)); if (player == NULL) { break; } if (mask & EYE_NAME_MASK) { player->name = dup_n1string(next, end, &next); //fprintf( stderr, "Player '%s'\n", player->name ); if (player->name == NULL) { break; } } if (mask & EYE_TEAM_MASK) { value = dup_n1string(next, end, &next); if (value == NULL) { break; } if (isdigit((unsigned char)value[0])) { player->team = atoi(value); free(value); } else { player->team_name = value; } } if (mask & EYE_SKIN_MASK) { player->skin = dup_n1string(next, end, &next); if (player->skin == NULL) { break; } } if (mask & EYE_SCORE_MASK) { value = dup_n1string(next, end, &next); if (value == NULL) { break; } player->score = atoi(value); player->frags = player->score; free(value); } if (mask & EYE_PING_MASK) { value = dup_n1string(next, end, &next); if (value == NULL) { break; } player->ping = atoi(value); free(value); } if (mask & EYE_TIME_MASK) { value = dup_n1string(next, end, &next); if (value == NULL) { break; } player->connect_time = atoi(value); free(value); } *last_player = player; last_player = &player->next; //fprintf( stderr, "Player '%s'\n", player->name ); } return (DONE_FORCE); } static const char hl2_statusresponse[] = "\xFF\xFF\xFF\xFF\x49"; static const char hl2_playersresponse[] = "\xFF\xFF\xFF\xFF\x44"; static const char hl2_rulesresponse[] = "\xFF\xFF\xFF\xFF\x45"; static const int hl2_response_size = sizeof(hl2_statusresponse) - 1; #define HL2_STATUS 1 #define HL2_PLAYERS 2 #define HL2_RULES 3 query_status_t deal_with_hl2_packet(struct qserver *server, char *rawpkt, int pktlen) { char *ptr = rawpkt; char *end = rawpkt + pktlen; char temp[512]; int type = 0; unsigned char protocolver = 0; int n_sent = 0; debug(2, "deal_with_hl2_packet %p, %d", server, pktlen); server->n_servers++; if (server->server_name == NULL) { server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); } else { gettimeofday(&server->packet_time1, NULL); } // Check if correct reply if (pktlen < hl2_response_size) { malformed_packet(server, "short response type"); return (PKT_ERROR); } else { if (0 == memcmp(hl2_statusresponse, ptr, hl2_response_size)) { if (pktlen < hl2_response_size + 20) { malformed_packet(server, "short packet"); return (PKT_ERROR); } type = HL2_STATUS; } else if (0 == memcmp(hl2_playersresponse, ptr, hl2_response_size)) { type = HL2_PLAYERS; } else if (0 == memcmp(hl2_rulesresponse, ptr, hl2_response_size)) { type = HL2_RULES; } else { malformed_packet(server, "unknown response"); return (PKT_ERROR); } } // header ptr += hl2_response_size; switch (type) { case HL2_STATUS: // protocol version protocolver = *ptr; ptr++; debug(2, "protocol: 0x%02X", protocolver); // Commented out till out of beta /* * if( '\x02' != protocolver ) * { * malformed_packet(server, "protocol version != 0x02"); * return PKT_ERROR; * } */ server->protocol_version = protocolver; sprintf(temp, "%d", protocolver); add_rule(server, "protocol", temp, NO_FLAGS); // server name server->server_name = strdup(ptr); ptr += strlen(ptr) + 1; // map server->map_name = strdup(ptr); ptr += strlen(ptr) + 1; // gamedir server->game = strdup(ptr); add_rule(server, "gamedir", ptr, NO_FLAGS); ptr += strlen(ptr) + 1; // description add_rule(server, "description", ptr, NO_FLAGS); ptr += strlen(ptr) + 1; // appid ptr += 2; // num players server->num_players = *ptr; ptr++; // max players server->max_players = *ptr; ptr++; // bot players sprintf(temp, "%hhu", (*ptr)); add_rule(server, "bot_players", temp, NO_FLAGS); ptr++; // dedicated if ('d' == *ptr) { add_rule(server, "sv_type", "dedicated", NO_FLAGS); } else if ('l' == *ptr) { add_rule(server, "sv_type", "listen", NO_FLAGS); } else { char tmp[2] = { *ptr, '\0' } ; add_rule(server, "sv_type", tmp, NO_FLAGS); } ptr++; // OS if ('l' == *ptr) { add_rule(server, "sv_os", "linux", NO_FLAGS); } else if ('w' == *ptr) { add_rule(server, "sv_os", "windows", NO_FLAGS); } else { char tmp[2] = { *ptr, '\0' } ; add_rule(server, "sv_os", tmp, NO_FLAGS); } ptr++; // passworded add_rule(server, "sv_password", *ptr ? "1" : "0", NO_FLAGS); ptr++; // secure add_rule(server, "secure", *ptr ? "1" : "0", NO_FLAGS); ptr++; // send the other request packets if wanted if (get_server_rules) { int requests = server->n_requests; debug(3, "send_rule_request_packet7"); send_rule_request_packet(server); server->n_requests = requests; // prevent wrong ping n_sent++; } else if (get_player_info) { int requests = server->n_requests; send_player_request_packet(server); server->n_requests = requests; // prevent wrong ping n_sent++; } break; case HL2_RULES: // num_players ptr++; // max_players ptr++; while (ptr < end) { char *var = ptr; char *val; ptr += strlen(var) + 1; val = ptr; ptr += strlen(val) + 1; add_rule(server, var, val, NO_FLAGS); } if (get_player_info) { send_player_request_packet(server); n_sent++; } break; case HL2_PLAYERS: // num_players ptr++; while (ptr < end) { struct player *player = add_player(server, server->n_player_info); // player no ptr++; // name player->name = strdup(ptr); ptr += strlen(ptr) + 1; // frags player->frags = swap_long_from_little(ptr); ptr += 4; // time player->connect_time = swap_float_from_little(ptr); ptr += 4; } break; default: malformed_packet(server, "unknown response"); return (PKT_ERROR); } return ((0 == n_sent) ? DONE_FORCE : INPROGRESS); } query_status_t deal_with_gamespy_master_response(struct qserver *server, char *rawpkt, int pktlen) { char *data; int len; int is_binary_packet = 0; char *ipptr, *portptr; char ipstr[16]; char portstr[6]; unsigned int ipaddr; unsigned short port; server->master_pkt_len = 0; server->master_pkt = NULL; debug(2, "deal_with_gamespy_master_response %p, %d", server, pktlen); if (server->saved_data.data == NULL) { debug(2, "gamespy master response first packet"); server->saved_data.data = (char *)malloc(pktlen); if (server->saved_data.data == NULL) { debug(0, "Failed to malloc memory for saved data"); return (MEM_ERROR); } } else { debug(2, "gamespy master response intermediate packet"); server->saved_data.data = (char *)realloc(server->saved_data.data, server->saved_data.datalen + pktlen); if (server->saved_data.data == NULL) { debug(0, "Failed to realloc memory for saved data"); return (MEM_ERROR); } } memcpy(server->saved_data.data + server->saved_data.datalen, rawpkt, pktlen); server->saved_data.datalen += pktlen; if ((pktlen == 0) || ((pktlen > 7) && (strncmp(rawpkt + pktlen - 7, "\\final\\", 7) == 0))) { debug(2, "gamespy master response final packet"); // the header is in the form \basic\\secure\XXXXXX if (strncmp(server->saved_data.data, "\\basic\\\\secure\\", 15) != 0) { malformed_packet(server, "gamespy master response unknown header"); return (PKT_ERROR); } server->server_name = GAMESPY_MASTER_NAME; data = server->saved_data.data + 21; len = server->saved_data.datalen - 28; if (strncmp(data, "\\ip\\", 4) != 0) { is_binary_packet = 1; } if (is_binary_packet) { debug(1, "gamespy master response uses binary packet format"); server->master_pkt_len = len; server->master_pkt = (char *)malloc(len); if (server->master_pkt == NULL) { debug(0, "Failed to malloc memory for internal master packet"); return (MEM_ERROR); } memcpy(server->master_pkt, data, server->master_pkt_len); return (DONE_FORCE); } debug(1, "gamespy master response uses text packet format"); while (len != 0) { // ip entry if (strncmp(data, "\\ip\\", 4) == 0) { debug(1, "gamespy master response address entry"); data += 4; len -= 4; server->master_pkt_len += 6; if (server->master_pkt == NULL) { server->master_pkt = (char *)malloc(server->master_pkt_len); if (server->master_pkt == NULL) { debug(0, "Failed to malloc memory for internal master packet"); return (MEM_ERROR); } } else { server->master_pkt = (char *)realloc(server->master_pkt, server->master_pkt_len); if (server->master_pkt == NULL) { debug(0, "Failed to realloc memory for internal master packet"); return (MEM_ERROR); } } ipptr = data; portptr = NULL; // walk to next entry for ( ; len && *data != '\\'; data++, len--) { // port found (ip end) if (*data == ':') { portptr = data + 1; } } if (portptr != NULL) { strncpy(ipstr, ipptr, portptr - 1 - ipptr); ipstr[portptr - 1 - ipptr] = '\0'; strncpy(portstr, portptr, data - portptr); portstr[data - portptr] = '\0'; ipaddr = inet_addr(ipstr); port = htons((unsigned short)atoi(portstr)); } else { strncpy(ipstr, ipptr, data - 1 - ipptr); ipstr[portptr - ipptr] = '\0'; ipaddr = inet_addr(ipstr); port = htons(28000); // default port } memcpy(server->master_pkt + server->master_pkt_len - 6, &ipaddr, 4); memcpy(server->master_pkt + server->master_pkt_len - 2, &port, 2); } else { malformed_packet(server, "gamespy master response unknown entry"); return (PKT_ERROR); } } server->n_servers = server->master_pkt_len / 6; server->next_player_info = -1; server->retry1 = 0; return (DONE_FORCE); } return (INPROGRESS); } /* Misc utility functions */ unsigned int swap_long(void *l) { unsigned char *b = (unsigned char *)l; return (b[0] + (b[1] << 8) + (b[2] << 16) + (b[3] << 24)); } unsigned short swap_short(void *l) { unsigned char *b = (unsigned char *)l; return ((unsigned short)b[0] | (b[1] << 8)); } unsigned int swap_long_from_little(void *l) { unsigned char *b = (unsigned char *)l; unsigned int result; if (little_endian) { memcpy(&result, l, 4); } else { result = (unsigned int)b[0] + (b[1] << 8) + (b[2] << 16) + (b[3] << 24); } return (result); } float swap_float_from_little(void *f) { union { int i; float fl; } temp; temp.i = swap_long_from_little(f); return (temp.fl); } unsigned short swap_short_from_little(void *l) { unsigned char *b = (unsigned char *)l; unsigned short result; if (little_endian) { memcpy(&result, l, 2); } else { result = (unsigned short)b[0] | ((unsigned short)b[1] << 8); } return (result); } /** write four byte to buf */ void put_long_little(unsigned val, char *buf) { buf[0] = val & 0xFF; buf[1] = (val >> 8) & 0xFF; buf[2] = (val >> 16) & 0xFF; buf[3] = (val >> 24) & 0xFF; } char * xml_escape(char *string) { static unsigned char _buf[4][MAXSTRLEN + 8]; static int _buf_index = 0; unsigned char *result, *b, *end; unsigned int c; if (string == NULL) { return (""); } result = &_buf[_buf_index][0]; _buf_index = (_buf_index + 1) % 4; end = &result[MAXSTRLEN]; b = result; for ( ; *string && b < end; string++) { c = *string; switch (c) { case '&': *b++ = '&'; *b++ = 'a'; *b++ = 'm'; *b++ = 'p'; *b++ = ';'; continue; case '\'': *b++ = '&'; *b++ = 'a'; *b++ = 'p'; *b++ = 'o'; *b++ = 's'; *b++ = ';'; continue; case '"': *b++ = '&'; *b++ = 'q'; *b++ = 'u'; *b++ = 'o'; *b++ = 't'; *b++ = ';'; continue; case '<': *b++ = '&'; *b++ = 'l'; *b++ = 't'; *b++ = ';'; continue; case '>': *b++ = '&'; *b++ = 'g'; *b++ = 't'; *b++ = ';'; continue; default: break; } // Validate character // http://www.w3.org/TR/2000/REC-xml-20001006#charsets if (! ( (0x09 == c) || (0xA == c) || (0xD == c) || ((0x20 <= c) && (0xD7FF >= c)) || ((0xE000 <= c) && (0xFFFD >= c)) || ((0x10000 <= c) && (0x10FFFF >= c)) ) ) { if (show_errors) { fprintf(stderr, "Encoding error (%d) for U+%x, D+%d\n", 1, c, c); } } else if (xml_encoding == ENCODING_LATIN_1) { if (!xform_names) { *b++ = c; } else { if (isprint(c)) { *b++ = c; } else { b += sprintf((char *)b, "&#%u;", c); } } } else if (xml_encoding == ENCODING_UTF_8) { unsigned char tempbuf[10] = { 0 }; unsigned char *buf = &tempbuf[0]; int bytes = 0; int error = 1; // Valid character ranges if ( (0x09 == c) || (0xA == c) || (0xD == c) || ((0x20 <= c) && (0xD7FF >= c)) || ((0xE000 <= c) && (0xFFFD >= c)) || ((0x10000 <= c) && (0x10FFFF >= c)) ) { error = 0; } if (c < 0x80) { /* 0XXX XXXX one byte */ buf[0] = c; bytes = 1; } else if (c < 0x0800) { /* 110X XXXX two bytes */ buf[0] = 0xC0 | (0x03 & (c >> 6)); buf[1] = 0x80 | (0x3F & c); bytes = 2; } else if (c < 0x10000) { /* 1110 XXXX three bytes */ buf[0] = 0xE0 | (c >> 12); buf[1] = 0x80 | ((c >> 6) & 0x3F); buf[2] = 0x80 | (c & 0x3F); bytes = 3; if ((c == UTF8BYTESWAPNOTACHAR) || (c == UTF8NOTACHAR)) { error = 3; } } else if (c < 0x10FFFF) { /* 1111 0XXX four bytes */ buf[0] = 0xF0 | (c >> 18); buf[1] = 0x80 | ((c >> 12) & 0x3F); buf[2] = 0x80 | ((c >> 6) & 0x3F); buf[3] = 0x80 | (c & 0x3F); bytes = 4; if (c > UTF8MAXFROMUCS4) { error = 4; } } else if (c < 0x4000000) { /* 1111 10XX five bytes */ buf[0] = 0xF8 | (c >> 24); buf[1] = 0x80 | (c >> 18); buf[2] = 0x80 | ((c >> 12) & 0x3F); buf[3] = 0x80 | ((c >> 6) & 0x3F); buf[4] = 0x80 | (c & 0x3F); bytes = 5; error = 5; } else if (c < 0x80000000) { /* 1111 110X six bytes */ buf[0] = 0xFC | (c >> 30); buf[1] = 0x80 | ((c >> 24) & 0x3F); buf[2] = 0x80 | ((c >> 18) & 0x3F); buf[3] = 0x80 | ((c >> 12) & 0x3F); buf[4] = 0x80 | ((c >> 6) & 0x3F); buf[5] = 0x80 | (c & 0x3F); bytes = 6; error = 6; } else { error = 7; } if (error) { int i; fprintf(stderr, "UTF-8 encoding error (%d) for U+%x, D+%d : ", error, c, c); for (i = 0; i < bytes; i++) { fprintf(stderr, "0x%02x ", buf[i]); } fprintf(stderr, "\n"); } else { int i; for (i = 0; i < bytes; ++i) { *b++ = buf[i]; } } } } *b = '\0'; return ((char *)result); } int is_default_rule(struct rule *rule) { if (strcmp(rule->name, "sv_maxspeed") == 0) { return (strcmp(rule->value, Q_DEFAULT_SV_MAXSPEED) == 0); } if (strcmp(rule->name, "sv_friction") == 0) { return (strcmp(rule->value, Q_DEFAULT_SV_FRICTION) == 0); } if (strcmp(rule->name, "sv_gravity") == 0) { return (strcmp(rule->value, Q_DEFAULT_SV_GRAVITY) == 0); } if (strcmp(rule->name, "noexit") == 0) { return (strcmp(rule->value, Q_DEFAULT_NOEXIT) == 0); } if (strcmp(rule->name, "teamplay") == 0) { return (strcmp(rule->value, Q_DEFAULT_TEAMPLAY) == 0); } if (strcmp(rule->name, "timelimit") == 0) { return (strcmp(rule->value, Q_DEFAULT_TIMELIMIT) == 0); } if (strcmp(rule->name, "fraglimit") == 0) { return (strcmp(rule->value, Q_DEFAULT_FRAGLIMIT) == 0); } return (0); } char * strherror(int h_err) { static char msg[100]; switch (h_err) { case HOST_NOT_FOUND: return ("host not found"); case TRY_AGAIN: return ("try again"); case NO_RECOVERY: return ("no recovery"); case NO_ADDRESS: return ("no address"); default: sprintf(msg, "%d", h_err); return (msg); } } int time_delta(struct timeval *later, struct timeval *past) { if (later->tv_usec < past->tv_usec) { later->tv_sec--; later->tv_usec += 1000000; } return ((later->tv_sec - past->tv_sec) * 1000 + (later->tv_usec - past->tv_usec) / 1000); } int connection_inprogress() { #ifdef _WIN32 return (WSAGetLastError() == WSAEWOULDBLOCK); #else return (errno == EINPROGRESS); #endif } int connection_refused() { #ifdef _WIN32 return (WSAGetLastError() == WSAECONNABORTED); #else return (errno == ECONNREFUSED); #endif } int connection_would_block() { #ifdef _WIN32 return (WSAGetLastError() == WSAEWOULDBLOCK); #else return (errno == EAGAIN); #endif } int connection_reset() { #ifdef _WIN32 return (WSAGetLastError() == WSAECONNRESET); #else return (errno == ECONNRESET); #endif } void clear_socketerror() { #ifdef _WIN32 WSASetLastError(0); #else errno = 0; #endif } void set_non_blocking(int fd) { #ifdef _WIN32 int one = 1; ioctlsocket(fd, FIONBIO, (unsigned long *)&one); #else #ifdef O_NONBLOCK fcntl(fd, F_SETFL, O_NONBLOCK); #else fcntl(fd, F_SETFL, O_NDELAY); #endif // O_NONBLOCK #endif // _WIN32 } char * quake_color(int color) { static char *colors[] = { "White", /* 0 */ "Brown", /* 1 */ "Lavender", /* 2 */ "Khaki", /* 3 */ "Red", /* 4 */ "Lt Brown", /* 5 */ "Peach", /* 6 */ "Lt Peach", /* 7 */ "Purple", /* 8 */ "Dk Purple", /* 9 */ "Tan", /* 10 */ "Green", /* 11 */ "Yellow", /* 12 */ "Blue", /* 13 */ "Blue", /* 14 */ "Blue" /* 15 */ }; static char *rgb_colors[] = { "#ffffff", /* 0 */ "#8b4513", /* 1 */ "#e6e6fa", /* 2 */ "#f0e68c", /* 3 */ "#ff0000", /* 4 */ "#deb887", /* 5 */ "#eecbad", /* 6 */ "#ffdab9", /* 7 */ "#9370db", /* 8 */ "#5d478b", /* 9 */ "#d2b48c", /* 10 */ "#00ff00", /* 11 */ "#ffff00", /* 12 */ "#0000ff", /* 13 */ "#0000ff", /* 14 */ "#0000ff" /* 15 */ }; static char *color_nr[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15" }; if (color_names) { if (color_names == 1) { return (colors[color & 0xf]); } else { return (rgb_colors[color & 0xf]); } } else { return (color_nr[color & 0xf]); } } #define FMT_HOUR_1 "%2dh" #define FMT_HOUR_2 "%dh" #define FMT_MINUTE_1 "%2dm" #define FMT_MINUTE_2 "%dm" #define FMT_SECOND_1 "%2ds" #define FMT_SECOND_2 "%ds" char * play_time(int seconds, int show_seconds) { static char time_string[24]; if (time_format == CLOCK_TIME) { char *fmt_hour = show_seconds == 2 ? FMT_HOUR_2 : FMT_HOUR_1; char *fmt_minute = show_seconds == 2 ? FMT_MINUTE_2 : FMT_MINUTE_1; char *fmt_second = show_seconds == 2 ? FMT_SECOND_2 : FMT_SECOND_1; time_string[0] = '\0'; if (seconds / 3600) { sprintf(time_string, fmt_hour, seconds / 3600); } else if (show_seconds < 2) { strcat(time_string, " "); } if ((seconds % 3600) / 60 || seconds / 3600) { sprintf(time_string + strlen(time_string), fmt_minute, (seconds % 3600) / 60); } else if (!show_seconds) { sprintf(time_string + strlen(time_string), " 0m"); } else if (show_seconds < 2) { strcat(time_string, " "); } if (show_seconds) { sprintf(time_string + strlen(time_string), fmt_second, seconds % 60); } } else if (time_format == STOPWATCH_TIME) { if (show_seconds) { sprintf(time_string, "%02d:%02d:%02d", seconds / 3600, (seconds % 3600) / 60, seconds % 60); } else { sprintf(time_string, "%02d:%02d", seconds / 3600, (seconds % 3600) / 60); } } else { sprintf(time_string, "%d", seconds); } return (time_string); } char * ping_time(int ms) { static char time_string[24]; if (ms < 1000) { sprintf(time_string, "%dms", ms); } else if (ms < 1000000) { sprintf(time_string, "%ds", ms / 1000); } else { sprintf(time_string, "%dm", ms / 1000 / 60); } return (time_string); } int count_bits(int n) { int b = 0; for ( ; n; n >>= 1) { if (n & 1) { b++; } } return (b); } int strcmp_withnull(char *one, char *two) { if ((one == NULL) && (two == NULL)) { return (0); } if ((one != NULL) && (two == NULL)) { return (-1); } if (one == NULL) { return (1); } return (strcasecmp(one, two)); } /* * Sorting functions */ void sort_servers(struct qserver **array, int size) { quicksort((void **)array, 0, size - 1, (int (*)(void *, void *))server_compare); } void sort_players(struct qserver *server) { struct player **array, *player, *last_team = NULL, **next; int np, i; if ((server->num_players == 0) || (server->players == NULL)) { return; } player = server->players; for ( ; player != NULL && player->number == TRIBES_TEAM; ) { last_team = player; player = player->next; } if (player == NULL) { return; } array = (struct player **)malloc(sizeof(struct player *) * (server->num_players + server->num_spectators)); for (np = 0; player != NULL && np < server->num_players + server->num_spectators; np++) { array[np] = player; player = player->next; } quicksort((void **)array, 0, np - 1, (int (*)(void *, void *))player_compare); if (last_team) { next = &last_team->next; } else { next = &server->players; } for (i = 0; i < np; i++) { *next = array[i]; array[i]->next = NULL; next = &array[i]->next; } free(array); } int server_compare(struct qserver *one, struct qserver *two) { int rc; char *key = sort_keys; for ( ; *key; key++) { switch (*key) { case 'g': rc = strcmp_withnull(one->game, two->game); if (rc) { return (rc); } break; case 'p': if (one->n_requests == 0) { return (two->n_requests); } else if (two->n_requests == 0) { return (-1); } rc = one->ping_total / one->n_requests - two->ping_total / two->n_requests; if (rc) { return (rc); } break; case 'i': if (one->ipaddr > two->ipaddr) { return (1); } else if (one->ipaddr < two->ipaddr) { return (-1); } else if (one->port > two->port) { return (1); } else if (one->port < two->port) { return (-1); } break; case 'h': rc = strcmp_withnull(one->host_name, two->host_name); if (rc) { return (rc); } break; case 'n': rc = two->num_players - one->num_players; if (rc) { return (rc); } break; } } return (0); } int type_option_compare(server_type *one, server_type *two) { return (strcmp_withnull(one->type_option, two->type_option)); } int type_string_compare(server_type *one, server_type *two) { return (strcmp_withnull(one->type_string, two->type_string)); } int player_compare(struct player *one, struct player *two) { int rc; char *key = sort_keys; for ( ; *key; key++) { switch (*key) { case 'P': rc = one->ping - two->ping; if (rc) { return (rc); } break; case 'F': rc = two->frags - one->frags; if (rc) { return (rc); } break; case 'S': rc = two->score - one->score; if (rc) { return (rc); } break; case 'T': rc = one->team - two->team; if (rc) { return (rc); } rc = strcmp_withnull(one->team_name, two->team_name); if (rc) { return (rc); } break; case 'N': rc = strcmp_withnull(one->name, two->name); if (rc) { return (rc); } return (one->number - two->number); break; } } return (0); } void quicksort(void **array, int i, int j, int (*compare)(void *, void *)) { int q = 0; if (i < j) { q = qpartition(array, i, j, compare); quicksort(array, i, q, compare); quicksort(array, q + 1, j, compare); } } int qpartition(void **array, int a, int b, int (*compare)(void *, void *)) { /* this is our comparison point. when we are done * splitting this array into 2 parts, we want all the * elements on the left side to be less then or equal * to this, all the elements on the right side need to * be greater then or equal to this */ void *z; /* indicies into the array to sort. Used to calculate a partition * point */ int i = a - 1; int j = b + 1; /* temp pointer used to swap two array elements */ void *tmp = NULL; z = array[a]; while (1) { /* move the right indice over until the value of that array * elem is less than or equal to z. Stop if we hit the left * side of the array (ie, j == a); */ do { j--; } while (j > a && compare(array[j], z) > 0); /* move the left indice over until the value of that * array elem is greater than or equal to z, or until * we hit the right side of the array (ie i == j) */ do { i++; } while (i <= j && compare(array[i], z) < 0); /* if i is less then j, we need to switch those two array * elements, if not then we are done partitioning this array * section */ if (i < j) { tmp = array[i]; array[i] = array[j]; array[j] = tmp; } else { return (j); } } } qstat-2.17/qstat.cfg000066400000000000000000000324231412457473700144360ustar00rootroot00000000000000# QStat configuration file # # See qstat documentation (qstatdoc.html) for instructions # http://www.qstat.org gametype SOF2S new extend Q3S name = Soldier of Fortune 2 default port = 20100 template var = SOLDIEROFFORTUNE2 game rule = gametype end # SOF2 public master: master.sof2.ravensoft.com # The 2003 protocol is for SOF2 1.01 # The 2004 protocol is for SOF2 1.02 gametype SOF2M new extend Q3M name = SOF2 Master default port = 20110 template var = SOF2MASTER master protocol = 2004 master query = SOF2FULL master for gametype = sof2s end # The 2002 protocol is for SOF2 1.0 gametype SOF2M1.0 new extend SOF2M name = SOF2 Master (1.0) master protocol = 2002 end gametype CRS new extend GPS name = Command and Conquer: Renegade default port = 25300 template var = CNCRENEGADE game rule = gametype end # MOHAA supports two status protocols, Gamespy and Quake 3. # Seems like servers support one or the other, but not both at the same time. # The Gamespy protocol returns player name, frags, ping, and deaths. gametype MAS new extend GPS name = Medal of Honor: Allied Assault default port = 12300 template var = MOHALLIEDASSAULT game rule = gametype end # MOHAA support for the Q3 protocol is broken and more limited. The # response to "getinfo" does not include the current and max players # (they're always zero). The response to "getstatus" has the correct # current and max players, but only reports player name and ping. # If -R or -P options are specified, then both "getinfo" and "getstatus" # are sent. Otherwise only "getinfo" is sent. gametype MAQS new extend Q3S name = Medal of Honor: Allied Assault (Q) default port = 12300 template var = MOHALLIEDASSAULT game rule = gametype status packet = \xff\xff\xff\xff\x02getinfo\n status2 packet = \xff\xff\xff\xff\x02getstatus\n end # Half-Life supports a Quake 2 style status protocol, but only for # basic status, there's no player or extended rule information. The # server rule names are different from HLS. gametype HLQS new extend Q2S name = Half-Life template var = HALFLIFE default port = 27015 game rule = gamedir status packet = \xff\xff\xff\xffinfostring\x00 end # Config for Serious Sam SMS gametype SMS new extend GPS name = Serious Sam default port = 25600 status port offset = 1 template var = SERIOUSSAM game rule = gametype end # Config for Medal of Honor: Allied Assault MHS gametype MHS new extend Q3S name = Medal of Honor: Allied Assault default port = 12204 template var = MOHALLIEDASSAULT game rule = gametype status packet = \377\377\377\377\002getinfo xxx status2 packet = \377\377\377\377\002getstatus xxx end # Config for Call of Duty gametype CODS new extend Q3S name = Call of Duty default port = 28960 template var = CALLOFDUTY game rule = gamename end gametype CODM new extend Q3M name = Call of Duty Master default port = 20510 template var = CODMASTER master protocol = 5 master for gametype = CODS master query = full empty end # Config for Call of Duty 2 gametype COD2S new extend Q3S name = Call of Duty 2 default port = 28960 template var = CALLOFDUTY2 game rule = gamename end gametype COD2M new extend Q3M name = Call of Duty 2 Master default port = 20710 template var = COD2MASTER master protocol = 118 master for gametype = COD2S master query = full empty end # Config for Call of Duty 4 gametype COD4S new extend Q3S name = Call of Duty 4 default port = 28960 template var = CALLOFDUTY4 game rule = gamename end gametype COD4M new extend Q3M name = Call of Duty 4 Master default port = 20810 template var = COD4MASTER master protocol = 6 master for gametype = COD4S master query = full empty end # Config for Call of Duty Word at War gametype WAWS new extend Q3S name = Call of Duty World at War default port = 28960 template var = CALLOFDUTYWAW game rule = gametype end # id Tech 3 (Enemy Territory flavour) gametype WOETS new extend Q3S name = Enemy Territory template var = ENEMYTERRITORY game rule = gamename end gametype WOETM new extend Q3M name = Enemy Territory Master template var = WOETSMASTER master protocol = 84 master for gametype = WOETS master query = empty full end # NetPanzer > 0.1.6 gametype NETP new extend GPS name = NetPanzer template var = NETPANZER end gametype NETPM new extend GSM name = NetPanzer Master template var = NETPANZERMASTER master for gametype = NETP end gametype STMHL2 new extend STM name = Steam Master for HL2 template var = STEAMMASTERHL2 master for gametype = HL2S default port = 27011 end gametype STMA2S new extend STM name = Steam Master for A2S template var = STEAMMASTERA2S master for gametype = A2S default port = 27011 end gametype UT2004S new extend UT2S name = UT2004 template var = UT2004 end gametype UT2004M modify master for gametype = UT2004S end # id Tech 2 fork (DarkPlace engine, Quake 2 derivative) gametype NEXUIZS new extend Q3S name = Nexuiz template var = NEXUIZ default port = 26000 game rule = gamename end gametype NEXUIZM new extend Q3M name = Nexuiz Master template var = NEXUIZMASTER default port = 27950 master packet = \377\377\377\377getservers Nexuiz %s %s master protocol = 3 master query = empty full master for gametype = NEXUIZS end # id Tech 2 fork (DarkPlace engine, Quake 2 derivative) gametype XONOTICS new extend Q3S name = Xonotic template var = XONOTIC default port = 26000 game rule = gamename end gametype XONOTICM new extend Q3M name = Xonotic Master template var = XONOTICMASTER default port = 27950 master packet = \377\377\377\377getservers Xonotic %s %s master protocol = 3 master query = empty full master for gametype = XONOTICS end # id Tech 2 fork (Qfusion engine, Quake 1 derivative) gametype WARSOWS new extend Q3S name = Warsow template var = WARSOW default port = 44400 game rule = gamename status packet = \377\377\377\377getinfo status2 packet = \377\377\377\377getstatus end gametype WARSOWM new extend Q3M name = Warsow Master template var = WARSOWMASTER default port = 27950 master packet = \377\377\377\377getservers Warsow %s %s master protocol = 22 master query = empty full master for gametype = WARSOWS end # id Tech 3 fork (Tremulous engine, Quake 3 derivative) gametype TREMULOUSS new extend Q3S name = Tremulous template var = TREMULOUSS game rule = gamename end gametype TREMULOUSM new extend Q3M name = Tremulous Master template var = TREMULOUSMASTER default port = 30710 master protocol = 69 master for gametype = TREMULOUSS end # id Tech 3 fork (Tremulous engine derivative, Quake 3 derivative) gametype TREMULOUSGPPS new extend Q3S name = Tremulous GPP template var = TREMULOUSGPPS game rule = gamename end gametype TREMULOUSGPPM new extend Q3M name = Tremulous GPP Master template var = TREMULOUSGPPMASTER default port = 30700 master protocol = 70 master for gametype = TREMULOUSGPPS end # id Tech 3 fork (Daemon engine, Quake 3 derivative, via Tremulous, ioquake3, XreaL, ioWolfET) gametype UNVANQUISHEDS new extend Q3S name = Unvanquished template var = UNVANQUISHED default port = 27960 status packet = \xff\xff\xff\xffgetstatus game rule = gamename end gametype UNVANQUISHEDM new extend Q3M name = Unvanquished Master template var = UNVANQUISHEDMASTER default port = 27950 master protocol = 86 master for gametype = UNVANQUISHEDS end gametype HLA2S new extend A2S name = Half-Life template var = HLA2S end gametype HLA2SM new extend STM master for gametype = HLA2S default port = 27010 end gametype PREYM new extend DM3M name = Prey Master template var = PREYMASTER default port = 27655 master query = empty full master for gametype = PREYS end gametype JK2S new extend Q3S name = Jedi Knight 2 default port = 28070 template var = JEDIKNIGHT2 game rule = gamename end gametype JK2M new extend JK3M name = Jedi Knight 2 default port = 28060 template var = JK2MASTER master protocol = 16 master for gametype = JK2S end gametype UT3S new extend GS4 name = UT3 default port = 6500 template var = UT3 end # id Tech 3 fork (ioquake3 engine, Quake 3 derivative) gametype OPENARENAS new extend Q3S name = OpenArena template var = OPENARENA game rule = gamename end gametype OPENARENAM new extend Q3M name = OpenArena Master template var = OPENARENAMASTER default port = 27950 master packet = \377\377\377\377getservers %s %s master protocol = 71 master query = empty full master for gametype = OPENARENAS end # id Tech 3 fork (ioquake3 engine, Quake 3 derivative) gametype Q3RALLYS new extend Q3S name = Q3 Rally template var = Q3RALLY game rule = gamename end gametype Q3RALLYM new extend Q3M name = Q3 Rally Master template var = Q3RALLYMASTER default port = 27950 master packet = \377\377\377\377getservers Q3Rally %s %s master protocol = 71 master query = empty full master for gametype = Q3RALLYS end # id Tech 3 fork (ioquake3 engine, Quake 3 derivative) gametype WOPS new extend Q3S name = World Of Padman template var = WOP game rule = gamename end gametype WOPM new extend Q3M name = World Of Padman Master template var = WOPMASTER default port = 27955 master packet = \377\377\377\377getservers WorldofPadman %s %s master protocol = 71 master query = empty full master for gametype = WOPS end # id Tech 3 fork (ioquake3 engine, Quake 3 derivative) gametype IOURTS new extend Q3S name = Urban Terror template var = IOURT game rule = gamename end gametype IOURTM new extend Q3M name = Urban Terror Master template var = IOURTMASTER default port = 27900 master packet = \377\377\377\377getservers %s %s master protocol = 68 master query = empty full master for gametype = IOURTS end # id Tech 3 fork (ioquake3 engine, Quake 3 derivative) gametype REACTIONS new extend Q3S name = Reaction template var = REACTION game rule = gamename end gametype REACTIONM new extend Q3M name = Reaction Master template var = REACTIONMASTER default port = 27950 master packet = \377\377\377\377getservers %s %s master protocol = 68 master query = empty full master for gametype = REACTIONS end # id Tech 3 fork (ioquake3 engine, Quake 3 derivative) gametype SMOKINGUNSS new extend Q3S name = Smokin' Guns template var = SMOKINGUNS game rule = gamename end gametype SMOKINGUNSM new extend Q3M name = Smokin' Guns Master template var = SMOKINGUNSMASTER default port = 27960 master packet = \377\377\377\377getservers %s %s master protocol = 68 master query = empty full master for gametype = SMOKINGUNSS end # id Tech 3 fork (ioquake3 engine, Quake 3 derivative) gametype ZEQ2LITES new extend Q3S name = ZEQ2 Lite template var = ZEQ2LITE game rule = gamename end gametype ZEQ2LITEM new extend Q3M name = ZEQ2 Lite Master template var = ZEQ2LITEMASTER default port = 27960 master packet = \377\377\377\377getservers %s %s master protocol = 75 master query = empty full master for gametype = ZEQ2LITES end # id Tech 3 fork (Spearmint engine, Quake 3 derivative) gametype TURTLEARENAS new extend Q3S name = Turtle Arena template var = TURTLEARENA default port = 27960 game rule = game end gametype TURTLEARENAM new extend Q3M name = Turtle Arena Master template var = TURTLEARENAMASTER default port = 27950 master packet = \377\377\377\377getservers TurtleArena %s %s master protocol = 11 master query = empty full master for gametype = TURTLEARENAS end # id Tech 2 fork (CRX engine, Quake 2 derivative) gametype ALIENARENAS new extend Q2S name = Alien Arena template var = ALIENARENA game rule = gamename end gametype ALIENARENAM new extend Q2M name = Alien Arena Master template var = ALIENARENAMASTER default port = 27900 master protocol = 34 master query = empty full master for gametype = ALIENARENAS end gametype TF2 new extend TF name = Titanfall 2 status packet = \xff\xff\xff\xff\x4f\x03 end gametype TF2E new extend TF name = Titanfall 2 Protocol v2 status packet = \xff\xff\xff\xff\x4d\x03 end # id Tech 2 fork (Quetoo engine, Quake 2 derivative) gametype QUETOOS new extend Q2S name = Quetoo template var = QUETOO default port = 1998 game rule = game_name end gametype QUETOOM new extend Q2M name = Quetoo Master template var = QUETOOMASTER default port = 1996 master query = empty full master for gametype = QUETOOS end # id Tech 2 fork (DarkPlace engine, Quake 2 derivative) gametype DOOMBRINGERS new extend Q3S name = Doombringer template var = DOOMBRINGER default port = 26000 game rule = gamename end gametype DOOMBRINGERM new extend Q3M name = Doombringer Master template var = DOOMBRINGERMASTER default port = 27950 master packet = \377\377\377\377getservers DOOMBRINGER %s %s master protocol = 3 master query = empty full master for gametype = DOOMBRINGERS end qstat-2.17/qstat.h000066400000000000000000006057671412457473700141470ustar00rootroot00000000000000/* * qstat.h * by Steve Jankowski * steve@qstat.org * http://www.qstat.org * * Copyright 1996,1997,1998,1999,2000,2001,2002 by Steve Jankowski * Copyright 2007-2021 Steven Hartland */ #ifndef __H_QSTAT #define __H_QSTAT #ifdef HAVE_CONFIG_H #include "gnuconfig.h" #endif #include "version.h" #ifdef __EMX__ #include #include #define strcasecmp stricmp #define strncasecmp strnicmp #endif #ifdef _WIN32 #include #include #define PATH_MAX MAX_PATH #include #define _POSIX_ 1 #ifndef FD_SETSIZE #define FD_SETSIZE 256 #endif #define close(a) closesocket(a) static int gettimeofday(struct timeval *now, void *blah) { struct timeb timeb; ftime(&timeb); now->tv_sec = timeb.time; now->tv_usec = (unsigned int)timeb.millitm * 1000; return (0); } #define sockerr() WSAGetLastError() #define strcasecmp stricmp #define strncasecmp strnicmp #define STATIC #ifndef EADDRINUSE #define EADDRINUSE WSAEADDRINUSE #endif #if _MSC_VER < 1900 #define snprintf _snprintf #endif #else #include #define SOCKET_ERROR -1 #endif /* _WIN32 */ #include #define MAXSTRLEN 2048 #define ENCODING_LATIN_1 1 #define ENCODING_UTF_8 8 #define UTF8BYTESWAPNOTACHAR 0xFFFE #define UTF8NOTACHAR 0xFFFF #define UTF8MAXFROMUCS4 0x10FFFF typedef struct _server_type server_type; #ifdef __GNUC__ #define GCC_FORMAT_PRINTF(a, b) __attribute__ ((format(printf, a, b))) #else #define GCC_FORMAT_PRINTF(a, b) #endif typedef enum { INPROGRESS = 0, DONE_AUTO = 1, DONE_FORCE = 2, SYS_ERROR = -1, MEM_ERROR = -2, PKT_ERROR = -3, ORD_ERROR = -4, REQ_ERROR = -5 } query_status_t; #include "qserver.h" typedef void (*DisplayFunc)(struct qserver *); typedef query_status_t (*QueryFunc)(struct qserver *); typedef query_status_t (*PacketFunc)(struct qserver *, char *rawpkt, int pktlen); // Display modules #include "display_json.h" // Packet modules #include "ut2004.h" #include "doom3.h" #include "a2s.h" #include "fl.h" #include "gps.h" #include "gs2.h" #include "gs3.h" #include "haze.h" #include "ts2.h" #include "ts3.h" #include "tm.h" #include "wic.h" #include "ottd.h" #include "tee.h" #include "cube2.h" #include "bfbc2.h" #include "ventrilo.h" #include "mumble.h" #include "terraria.h" #include "crysis.h" #include "dirtybomb.h" #include "starmade.h" #include "farmsim.h" #include "ksp.h" #include "tf.h" #include "armyops.h" /* * Various magic numbers. */ #define Q_DEFAULT_PORT 26000 #define HEXEN2_DEFAULT_PORT 26900 #define Q2_DEFAULT_PORT 27910 #define Q3_DEFAULT_PORT 27960 #define Q2_MASTER_DEFAULT_PORT 27900 #define Q3_MASTER_DEFAULT_PORT 27950 #define QW_DEFAULT_PORT 27500 #define QW_MASTER_DEFAULT_PORT 27000 #define HW_DEFAULT_PORT 26950 #define HW_MASTER_DEFAULT_PORT 26900 #define UNREAL_DEFAULT_PORT 7777 #define UNREAL_MASTER_DEFAULT_PORT 28900 #define HALFLIFE_DEFAULT_PORT 27015 #define HL_MASTER_DEFAULT_PORT 27010 #define SIN_DEFAULT_PORT 22450 #define SHOGO_DEFAULT_PORT 27888 #define TRIBES_DEFAULT_PORT 28001 #define TRIBES_MASTER_DEFAULT_PORT 28000 #define BFRIS_DEFAULT_PORT 44001 #define KINGPIN_DEFAULT_PORT 31510 #define HERETIC2_DEFAULT_PORT 28910 #define SOF_DEFAULT_PORT 28910 #define GAMESPY_MASTER_DEFAULT_PORT 28900 #define TRIBES2_DEFAULT_PORT 28000 #define TRIBES2_MASTER_DEFAULT_PORT 28002 #define DESCENT3_GAMESPY_DEFAULT_PORT 20142 #define DESCENT3_DEFAULT_PORT 2092 #define DESCENT3_MASTER_DEFAULT_PORT 3445 #define RTCW_DEFAULT_PORT 27960 #define RTCW_MASTER_DEFAULT_PORT 27950 #define STEF_DEFAULT_PORT 27960 #define STEF_MASTER_DEFAULT_PORT 27953 #define JK3_DEFAULT_PORT 29070 #define JK3_MASTER_DEFAULT_PORT 29060 #define GHOSTRECON_PLAYER_DEFAULT_PORT 2346 #define RAVENSHIELD_DEFAULT_PORT 8777 #define SAVAGE_DEFAULT_PORT 11235 #define FARCRY_DEFAULT_PORT 49001 #define STEAM_MASTER_DEFAULT_PORT 27010 #define HL2_DEFAULT_PORT 27015 #define HL2_MASTER_DEFAULT_PORT 27011 #define TS2_DEFAULT_PORT 51234 #define TS3_DEFAULT_PORT 10011 #define BFBC2_DEFAULT_PORT 48888 #define TM_DEFAULT_PORT 5000 #define WIC_DEFAULT_PORT 5000 // Default is actually disabled #define FL_DEFAULT_PORT 5478 #define VENTRILO_DEFAULT_PORT 3784 #define CUBE2_DEFAULT_PORT 28785 #define MUMBLE_DEFAULT_PORT 64738 #define TERRARIA_DEFAULT_PORT 7777 #define CRYSIS_DEFAULT_PORT 64087 #define DIRTYBOMB_DEFAULT_PORT 7877 #define STARMADE_DEFAULT_PORT 4242 #define FARMSIM_DEFAULT_PORT 10828 #define KSP_DEFAULT_PORT 6702 #define TF_DEFAULT_PORT 37016 #define Q_UNKNOWN_TYPE 0 #define MASTER_SERVER 0x40000000 #define Q_SERVER 1 #define QW_SERVER 2 #define QW_MASTER (3 | MASTER_SERVER) #define H2_SERVER 4 #define Q2_SERVER 5 #define Q2_MASTER (6 | MASTER_SERVER) #define HW_SERVER 7 #define UN_SERVER 8 #define UN_MASTER (9 | MASTER_SERVER) #define HL_SERVER 10 #define HL_MASTER (11 | MASTER_SERVER) #define SIN_SERVER 12 #define SHOGO_SERVER 13 #define TRIBES_SERVER 14 #define TRIBES_MASTER (15 | MASTER_SERVER) #define Q3_SERVER 16 #define Q3_MASTER (17 | MASTER_SERVER) #define BFRIS_SERVER 18 #define KINGPIN_SERVER 19 #define HERETIC2_SERVER 20 #define SOF_SERVER 21 #define GAMESPY_PROTOCOL_SERVER 22 #define GAMESPY_MASTER (23 | MASTER_SERVER) #define TRIBES2_SERVER 24 #define TRIBES2_MASTER (25 | MASTER_SERVER) #define DESCENT3_GAMESPY_SERVER 26 #define DESCENT3_PXO_SERVER 27 #define DESCENT3_SERVER 28 #define DESCENT3_MASTER (29 | MASTER_SERVER) #define RTCW_SERVER 30 #define RTCW_MASTER (31 | MASTER_SERVER) #define STEF_SERVER 32 #define STEF_MASTER (33 | MASTER_SERVER) #define UT2003_SERVER 34 #define GHOSTRECON_SERVER 35 #define ALLSEEINGEYE_PROTOCOL_SERVER 36 #define RAVENSHIELD_SERVER 37 #define SAVAGE_SERVER 38 #define FARCRY_SERVER 39 #define GAMESPY2_PROTOCOL_SERVER 40 #define STEAM_MASTER (41 | MASTER_SERVER) #define JK3_SERVER 42 #define JK3_MASTER (43 | MASTER_SERVER) #define DOOM3_SERVER 44 #define DOOM3_MASTER (45 | MASTER_SERVER) #define HL2_SERVER 46 #define HL2_MASTER (47 | MASTER_SERVER) #define UT2004_MASTER (48 | MASTER_SERVER) #define A2S_SERVER 49 #define PARIAH_SERVER 50 #define GAMESPY3_PROTOCOL_SERVER 51 #define TS2_PROTOCOL_SERVER 52 #define QUAKE4_SERVER 53 #define QUAKE4_MASTER (53 | MASTER_SERVER) #define ARMYOPS_SERVER 54 #define GAMESPY4_PROTOCOL_SERVER 55 #define PREY_SERVER 56 #define TM_PROTOCOL_SERVER 57 #define ETQW_SERVER 58 #define HAZE_SERVER 59 #define HW_MASTER (60 | MASTER_SERVER) #define WIC_PROTOCOL_SERVER 61 #define OTTD_SERVER 62 #define OTTD_MASTER (63 | MASTER_SERVER) #define FL_SERVER 64 #define WOLF_SERVER 65 #define TEE_SERVER 66 #define TS3_PROTOCOL_SERVER 67 #define BFBC2_PROTOCOL_SERVER 68 #define VENTRILO_PROTOCOL_SERVER 69 #define CUBE2_SERVER 70 #define MUMBLE_PROTOCOL_SERVER 71 #define TERRARIA_PROTOCOL_SERVER 72 #define CRYSIS_PROTOCOL_SERVER 73 #define DIRTYBOMB_PROTOCOL_SERVER 74 #define STARMADE_PROTOCOL_SERVER 75 #define FARMSIM_PROTOCOL_SERVER 76 #define KSP_PROTOCOL_SERVER 77 #define TF_PROTOCOL_SERVER 78 #define TEE_MASTER 79 #define LAST_BUILTIN_SERVER 80 #define TF_SINGLE_QUERY (1 << 1) #define TF_OUTFILE (1 << 2) #define TF_MASTER_MULTI_RESPONSE (1 << 3) #define TF_TCP_CONNECT (1 << 4) #define TF_QUERY_ARG (1 << 5) #define TF_QUERY_ARG_REQUIRED (1 << 6) #define TF_QUAKE3_NAMES (1 << 7) #define TF_TRIBES2_NAMES (1 << 8) #define TF_SOF_NAMES (1 << 9) #define TF_U2_NAMES (1 << 10) #define TF_RAW_STYLE_QUAKE (1 << 11) #define TF_RAW_STYLE_TRIBES (1 << 12) #define TF_RAW_STYLE_GHOSTRECON (1 << 13) #define TF_NO_PORT_OFFSET (1 << 14) #define TF_SHOW_GAME_PORT (1 << 15) #define TF_MASTER_STEAM (1 << 16) /* supports steam server filter */ // What response type are we expecting // XXX: this is not what server->flags is for #define TF_STATUS_QUERY (1 << 17) #define TF_PLAYER_QUERY (1 << 18) #define TF_RULES_QUERY (1 << 19) #define TF_TM_NAMES (1 << 20) #define TRIBES_TEAM -1 struct q_packet; /* * Output and formatting functions */ void display_server(struct qserver *server); void display_qwmaster(struct qserver *server); void display_server_rules(struct qserver *server); void display_player_info(struct qserver *server); void display_q_player_info(struct qserver *server); void display_qw_player_info(struct qserver *server); void display_q2_player_info(struct qserver *server); void display_unreal_player_info(struct qserver *server); void display_shogo_player_info(struct qserver *server); void display_halflife_player_info(struct qserver *server); void display_tribes_player_info(struct qserver *server); void display_tribes2_player_info(struct qserver *server); void display_bfris_player_info(struct qserver *server); void display_descent3_player_info(struct qserver *server); void display_ravenshield_player_info(struct qserver *server); void display_savage_player_info(struct qserver *server); void display_farcry_player_info(struct qserver *server); void display_ghostrecon_player_info(struct qserver *server); void display_eye_player_info(struct qserver *server); void display_armyops_player_info(struct qserver *server); void display_gs2_player_info(struct qserver *server); void display_doom3_player_info(struct qserver *server); void display_ts2_player_info(struct qserver *server); void display_ts3_player_info(struct qserver *server); void display_bfbc2_player_info(struct qserver *server); void display_tm_player_info(struct qserver *server); void display_wic_player_info(struct qserver *server); void display_fl_player_info(struct qserver *server); void display_tee_player_info(struct qserver *server); void display_ventrilo_player_info(struct qserver *server); void display_starmade_player_info(struct qserver *server); void raw_display_server(struct qserver *server); void raw_display_server_rules(struct qserver *server); void raw_display_player_info(struct qserver *server); void raw_display_q_player_info(struct qserver *server); void raw_display_qw_player_info(struct qserver *server); void raw_display_q2_player_info(struct qserver *server); void raw_display_unreal_player_info(struct qserver *server); void raw_display_halflife_player_info(struct qserver *server); void raw_display_tribes_player_info(struct qserver *server); void raw_display_tribes2_player_info(struct qserver *server); void raw_display_bfris_player_info(struct qserver *server); void raw_display_ravenshield_player_info(struct qserver *server); void raw_display_savage_player_info(struct qserver *server); void raw_display_farcry_player_info(struct qserver *server); void raw_display_descent3_player_info(struct qserver *server); void raw_display_ghostrecon_player_info(struct qserver *server); void raw_display_eye_player_info(struct qserver *server); void raw_display_armyops_player_info(struct qserver *server); void raw_display_gs2_player_info(struct qserver *server); void raw_display_doom3_player_info(struct qserver *server); void raw_display_ts2_player_info(struct qserver *server); void raw_display_ts3_player_info(struct qserver *server); void raw_display_bfbc2_player_info(struct qserver *server); void raw_display_tm_player_info(struct qserver *server); void raw_display_wic_player_info(struct qserver *server); void raw_display_fl_player_info(struct qserver *server); void raw_display_tee_player_info(struct qserver *server); void raw_display_ventrilo_player_info(struct qserver *server); void raw_display_starmade_player_info(struct qserver *server); void xml_display_server(struct qserver *server); void xml_header(); void xml_footer(); void xml_display_server_rules(struct qserver *server); void xml_display_player_info(struct qserver *server); void xml_display_q_player_info(struct qserver *server); void xml_display_qw_player_info(struct qserver *server); void xml_display_q2_player_info(struct qserver *server); void xml_display_unreal_player_info(struct qserver *server); void xml_display_halflife_player_info(struct qserver *server); void xml_display_tribes_player_info(struct qserver *server); void xml_display_tribes2_player_info(struct qserver *server); void xml_display_ravenshield_player_info(struct qserver *server); void xml_display_savage_player_info(struct qserver *server); void xml_display_farcry_player_info(struct qserver *server); void xml_display_bfris_player_info(struct qserver *server); void xml_display_descent3_player_info(struct qserver *server); void xml_display_ghostrecon_player_info(struct qserver *server); void xml_display_eye_player_info(struct qserver *server); void xml_display_armyops_player_info(struct qserver *server); void xml_display_player_info(struct qserver *server); void xml_display_doom3_player_info(struct qserver *server); void xml_display_ts2_player_info(struct qserver *server); void xml_display_ts3_player_info(struct qserver *server); void xml_display_bfbc2_player_info(struct qserver *server); void xml_display_tm_player_info(struct qserver *server); void xml_display_wic_player_info(struct qserver *server); void xml_display_fl_player_info(struct qserver *server); void xml_display_tee_player_info(struct qserver *server); void xml_display_ventrilo_player_info(struct qserver *server); void xml_display_starmade_player_info(struct qserver *server); char *xml_escape(char *); query_status_t send_qserver_request_packet(struct qserver *server); query_status_t send_qwserver_request_packet(struct qserver *server); query_status_t send_ut2003_request_packet(struct qserver *server); query_status_t send_tribes_request_packet(struct qserver *server); query_status_t send_qwmaster_request_packet(struct qserver *server); query_status_t send_bfris_request_packet(struct qserver *server); query_status_t send_player_request_packet(struct qserver *server); query_status_t send_rule_request_packet(struct qserver *server); query_status_t send_savage_request_packet(struct qserver *server); query_status_t send_farcry_request_packet(struct qserver *server); query_status_t send_gamespy_master_request(struct qserver *server); query_status_t send_tribes2_request_packet(struct qserver *server); query_status_t send_tribes2master_request_packet(struct qserver *server); query_status_t send_hl2_request_packet(struct qserver *server); query_status_t deal_with_q_packet(struct qserver *server, char *pkt, int pktlen); query_status_t deal_with_qw_packet(struct qserver *server, char *pkt, int pktlen); query_status_t deal_with_q1qw_packet(struct qserver *server, char *pkt, int pktlen); query_status_t deal_with_q2_packet(struct qserver *server, char *pkt, int pktlen); query_status_t deal_with_qwmaster_packet(struct qserver *server, char *pkt, int pktlen); query_status_t deal_with_halflife_packet(struct qserver *server, char *pkt, int pktlen); query_status_t deal_with_ut2003_packet(struct qserver *server, char *pkt, int pktlen); query_status_t deal_with_tribes_packet(struct qserver *server, char *pkt, int pktlen); query_status_t deal_with_tribesmaster_packet(struct qserver *server, char *pkt, int pktlen); query_status_t deal_with_bfris_packet(struct qserver *server, char *pkt, int pktlen); query_status_t deal_with_gamespy_master_response(struct qserver *server, char *pkt, int pktlen); query_status_t deal_with_ravenshield_packet(struct qserver *server, char *pkt, int pktlen); query_status_t deal_with_savage_packet(struct qserver *server, char *pkt, int pktlen); query_status_t deal_with_farcry_packet(struct qserver *server, char *pkt, int pktlen); query_status_t deal_with_tribes2_packet(struct qserver *server, char *pkt, int pktlen); query_status_t deal_with_tribes2master_packet(struct qserver *server, char *pkt, int pktlen); query_status_t deal_with_descent3_packet(struct qserver *server, char *pkt, int pktlen); query_status_t deal_with_descent3master_packet(struct qserver *server, char *pkt, int pktlen); query_status_t deal_with_ghostrecon_packet(struct qserver *server, char *pkt, int pktlen); query_status_t deal_with_eye_packet(struct qserver *server, char *pkt, int pktlen); query_status_t deal_with_hl2_packet(struct qserver *server, char *pkt, int pktlen); struct _server_type { int id; char *type_prefix; char *type_string; char *type_option; char *game_name; int master; unsigned short default_port; unsigned short port_offset; int flags; char *game_rule; char *template_var; char *status_packet; int status_len; char *player_packet; int player_len; char *rule_packet; int rule_len; char *master_packet; int master_len; char *master_protocol; char *master_query; DisplayFunc display_player_func; DisplayFunc display_rule_func; DisplayFunc display_raw_player_func; DisplayFunc display_raw_rule_func; DisplayFunc display_xml_player_func; DisplayFunc display_xml_rule_func; DisplayFunc display_json_player_func; DisplayFunc display_json_rule_func; QueryFunc status_query_func; QueryFunc rule_query_func; QueryFunc player_query_func; PacketFunc packet_func; }; extern server_type builtin_types[]; extern server_type *types; extern int n_server_types; extern server_type *default_server_type; server_type *find_server_type_string(char *type_string); #ifdef QUERY_PACKETS #undef QUERY_PACKETS /* QUAKE */ struct q_packet { unsigned char flag1; unsigned char flag2; unsigned short length; unsigned char op_code; unsigned char data[19]; }; #define Q_HEADER_LEN 5 /* * struct { * unsigned char flag1; * unsigned char flag2; * unsigned short length; * unsigned char op_code; * char name[6]; * unsigned char version; * }; */ #define Q_FLAG1 0x80 #define Q_FLAG2 0x00 #define Q_CCREQ_SERVER_INFO 0x02 #define Q_CCREQ_PLAYER_INFO 0x03 #define Q_CCREQ_RULE_INFO 0x04 /* The \003 below is the protocol version */ #define Q_SERVERINFO_LEN 12 struct q_packet q_serverinfo = { Q_FLAG1, Q_FLAG2, Q_SERVERINFO_LEN, Q_CCREQ_SERVER_INFO, "QUAKE\000\003" }; struct q_packet q_rule = { Q_FLAG1, Q_FLAG2, 0, Q_CCREQ_RULE_INFO, "" }; struct q_packet q_player = { Q_FLAG1, Q_FLAG2, 6, Q_CCREQ_PLAYER_INFO, "" }; /* QUAKE WORLD */ struct { char prefix[4]; char command[10]; } qw_serverstatus = { { '\377', '\377', '\377', '\377' }, { 's', 't', 'a', 't', 'u', 's', ' ', '2', '3', '\n' } }; /* QUAKE3 */ struct { char prefix[4]; char command[10]; } q3_serverstatus = { { '\377', '\377', '\377', '\377' }, { 'g', 'e', 't', 's', 't', 'a', 't', 'u', 's', '\n' } }; struct { char prefix[4]; char command[8]; } q3_serverinfo = { { '\377', '\377', '\377', '\377' }, { 'g', 'e', 't', 'i', 'n', 'f', 'o', '\n' } }; /* DOOM 3 */ struct { char prefix[2]; char command[12]; } doom3_serverinfo = { { '\377', '\377' }, { 'g', 'e', 't', 'I', 'n', 'f', 'o', '\0', '\0', '\0', '\0', '\0' } }; /* HALF-LIFE 2 */ char hl2_serverinfo[20] = { '\xFF', '\xFF', '\xFF', '\xFF', 'T', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0' }; char hl2_playerinfo[20] = { '\xFF', '\xFF', '\xFF', '\xFF', 'U', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0' }; char hl2_ruleinfo[20] = { '\xFF', '\xFF', '\xFF', '\xFF', 'V', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0' }; /* HEXEN WORLD */ struct { char prefix[5]; char command[7]; } hw_serverstatus = { { '\377', '\377', '\377', '\377', '\377' }, { 's', 't', 'a', 't', 'u', 's', '\n' } }; /* HEXEN 2 * The \004 below is the protocol version */ #define H2_SERVERINFO_LEN 14 struct q_packet h2_serverinfo = { Q_FLAG1, Q_FLAG2, H2_SERVERINFO_LEN, Q_CCREQ_SERVER_INFO, "HEXENII\000\004" }; /* UNREAL */ char unreal_serverstatus[8] = { '\\', 's', 't', 'a', 't', 'u', 's', '\\' }; /* * char unreal_serverstatus[] = { '\\', 's','t','a','t','u','s', '\\', '\\', 'p','l','a','y','e','r','s', '\\', '\\' }; */ char unreal_masterlist[23] = "\\list\\\\gamename\\unreal"; /* UT 2003 */ char ut2003_basicstatus[] = { 0x78, 0, 0, 0, 0 }; //char ut2003_ruleinfo[] = { 0x78, 0,0,0, 1 }; //char ut2003_playerinfo[] = { 0x78, 0,0,0, 2 }; char ut2003_allinfo[] = { 0x78, 0, 0, 0, 3 }; /* Pariah */ char pariah_basicstatus[] = { 0x77, 0, 0, 0, 0x13 }; /* HALF LIFE */ char hl_ping[9] = { '\377', '\377', '\377', '\377', 'p', 'i', 'n', 'g', '\0' }; char hl_rules[10] = { '\377', '\377', '\377', '\377', 'r', 'u', 'l', 'e', 's', '\0' }; char hl_info[9] = { '\377', '\377', '\377', '\377', 'i', 'n', 'f', 'o', '\0' }; char hl_players[12] = { '\377', '\377', '\377', '\377', 'p', 'l', 'a', 'y', 'e', 'r', 's', '\0' }; char hl_details[12] = { '\377', '\377', '\377', '\377', 'd', 'e', 't', 'a', 'i', 'l', 's', '\0' }; /* QUAKE WORLD MASTER */ #define QW_GET_SERVERS 'c' char qw_masterquery[] = { QW_GET_SERVERS, '\n', '\0' }; /* HEXENWORLD MASTER */ #define HW_GET_SERVERS 'c' char hw_masterquery[] = { '\377', HW_GET_SERVERS, '\0' }; /* QUAKE 2 MASTER */ char q2_masterquery[] = { 'q', 'u', 'e', 'r', 'y', '\n', '\0' }; /* QUAKE 3 MASTER */ char q3_master_query_template[] = "\377\377\377\377getservers %s %s"; char q3_master_default_protocol[] = "68"; char q3_master_default_query[] = "empty full\n"; /* RETURN TO CASTLE WOLFENSTEIN */ char rtcw_master_default_protocol[] = "60"; /* STAR TREK: ELITE FORCE */ char stef_master_default_protocol[] = "24"; /* JEDI KNIGHT: JEDI ACADEMY */ char jk3_master_default_protocol[] = "26"; /* HALF-LIFE MASTER */ char hl_masterquery[4] = { 'e', '\0', '\0', '\0' }; char new_hl_masterquery_prefix[5] = { '1', '\0', '\0', '\0', '\0' }; /* TRIBES */ char tribes_info[] = { '`', '*', '*' }; char tribes_players[] = { 'b', '*', '*' }; /* This is what the game sends to get minimal status * { '\020', '\03', '\377', 0, (unsigned char)0xc5, 6 }; */ char tribes_info_reponse[] = { 'a', '*', '*', 'b' }; char tribes_players_reponse[] = { 'c', '*', '*', 'b' }; char tribes_masterquery[] = { 0x10, 0x3, '\377', 0, 0x2 }; char tribes_master_response[] = { 0x10, 0x6 }; /* GAMESPY */ char gamespy_master_request_prefix[] = "\\list\\\\gamename\\"; char gamespy_master_validate[] = "\\gamename\\gamespy2\\gamever\\020109017\\location\\5\\validate\\12345678\\final\\"; /* TRIBES 2 */ #define TRIBES2_QUERY_GAME_TYPES 2 #define TRIBES2_QUERY_MASTER 6 #define TRIBES2_QUERY_PING 14 #define TRIBES2_QUERY_INFO 18 #define TRIBES2_RESPONSE_GAME_TYPES 4 #define TRIBES2_RESPONSE_MASTER 8 #define TRIBES2_RESPONSE_PING 16 #define TRIBES2_RESPONSE_INFO 20 #define TRIBES2_NO_COMPRESS 2 #define TRIBES2_DEFAULT_PACKET_INDEX 255 #define TRIBES2_STATUS_DEDICATED (1 << 0) #define TRIBES2_STATUS_PASSWORD (1 << 1) #define TRIBES2_STATUS_LINUX (1 << 2) #define TRIBES2_STATUS_TOURNAMENT (1 << 3) #define TRIBES2_STATUS_NOALIAS (1 << 4) #define TRIBES2_STATUS_TEAMDAMAGE (1 << 5) #define TRIBES2_STATUS_TOURNAMENT_VER3 (1 << 6) #define TRIBES2_STATUS_NOALIAS_VER3 (1 << 7) char tribes2_game_types_request[] = { TRIBES2_QUERY_GAME_TYPES, 0, 1, 2, 3, 4 }; char tribes2_ping[] = { TRIBES2_QUERY_PING, TRIBES2_NO_COMPRESS, 1, 2, 3, 4 }; char tribes2_info[] = { TRIBES2_QUERY_INFO, TRIBES2_NO_COMPRESS, 1, 2, 3, 4 }; unsigned char tribes2_masterquery[] = { TRIBES2_QUERY_MASTER, 128, /* <= build 22228, this was 0 */ 11, 12, 13, 14, 255, 3, 'a', 'n', 'y', 3, 'a', 'n', 'y', 0, 255, /* min/max players */ 0xff, 0xff, 0xff,0xff, /* region mask */ 0, 0, 0, 0, /* build version */ 0, /* status */ 255, /* max bots */ 0, 0, /* min cpu */ 0 /* # buddies */ }; #define TRIBES2_ID_OFFSET 2 unsigned char descent3_masterquery[] = { 0x03, /* ID or something */ 0x24, 0x00, 0x00, 0x00, /* packet len */ 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* unknown */ 0x07, /* type */ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* unknown */ 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x00 /* "global" */ }; /* for some reason Descent3 uses a different request for pxo/non-pxo games. blah. */ unsigned char descent3_pxoinfoquery[] = { 0x01, /* "internal descent3 routing" */ 0x29, /* request server info? (pxo listed servers) */ 0x0b, 0x00, /* packet length (- routing byte) */ 0x1b, 0x2f, 0xf4, 0x41, 0x09, 0x00, 0x00, 0x00 /* unknown */ }; unsigned char descent3_tcpipinfoquery[] = { 0x01, /* "internal descent3 routing" */ 0x1e, /* request server info? (tcpip only servers) */ 0x0b, 0x00, /* packet length (- routing byte) */ 0x1b, 0x2f, 0xf4, 0x41, 0x09, 0x00, 0x00, 0x00 /* unknown */ }; /* http://ml.warpcore.org/d3dl/200101/msg00001.html * http://ml.warpcore.org/d3dl/200101/msg00004.html */ unsigned char descent3_playerquery[] = { 0x01, /* "internal descent3 routing" */ 0x72, /* MP_REQUEST_PLAYERLIST */ 0x03, 0x00 /* packet length (- routing byte) */ }; unsigned char ghostrecon_serverquery[] = { 0xc0, 0xde, 0xf1, 0x11, /* const ? header */ 0x42, /* start flag */ 0x03, 0x00, /* data len */ 0xe9, 0x03, 0x00 /* server request ?? */ }; unsigned char ghostrecon_playerquery[] = { 0xc0, 0xde, 0xf1, 0x11, /* const ? header */ 0x42, /* start flag */ 0x06, 0x00, /* data len */ 0xf5, 0x03, 0x00, 0x78, 0x30, 0x63 /* player request ?? may be flag 0xf5; len 0x03,0x00; data 0x78, 0x30, 0x63 */ }; /* All Seeing Eye */ char eye_status_query[1] = "s"; char eye_ping_query[1] = "p"; // Gamespy v2 // Format: // 1 - 3: query head // 4 - 7: queryid // 8: server + rules info (00 to disable) // 9: Player information (00 to disable) // 10: Team information (00 to disable) unsigned char gs2_status_query[] = { 0xfe, 0xfd, 0x00, 0x10, 0x20, 0x30, 0x40, 0xff, 0xff, 0xff }; // Gamespy v3 // Format: // 1 - 3: query head // 4 - 7: queryid // 8: server + rules info (00 to disable) // 9: Player information (00 to disable) // 10: Team information (00 to disable) // 11: Request new format unsigned char gs3_player_query[] = { 0xfe, 0xfd, 0x00, 0x10, 0x20, 0x30, 0x40, 0xff, 0xff, 0xff, 0x01 }; // Format: // 1 - 3: query head // 4 - 7: queryid // 8: requested number of rules // 9 - 9 + no_rules: requested ruleid // last 2 : terminator? // Known ruleid's: // 0x01: hostname // 0x03: version // 0x04: hostport // 0x05: map // 0x06: gametype // 0x07: gamevarient // 0x08: num_players // 0x0a: max_players // 0x0b: gamemode unsigned char gs3_status_query[] = { 0xfe, 0xfd, 0x00, 0x10, 0x20, 0x30, 0x40, 0x06, 0x01, 0x06, 0x05, 0x08, 0x0a, 0x04, 0x00, 0x00 }; // Gamespy v3 + challenge // Format: // 1 - 3: query head // 4 - 7: queryid unsigned char gs3_challenge[] = { 0xfe, 0xfd, 0x09, 0x10, 0x20, 0x30, 0x40 }; // Format: // 1 - 8: Query Request // 9 - 12: Query Header // 13: Query ID // Query ID is made up of the following // 0x01: Basic Info // 0x02: Game Rules // 0x03: Player Information // 0x04: Team Information unsigned char haze_status_query[] = { 'f', 'r', 'd', 'q', 'u', 'e', 'r', 'y', 0x10, 0x20, 0x30, 0x40, 0x0A }; // Format: // 1 - 8: Query Request // 9 - 12: Query Header // 13: Query ID // Query ID is made up of the following // 0x01: Basic Info // 0x02: Game Rules // 0x03: Player Information // 0x04: Team Information unsigned char haze_player_query[] = { 'f', 'r', 'd', 'q', 'u', 'e', 'r', 'y', 0x10, 0x20, 0x30, 0x40, 0x03 }; // Steam // Format: // 1. Request type ( 1 byte ) // 2. Region ( 1 byte ) // 3. ip ( string + null ) // 4. Filter ( optional + null ) // // Regions: // 0 = US East Coast // 1 = US West Coast // 2 = South America // 3 = Europe // 4 = Asia // 5 = Australia // 6 = Middle East // 7 = Africa // 255 = N/A // Filter: // \type\d = Returns only dedicated servers // // \secure\1 = Returns servers running anti-cheat technology // // \gamedir\[mod] = Servers running the specified modification. // The parameter is the directory that the mod resides in e.g. // cstrike for Counter-Strike or dod for Day of Defeat. // // \map\[map] = Returns servers running the specified map // (e.g. de_dust2 or cs_italy) // // \linux\1 = Servers running on the Linux platform // // \empty\1 = Servers that are not empty // // \full\1 = Servers that are not full // // \proxy\1 = Servers that are spectator proxies // // End the filter with 0x00 // unsigned char steam_masterquery_template[] = "1%c%s%c%s"; unsigned char savage_serverquery[] = { 0x9e, 0x4c, 0x23, 0x00, 0x00, 0xc8, 0x01, 0x21, 0x00, 0x00 }; unsigned char savage_playerquery[] = { 0x9e, 0x4c, 0x23, 0x00, 0x00, 0xce, 0x76, 0x46, 0x00, 0x00 }; unsigned char farcry_serverquery[] = { 0x08, 0x80 }; char ravenshield_serverquery[] = "REPORT"; char ottd_master_query[] = { 0x04, 0x00, // packet length 0x06, // packet type 0x01 // packet version }; char ottd_serverinfo[] = { 0x03, 0x00, // packet length 0x00, // packet type }; char ottd_serverdetails[] = { 0x03, 0x00, // packet length 0x02, // packet type }; /* Cube2 */ char cube2_serverstatus[3] = { '\x80', '\x10', '\x27' }; /* Mumble */ char mumble_serverstatus[12] = { '\x00', '\x00', '\x00', '\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08' }; /* Terraria */ char terraria_serverstatus[] = "GET /v2/server/status HTTP/1.1\x0d\x0a\x0d\x0a"; /* SERVER BUILTIN TYPES */ server_type builtin_types[] = { { /* QUAKE */ Q_SERVER, /* id */ "QS", /* type_prefix */ "qs", /* type_string */ "-qs", /* type_option */ "Quake", /* game_name */ 0, /* master */ Q_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_RAW_STYLE_QUAKE, /* flags */ "*gamedir", /* game_rule */ "QUAKE", /* template_var */ (char *)&q_serverinfo, /* status_packet */ Q_SERVERINFO_LEN, /* status_len */ (char *)&q_player, /* player_packet */ Q_HEADER_LEN + 1, /* player_len */ (char *)&q_rule, /* rule_packet */ sizeof(q_rule), /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_q_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_q_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_q_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_q_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_qserver_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_q_packet, /* packet_func */ }, { /* HEXEN 2 */ H2_SERVER, /* id */ "H2S", /* type_prefix */ "h2s", /* type_string */ "-h2s", /* type_option */ "Hexen II", /* game_name */ 0, /* master */ HEXEN2_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_RAW_STYLE_QUAKE, /* flags */ "*gamedir", /* game_rule */ "HEXEN2", /* template_var */ (char *)&h2_serverinfo, /* status_packet */ H2_SERVERINFO_LEN, /* status_len */ (char *)&q_player, /* player_packet */ Q_HEADER_LEN + 1, /* player_len */ (char *)&q_rule, /* rule_packet */ sizeof(q_rule), /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_q_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_q_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_q_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_q_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_qserver_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_q_packet, /* packet_func */ }, { /* QUAKE WORLD */ QW_SERVER, /* id */ "QWS", /* type_prefix */ "qws", /* type_string */ "-qws", /* type_option */ "QuakeWorld", /* game_name */ 0, /* master */ QW_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_SINGLE_QUERY, /* flags */ "*gamedir", /* game_rule */ "QUAKEWORLD", /* template_var */ (char *)&qw_serverstatus, /* status_packet */ sizeof(qw_serverstatus), /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_qw_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_qw_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_qw_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_qw_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_qwserver_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_qw_packet, /* packet_func */ }, { /* HEXEN WORLD */ HW_SERVER, /* id */ "HWS", /* type_prefix */ "hws", /* type_string */ "-hws", /* type_option */ "HexenWorld", /* game_name */ 0, /* master */ HW_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_SINGLE_QUERY, /* flags */ "*gamedir", /* game_rule */ "HEXENWORLD", /* template_var */ (char *)&hw_serverstatus, /* status_packet */ sizeof(hw_serverstatus), /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_qw_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_qw_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_qw_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_qw_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_qwserver_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_qw_packet, /* packet_func */ }, { /* QUAKE 2 */ Q2_SERVER, /* id */ "Q2S", /* type_prefix */ "q2s", /* type_string */ "-q2s", /* type_option */ "Quake II", /* game_name */ 0, /* master */ Q2_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_SINGLE_QUERY, /* flags */ "gamedir", /* game_rule */ "QUAKE2", /* template_var */ (char *)&qw_serverstatus, /* status_packet */ sizeof(qw_serverstatus), /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_q2_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_q2_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_q2_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_q2_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_qwserver_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_qw_packet, /* packet_func */ }, { /* QUAKE 3 */ Q3_SERVER, /* id */ "Q3S", /* type_prefix */ "q3s", /* type_string */ "-q3s", /* type_option */ "Quake III: Arena", /* game_name */ 0, /* master */ Q3_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_QUAKE3_NAMES /* TF_SINGLE_QUERY */, /* flags */ "game", /* game_rule */ "QUAKE3", /* template_var */ (char *)&q3_serverinfo, /* status_packet */ sizeof(q3_serverinfo), /* status_len */ NULL, /* player_packet */ 0, /* player_len */ (char *)&q3_serverstatus, /* rule_packet */ sizeof(q3_serverstatus), /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_q2_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_q2_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_q2_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_q2_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_qwserver_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_qw_packet, /* packet_func */ }, { /* DOOM 3 */ DOOM3_SERVER, /* id */ "DM3S", /* type_prefix */ "dm3s", /* type_string */ "-dm3s", /* type_option */ "Doom 3", /* game_name */ 0, /* master */ DOOM3_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_QUAKE3_NAMES, /* flags */ "fs_game", /* game_rule */ "DOOM3", /* template_var */ (char *)&doom3_serverinfo, /* status_packet */ sizeof(doom3_serverinfo), /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_doom3_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_doom3_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_doom3_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_doom3_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_qwserver_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_doom3_packet, /* packet_func */ }, { /* HALFLIFE 2 */ HL2_SERVER, /* id */ "HL2S", /* type_prefix */ "hl2s", /* type_string */ "-hl2s", /* type_option */ "Half-Life 2", /* game_name */ 0, /* master */ HL2_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_QUAKE3_NAMES, /* flags */ "", /* game_rule */ "HL2", /* template_var */ (char *)&hl2_serverinfo, /* status_packet */ sizeof(hl2_serverinfo), /* status_len */ (char *)&hl2_playerinfo, /* player_packet */ sizeof(hl2_playerinfo), /* player_len */ (char *)&hl2_ruleinfo, /* rule_packet */ sizeof(hl2_ruleinfo), /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_halflife_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_halflife_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_halflife_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_halflife_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_hl2_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_hl2_packet, /* packet_func */ }, { /* RETURN TO CASTLE WOLFENSTEIN */ RTCW_SERVER, /* id */ "RWS", /* type_prefix */ "rws", /* type_string */ "-rws", /* type_option */ "Return to Castle Wolfenstein", /* game_name */ 0, /* master */ RTCW_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_QUAKE3_NAMES /* TF_SINGLE_QUERY */, /* flags */ "game", /* game_rule */ "RTCW", /* template_var */ (char *)&q3_serverinfo, /* status_packet */ sizeof(q3_serverinfo), /* status_len */ NULL, /* player_packet */ 0, /* player_len */ (char *)&q3_serverstatus, /* rule_packet */ sizeof(q3_serverstatus), /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_q2_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_q2_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_q2_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_q2_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_qwserver_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_qw_packet, /* packet_func */ }, { /* STAR TREK: ELITE FORCE */ STEF_SERVER, /* id */ "EFS", /* type_prefix */ "efs", /* type_string */ "-efs", /* type_option */ "Star Trek: Elite Force", /* game_name */ 0, /* master */ STEF_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_QUAKE3_NAMES /* TF_SINGLE_QUERY */, /* flags */ "game", /* game_rule */ "ELITEFORCE", /* template_var */ (char *)&q3_serverinfo, /* status_packet */ sizeof(q3_serverinfo), /* status_len */ NULL, /* player_packet */ 0, /* player_len */ (char *)&q3_serverstatus, /* rule_packet */ sizeof(q3_serverstatus), /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_q2_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_q2_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_q2_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_q2_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_qwserver_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_qw_packet, /* packet_func */ }, { /* JEDI KNIGHT: JEDI ACADEMY */ JK3_SERVER, /* id */ "JK3S", /* type_prefix */ "jk3s", /* type_string */ "-jk3s", /* type_option */ "Jedi Knight: Jedi Academy", /* game_name */ 0, /* master */ JK3_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_QUAKE3_NAMES /* TF_SINGLE_QUERY */, /* flags */ "game", /* game_rule */ "JEDIKNIGHT3", /* template_var */ (char *)&q3_serverinfo, /* status_packet */ sizeof(q3_serverinfo), /* status_len */ NULL, /* player_packet */ 0, /* player_len */ (char *)&q3_serverstatus, /* rule_packet */ sizeof(q3_serverstatus), /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_q2_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_q2_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_q2_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_q2_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_qwserver_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_qw_packet, /* packet_func */ }, { /* UNREAL TOURNAMENT 2003 */ UT2003_SERVER, /* id */ "UT2S", /* type_prefix */ "ut2s", /* type_string */ "-ut2s", /* type_option */ "Unreal Tournament 2003", /* game_name */ 0, /* master */ UNREAL_DEFAULT_PORT, /* default_port */ 1, /* port_offset */ TF_U2_NAMES, /* flags */ "gametype", /* game_rule */ "UNREALTOURNAMENT2003", /* template_var */ (char *)&ut2003_basicstatus, /* status_packet */ sizeof(ut2003_basicstatus), /* status_len */ NULL, /* player_packet */ 0, /* player_len */ (char *)&ut2003_allinfo, /* rule_packet */ sizeof(ut2003_allinfo), /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ 0, /* master_query */ display_unreal_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_unreal_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_unreal_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_unreal_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_ut2003_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_ut2003_packet, /* packet_func */ }, { /* UNREAL */ UN_SERVER, /* id */ "UNS", /* type_prefix */ "uns", /* type_string */ "-uns", /* type_option */ "Unreal", /* game_name */ 0, /* master */ UNREAL_DEFAULT_PORT, /* default_port */ 1, /* port_offset */ TF_SINGLE_QUERY, /* flags */ "gametype", /* game_rule */ "UNREAL", /* template_var */ (char *)&unreal_serverstatus, /* status_packet */ sizeof(unreal_serverstatus), /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_unreal_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_unreal_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_unreal_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_unreal_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_gps_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_gps_packet, /* packet_func */ }, { /* HALF LIFE */ HL_SERVER, /* id */ "HLS", /* type_prefix */ "hls", /* type_string */ "-hls", /* type_option */ "Half-Life", /* game_name */ 0, /* master */ HALFLIFE_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ 0, /* flags */ "game", /* game_rule */ "HALFLIFE", /* template_var */ (char *)&hl_details, /* status_packet */ sizeof(hl_details), /* status_len */ (char *)&hl_players, /* player_packet */ sizeof(hl_players), /* player_len */ (char *)&hl_rules, /* rule_packet */ sizeof(hl_rules), /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_halflife_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_halflife_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_halflife_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_halflife_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_qwserver_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_halflife_packet, /* packet_func */ }, { /* SIN */ SIN_SERVER, /* id */ "SNS", /* type_prefix */ "sns", /* type_string */ "-sns", /* type_option */ "Sin", /* game_name */ 0, /* master */ SIN_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_SINGLE_QUERY, /* flags */ "gamedir", /* game_rule */ "SIN", /* template_var */ (char *)&qw_serverstatus, /* status_packet */ sizeof(qw_serverstatus), /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_q2_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_q2_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_q2_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_q2_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_qwserver_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_qw_packet, /* packet_func */ }, { /* SHOGO */ SHOGO_SERVER, /* id */ "SGS", /* type_prefix */ "sgs", /* type_string */ "-sgs", /* type_option */ "Shogo: Mobile Armor Division", /* game_name */ 0, /* master */ SHOGO_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ 0, /* flags */ "", /* game_rule */ "SHOGO", /* template_var */ (char *)&unreal_serverstatus, /* status_packet */ sizeof(unreal_serverstatus), /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_q2_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_q2_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_q2_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_q2_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_gps_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_gps_packet, /* packet_func */ }, { /* TRIBES */ TRIBES_SERVER, /* id */ "TBS", /* type_prefix */ "tbs", /* type_string */ "-tbs", /* type_option */ "Tribes", /* game_name */ 0, /* master */ TRIBES_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_SINGLE_QUERY, /* flags */ "game", /* game_rule */ "TRIBES", /* template_var */ (char *)&tribes_info, /* status_packet */ sizeof(tribes_info), /* status_len */ (char *)&tribes_players, /* player_packet */ sizeof(tribes_players), /* player_len */ (char *)&tribes_players, /* rule_packet */ sizeof(tribes_players), /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_tribes_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_tribes_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_tribes_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_tribes_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_tribes_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_tribes_packet, /* packet_func */ }, { /* BFRIS */ BFRIS_SERVER, /* id */ "BFS", /* type_prefix */ "bfs", /* type_string */ "-bfs", /* type_option */ "BFRIS", /* game_name */ 0, /* master */ BFRIS_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_TCP_CONNECT, /* flags */ "Rules", /* game_rule */ "BFRIS", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_bfris_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_bfris_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_bfris_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_bfris_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_bfris_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_bfris_packet, /* packet_func */ }, { /* KINGPIN */ KINGPIN_SERVER, /* id */ "KPS", /* type_prefix */ "kps", /* type_string */ "-kps", /* type_option */ "Kingpin", /* game_name */ 0, /* master */ KINGPIN_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_SINGLE_QUERY, /* flags */ "gamedir", /* game_rule */ "KINGPIN", /* template_var */ (char *)&qw_serverstatus, /* status_packet */ sizeof(qw_serverstatus), /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_q2_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_q2_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_q2_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_q2_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_qwserver_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_qw_packet, /* packet_func */ }, { /* HERETIC II */ HERETIC2_SERVER, /* id */ "HRS", /* type_prefix */ "hrs", /* type_string */ "-hrs", /* type_option */ "Heretic II", /* game_name */ 0, /* master */ HERETIC2_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_SINGLE_QUERY, /* flags */ "gamedir", /* game_rule */ "HERETIC2", /* template_var */ (char *)&qw_serverstatus, /* status_packet */ sizeof(qw_serverstatus), /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_q2_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_q2_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_q2_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_q2_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_qwserver_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_qw_packet, /* packet_func */ }, { /* SOLDIER OF FORTUNE */ SOF_SERVER, /* id */ "SFS", /* type_prefix */ "sfs", /* type_string */ "-sfs", /* type_option */ "Soldier of Fortune", /* game_name */ 0, /* master */ SOF_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_SINGLE_QUERY | TF_SOF_NAMES, /* flags */ "gamedir", /* game_rule */ "SOLDIEROFFORTUNE", /* template_var */ (char *)&qw_serverstatus, /* status_packet */ sizeof(qw_serverstatus), /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_q2_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_q2_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_q2_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_q2_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_qwserver_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_qw_packet, /* packet_func */ }, { /* GAMESPY PROTOCOL */ GAMESPY_PROTOCOL_SERVER, /* id */ "GPS", /* type_prefix */ "gps", /* type_string */ "-gps", /* type_option */ "Gamespy Protocol", /* game_name */ 0, /* master */ 0, /* default_port */ 0, /* port_offset */ TF_SINGLE_QUERY | TF_U2_NAMES, /* flags */ "gametype", /* game_rule */ "GAMESPYPROTOCOL", /* template_var */ (char *)&unreal_serverstatus, /* status_packet */ sizeof(unreal_serverstatus), /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_unreal_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_unreal_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_unreal_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_unreal_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_gps_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_gps_packet, /* packet_func */ }, { /* TRIBES 2 */ TRIBES2_SERVER, /* id */ "T2S", /* type_prefix */ "t2s", /* type_string */ "-t2s", /* type_option */ "Tribes 2", /* game_name */ 0, /* master */ TRIBES2_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ 0, /* flags */ "game", /* game_rule */ "TRIBES2", /* template_var */ (char *)&tribes2_ping, /* status_packet */ sizeof(tribes2_ping), /* status_len */ (char *)&tribes2_info, /* player_packet */ sizeof(tribes2_info), /* player_len */ (char *)NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_tribes2_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_tribes2_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_tribes2_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_tribes2_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_tribes2_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_tribes2_packet, /* packet_func */ }, { /* DESCENT3 GAMESPY PROTOCOL */ DESCENT3_GAMESPY_SERVER, /* id */ "D3G", /* type_prefix */ "d3g", /* type_string */ "-d3g", /* type_option */ "Descent3 Gamespy Protocol", /* game_name */ 0, /* master */ DESCENT3_GAMESPY_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_SINGLE_QUERY, /* flags */ "gametype", /* game_rule */ "DESCENT3", /* template_var */ (char *)&unreal_serverstatus, /* status_packet */ sizeof(unreal_serverstatus), /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_descent3_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_descent3_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_descent3_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_descent3_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_gps_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_gps_packet, /* packet_func */ }, { /* DESCENT3 PROTOCOL */ DESCENT3_SERVER, /* id */ "D3S", /* type_prefix */ "d3s", /* type_string */ "-d3s", /* type_option */ "Descent3", /* game_name */ 0, /* master */ DESCENT3_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ 0, /* flags */ "gametype", /* game_rule */ "DESCENT3", /* template_var */ (char *)&descent3_tcpipinfoquery, /* status_packet */ sizeof(descent3_tcpipinfoquery), /* status_len */ (char *)&descent3_playerquery, /* player_packet */ sizeof(descent3_playerquery), /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_descent3_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_descent3_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_descent3_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_descent3_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_gps_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_descent3_packet, /* packet_func */ }, { /* DESCENT3 PROTOCOL */ DESCENT3_PXO_SERVER, /* id */ "D3P", /* type_prefix */ "d3p", /* type_string */ "-d3p", /* type_option */ "Descent3 PXO protocol", /* game_name */ 0, /* master */ DESCENT3_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ 0, /* flags */ "gametype", /* game_rule */ "DESCENT3", /* template_var */ (char *)&descent3_pxoinfoquery, /* status_packet */ sizeof(descent3_pxoinfoquery), /* status_len */ (char *)&descent3_playerquery, /* player_packet */ sizeof(descent3_playerquery), /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_descent3_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_descent3_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_descent3_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_descent3_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_gps_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_descent3_packet, /* packet_func */ }, { /* GHOSTRECON PROTOCOL */ GHOSTRECON_SERVER, /* id */ "GRS", /* type_prefix */ "grs", /* type_string */ "-grs", /* type_option */ "Ghost Recon", /* game_name */ 0, /* master */ GHOSTRECON_PLAYER_DEFAULT_PORT, /* default_port */ 2, /* port_offset */ TF_QUERY_ARG, /* flags */ "gametype", /* game_rule */ "GHOSTRECON", /* template_var */ (char *)&ghostrecon_playerquery, /* status_packet */ sizeof(ghostrecon_playerquery), /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_ghostrecon_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_ghostrecon_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_ghostrecon_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_ghostrecon_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_qserver_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_ghostrecon_packet, /* packet_func */ }, { /* ALL SEEING EYE PROTOCOL */ ALLSEEINGEYE_PROTOCOL_SERVER, /* id */ "EYE", /* type_prefix */ "eye", /* type_string */ "-eye", /* type_option */ "All Seeing Eye Protocol", /* game_name */ 0, /* master */ 0, /* default_port */ 123, /* port_offset */ TF_SINGLE_QUERY, /* flags */ "gametype", /* game_rule */ "EYEPROTOCOL", /* template_var */ (char *)&eye_status_query, /* status_packet */ sizeof(eye_status_query), /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_eye_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_eye_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_eye_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_eye_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_qserver_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_eye_packet, /* packet_func */ }, { /* GAMESPY V2 PROTOCOL */ GAMESPY2_PROTOCOL_SERVER, /* id */ "GS2", /* type_prefix */ "gs2", /* type_string */ "-gs2", /* type_option */ "Gamespy V2 Protocol", /* game_name */ 0, /* master */ 0, /* default_port */ 0, /* port_offset */ TF_SINGLE_QUERY, /* flags */ "gametype", /* game_rule */ "GPS2PROTOCOL", /* template_var */ (char *)&gs2_status_query, /* status_packet */ sizeof(gs2_status_query), /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_gs2_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_gs2_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_gs2_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_gs2_packet, /* packet_func */ }, { /* AMERICA'S ARMY EXTENSION (GS2 BASED) */ ARMYOPS_SERVER, /* id */ "AMS", /* type_prefix */ "ams", /* type_string */ "-ams", /* type_option */ "America's Army v2.x", /* game_name */ 0, /* master */ 1716, /* default_port */ 1, /* port_offset */ TF_SINGLE_QUERY | TF_U2_NAMES, /* flags */ "gametype", /* game_rule */ "AMERICASARMY", /* template_var */ (char *)&gs2_status_query, /* status_packet */ sizeof(gs2_status_query), /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_armyops_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_armyops_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_armyops_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_armyops_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_gs2_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_gs2_packet, /* packet_func */ }, { /* RAVENSHIELD PROTOCOL */ RAVENSHIELD_SERVER, /* id */ "RSS", /* type_prefix */ "rss", /* type_string */ "-rss", /* type_option */ "Ravenshield", /* game_name */ 0, /* master */ RAVENSHIELD_DEFAULT_PORT, /* default_port */ 1000, /* port_offset */ TF_QUERY_ARG, /* flags */ "gametype", /* game_rule */ "RAVENSHIELD", /* template_var */ (char *)ravenshield_serverquery, /* status_packet */ sizeof(ravenshield_serverquery) - 1, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_ravenshield_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_ravenshield_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_ravenshield_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_ravenshield_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_qserver_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_ravenshield_packet, /* packet_func */ }, { /* SAVAGE PROTOCOL */ SAVAGE_SERVER, /* id */ "SAS", /* type_prefix */ "sas", /* type_string */ "-sas", /* type_option */ "Savage", /* game_name */ 0, /* master */ SAVAGE_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_QUERY_ARG, /* flags */ "gametype", /* game_rule */ "SAVAGE", /* template_var */ (char *)savage_serverquery, /* status_packet */ sizeof(savage_serverquery) - 1, /* status_len */ (char *)savage_playerquery, /* player_packet */ sizeof(savage_playerquery) - 1, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_savage_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_savage_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_savage_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_savage_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_savage_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_savage_packet, /* packet_func */ }, { /* FARCRY PROTOCOL */ FARCRY_SERVER, /* id */ "FCS", /* type_prefix */ "fcs", /* type_string */ "-fcs", /* type_option */ "FarCry", /* game_name */ 0, /* master */ FARCRY_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_QUERY_ARG, /* flags */ "gametype", /* game_rule */ "FARCRY", /* template_var */ (char *)farcry_serverquery, /* status_packet */ sizeof(savage_serverquery) - 1, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_farcry_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_farcry_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_farcry_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_farcry_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_farcry_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_farcry_packet, /* packet_func */ }, /* --- MASTER SERVERS --- */ { /* QUAKE WORLD MASTER */ QW_MASTER, /* id */ "QWM", /* type_prefix */ "qwm", /* type_string */ "-qwm", /* type_option */ /* ## also "-qw" */ "QuakeWorld Master", /* game_name */ QW_SERVER, /* master */ QW_MASTER_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_SINGLE_QUERY | TF_OUTFILE, /* flags */ "", /* game_rule */ "QWMASTER", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ (char *)&qw_masterquery, /* master_packet */ sizeof(qw_masterquery), /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_qwmaster, /* display_player_func */ NULL, /* display_rule_func */ NULL, /* display_raw_player_func */ NULL, /* display_raw_rule_func */ NULL, /* display_xml_player_func */ NULL, /* display_xml_rule_func */ NULL, /* display_json_player_func */ NULL, /* display_json_rule_func */ send_qwmaster_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_qwmaster_packet, /* packet_func */ }, { /* HEXEN2WORLD MASTER */ HW_MASTER, /* id */ "HWM", /* type_prefix */ "hwm", /* type_string */ "-hwm", /* type_option */ /* ## also "-qw" */ "HexenWorld Master", /* game_name */ HW_SERVER, /* master */ HW_MASTER_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_SINGLE_QUERY | TF_OUTFILE, /* flags */ "", /* game_rule */ "HWMASTER", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ (char *)&hw_masterquery, /* master_packet */ sizeof(hw_masterquery), /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_qwmaster, /* display_player_func */ NULL, /* display_rule_func */ NULL, /* display_raw_player_func */ NULL, /* display_raw_rule_func */ NULL, /* display_xml_player_func */ NULL, /* display_xml_rule_func */ NULL, /* display_json_player_func */ NULL, /* display_json_rule_func */ send_qwmaster_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_qwmaster_packet, /* packet_func */ }, { /* QUAKE 2 MASTER */ Q2_MASTER, /* id */ "Q2M", /* type_prefix */ "q2m", /* type_string */ "-q2m", /* type_option */ /* ## also "-qw" */ "Quake II Master", /* game_name */ Q2_SERVER, /* master */ Q2_MASTER_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_SINGLE_QUERY | TF_OUTFILE, /* flags */ "", /* game_rule */ "Q2MASTER", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ q2_masterquery, /* master_packet */ sizeof(q2_masterquery), /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_qwmaster, /* display_player_func */ NULL, /* display_rule_func */ NULL, /* display_raw_player_func */ NULL, /* display_raw_rule_func */ NULL, /* display_xml_player_func */ NULL, /* display_xml_rule_func */ NULL, /* display_json_player_func */ NULL, /* display_json_rule_func */ send_qwmaster_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_qwmaster_packet, /* packet_func */ }, { /* QUAKE 3 MASTER */ Q3_MASTER, /* id */ "Q3M", /* type_prefix */ "q3m", /* type_string */ "-q3m", /* type_option */ "Quake III Master", /* game_name */ Q3_SERVER, /* master */ Q3_MASTER_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_OUTFILE | TF_QUERY_ARG, /* flags */ "", /* game_rule */ "Q3MASTER", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ q3_master_query_template, /* master_packet */ 0, /* master_len */ q3_master_default_protocol, /* master_protocol */ q3_master_default_query, /* master_query */ display_qwmaster, /* display_player_func */ NULL, /* display_rule_func */ NULL, /* display_raw_player_func */ NULL, /* display_raw_rule_func */ NULL, /* display_xml_player_func */ NULL, /* display_xml_rule_func */ NULL, /* display_json_player_func */ NULL, /* display_json_rule_func */ send_qwmaster_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_qwmaster_packet, /* packet_func */ }, { /* DOOM 3 MASTER */ DOOM3_MASTER, /* id */ "DM3M", /* type_prefix */ "dm3m", /* type_string */ "-dm3m", /* type_option */ "Doom 3 Master", /* game_name */ DOOM3_SERVER, /* master */ DOOM3_MASTER_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_OUTFILE | TF_QUERY_ARG, /* flags */ "", /* game_rule */ "DOOM3MASTER", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_qwmaster, /* display_player_func */ NULL, /* display_rule_func */ NULL, /* display_raw_player_func */ NULL, /* display_raw_rule_func */ NULL, /* display_xml_player_func */ NULL, /* display_xml_rule_func */ NULL, /* display_json_player_func */ NULL, /* display_json_rule_func */ send_doom3master_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_doom3master_packet, /* packet_func */ }, { /* RETURN TO CASTLE WOLFENSTEIN MASTER */ RTCW_MASTER, /* id */ "RWM", /* type_prefix */ "rwm", /* type_string */ "-rwm", /* type_option */ "Return to Castle Wolfenstein Master", /* game_name */ RTCW_SERVER, /* master */ RTCW_MASTER_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_OUTFILE | TF_QUERY_ARG, /* flags */ "", /* game_rule */ "RTCWMASTER", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ q3_master_query_template, /* master_packet */ 0, /* master_len */ rtcw_master_default_protocol, /* master_protocol */ q3_master_default_query, /* master_query */ display_qwmaster, /* display_player_func */ NULL, /* display_rule_func */ NULL, /* display_raw_player_func */ NULL, /* display_raw_rule_func */ NULL, /* display_xml_player_func */ NULL, /* display_xml_rule_func */ NULL, /* display_json_player_func */ NULL, /* display_json_rule_func */ send_qwmaster_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_qwmaster_packet, /* packet_func */ }, { /* STAR TREK: ELITE FORCE MASTER */ STEF_MASTER, /* id */ "EFM", /* type_prefix */ "efm", /* type_string */ "-efm", /* type_option */ "Star Trek: Elite Force", /* game_name */ STEF_SERVER, /* master */ STEF_MASTER_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_OUTFILE | TF_QUERY_ARG, /* flags */ "", /* game_rule */ "ELITEFORCEMASTER", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ q3_master_query_template, /* master_packet */ 0, /* master_len */ stef_master_default_protocol, /* master_protocol */ q3_master_default_query, /* master_query */ display_qwmaster, /* display_player_func */ NULL, /* display_rule_func */ NULL, /* display_raw_player_func */ NULL, /* display_raw_rule_func */ NULL, /* display_xml_player_func */ NULL, /* display_xml_rule_func */ NULL, /* display_json_player_func */ NULL, /* display_json_rule_func */ send_qwmaster_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_qwmaster_packet, /* packet_func */ }, { /* JEDI KNIGHT 3 FORCE MASTER */ JK3_MASTER, /* id */ "JK3M", /* type_prefix */ "jk3m", /* type_string */ "-jk3m", /* type_option */ "Jedi Knight: Jedi Academy", /* game_name */ JK3_SERVER, /* master */ JK3_MASTER_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_OUTFILE | TF_QUERY_ARG, /* flags */ "", /* game_rule */ "JK3MASTER", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ q3_master_query_template, /* master_packet */ 0, /* master_len */ jk3_master_default_protocol, /* master_protocol */ NULL, /* master_query */ display_qwmaster, /* display_player_func */ NULL, /* display_rule_func */ NULL, /* display_raw_player_func */ NULL, /* display_raw_rule_func */ NULL, /* display_xml_player_func */ NULL, /* display_xml_rule_func */ NULL, /* display_json_player_func */ NULL, /* display_json_rule_func */ send_qwmaster_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_qwmaster_packet, /* packet_func */ }, { /* HALF-LIFE MASTER */ HL_MASTER, /* id */ "HLM", /* type_prefix */ "hlm", /* type_string */ "-hlm", /* type_option */ /* ## also "-qw" */ "Half-Life Master", /* game_name */ HL_SERVER, /* master */ HL_MASTER_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_SINGLE_QUERY | TF_OUTFILE | TF_QUERY_ARG, /* flags */ "", /* game_rule */ "HLMASTER", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ (char *)&new_hl_masterquery_prefix, /* master_packet */ sizeof(new_hl_masterquery_prefix), /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_qwmaster, /* display_player_func */ NULL, /* display_rule_func */ NULL, /* display_raw_player_func */ NULL, /* display_raw_rule_func */ NULL, /* display_xml_player_func */ NULL, /* display_xml_rule_func */ NULL, /* display_json_player_func */ NULL, /* display_json_rule_func */ send_qwmaster_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_qwmaster_packet, /* packet_func */ }, { /* TRIBES MASTER */ TRIBES_MASTER, /* id */ "TBM", /* type_prefix */ "tbm", /* type_string */ "-tbm", /* type_option */ "Tribes Master", /* game_name */ TRIBES_SERVER, /* master */ TRIBES_MASTER_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_OUTFILE, /* flags */ "", /* game_rule */ "TRIBESMASTER", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ (char *)&tribes_masterquery, /* master_packet */ sizeof(tribes_masterquery), /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_qwmaster, /* display_player_func */ NULL, /* display_rule_func */ NULL, /* display_raw_player_func */ NULL, /* display_raw_rule_func */ NULL, /* display_xml_player_func */ NULL, /* display_xml_rule_func */ NULL, /* display_json_player_func */ NULL, /* display_json_rule_func */ send_qwmaster_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_tribesmaster_packet, /* packet_func */ }, { /* GAMESPY MASTER */ GAMESPY_MASTER, /* id */ "GSM", /* type_prefix */ "gsm", /* type_string */ "-gsm", /* type_option */ "Gamespy Master", /* game_name */ GAMESPY_PROTOCOL_SERVER, /* master */ GAMESPY_MASTER_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_OUTFILE | TF_TCP_CONNECT | TF_QUERY_ARG | TF_QUERY_ARG_REQUIRED, /* flags */ "", /* game_rule */ "GAMESPYMASTER", /* template_var */ (char *)&gamespy_master_request_prefix, /* status_packet */ sizeof(gamespy_master_request_prefix) - 1, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ (char *)&gamespy_master_validate, /* master_packet */ sizeof(gamespy_master_validate) - 1, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_qwmaster, /* display_player_func */ NULL, /* display_rule_func */ NULL, /* display_raw_player_func */ NULL, /* display_raw_rule_func */ NULL, /* display_xml_player_func */ NULL, /* display_xml_rule_func */ NULL, /* display_json_player_func */ NULL, /* display_json_rule_func */ send_gamespy_master_request, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_gamespy_master_response, /* packet_func */ }, { /* TRIBES 2 MASTER */ TRIBES2_MASTER, /* id */ "T2M", /* type_prefix */ "t2m", /* type_string */ "-t2m", /* type_option */ "Tribes 2 Master", /* game_name */ TRIBES2_SERVER, /* master */ TRIBES2_MASTER_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_OUTFILE | TF_QUERY_ARG, /* flags */ "", /* game_rule */ "TRIBES2MASTER", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ (char *)&tribes2_masterquery, /* master_packet */ sizeof(tribes2_masterquery), /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_qwmaster, /* display_player_func */ NULL, /* display_rule_func */ NULL, /* display_raw_player_func */ NULL, /* display_raw_rule_func */ NULL, /* display_xml_player_func */ NULL, /* display_xml_rule_func */ NULL, /* display_json_player_func */ NULL, /* display_json_rule_func */ send_tribes2master_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_tribes2master_packet, /* packet_func */ }, { /* DESCENT3 MASTER */ DESCENT3_MASTER, /* id */ "D3M", /* type_prefix */ "d3m", /* type_string */ "-d3m", /* type_option */ /* ## also "-qw" */ "Descent3 Master (PXO)", /* game_name */ DESCENT3_PXO_SERVER, /* master */ DESCENT3_MASTER_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_MASTER_MULTI_RESPONSE | TF_OUTFILE, /* flags */ "", /* game_rule */ "D3MASTER", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ (char *)descent3_masterquery, /* master_packet */ sizeof(descent3_masterquery), /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_qwmaster, /* display_player_func */ NULL, /* display_rule_func */ NULL, /* display_raw_player_func */ NULL, /* display_raw_rule_func */ NULL, /* display_xml_player_func */ NULL, /* display_xml_rule_func */ NULL, /* display_json_player_func */ NULL, /* display_json_rule_func */ send_qwmaster_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_descent3master_packet, /* packet_func */ }, { /* STEAM MASTER */ STEAM_MASTER, /* id */ "STM", /* type_prefix */ "stm", /* type_string */ "-stm", /* type_option */ /* ## also "-qw" */ "Steam Master", /* game_name */ A2S_SERVER, /* master */ STEAM_MASTER_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_SINGLE_QUERY | TF_OUTFILE | TF_QUERY_ARG | TF_MASTER_STEAM, /* flags */ "", /* game_rule */ "STEAMMASTER", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ (char *)&steam_masterquery_template, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_qwmaster, /* display_player_func */ NULL, /* display_rule_func */ NULL, /* display_raw_player_func */ NULL, /* display_raw_rule_func */ NULL, /* display_xml_player_func */ NULL, /* display_xml_rule_func */ NULL, /* display_json_player_func */ NULL, /* display_json_rule_func */ send_qwmaster_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_qwmaster_packet, /* packet_func */ }, { /* UT2004 MASTER */ UT2004_MASTER, /* id */ "UT2004M", /* type_prefix */ "ut2004m", /* type_string */ "-ut2004m", /* type_option */ "UT2004 Master", /* game_name */ UT2003_SERVER, /* master */ 28902, /* default_port */ 0, /* port_offset */ TF_OUTFILE | TF_QUERY_ARG | TF_TCP_CONNECT, /* flags */ "", /* game_rule */ "UT2004MASTER", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_qwmaster, /* display_player_func */ NULL, /* display_rule_func */ NULL, /* display_raw_player_func */ NULL, /* display_raw_rule_func */ NULL, /* display_xml_player_func */ NULL, /* display_xml_rule_func */ NULL, /* display_json_player_func */ NULL, /* display_json_rule_func */ send_ut2004master_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_ut2004master_packet, /* packet_func */ }, { /* HALFLIFE 2 */ A2S_SERVER, /* id */ "A2S", /* type_prefix */ "a2s", /* type_string */ "-a2s", /* type_option */ "Half-Life 2 new", /* game_name */ 0, /* master */ HL2_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_QUAKE3_NAMES, /* flags */ "gamedir", /* game_rule */ "A2S", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_halflife_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_halflife_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_halflife_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_halflife_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_a2s_request_packet, /* status_query_func */ send_a2s_rule_request_packet, /* rule_query_func */ NULL, /* player_query_func */ deal_with_a2s_packet, /* packet_func */ }, { /* PARIAH */ PARIAH_SERVER, /* id */ "PRS", /* type_prefix */ "prs", /* type_string */ "-prs", /* type_option */ "Pariah", /* game_name */ 0, /* master */ UNREAL_DEFAULT_PORT, /* default_port */ 1, /* port_offset */ TF_U2_NAMES, /* flags */ "gametype", /* game_rule */ "UNREALTOURNAMENT2003", /* template_var */ (char *)&pariah_basicstatus, /* status_packet */ sizeof(pariah_basicstatus), /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ 0, /* master_query */ display_unreal_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_unreal_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_unreal_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_unreal_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_ut2003_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_ut2003_packet, /* packet_func */ }, { /* GAMESPY V3 PROTOCOL */ GAMESPY3_PROTOCOL_SERVER, /* id */ "GS3", /* type_prefix */ "gs3", /* type_string */ "-gs3", /* type_option */ "Gamespy V3 Protocol", /* game_name */ 0, /* master */ 0, /* default_port */ 0, /* port_offset */ TF_SINGLE_QUERY, /* flags */ "gametype", /* game_rule */ "GPS3PROTOCOL", /* template_var */ (char *)&gs3_status_query, /* status_packet */ sizeof(gs3_status_query), /* status_len */ (char *)&gs3_player_query, /* player_packet */ sizeof(gs3_player_query), /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_gs2_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_gs2_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_gs3_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_gs3_packet, /* packet_func */ }, { /* TEAMSPEAK 2 PROTOCOL */ TS2_PROTOCOL_SERVER, /* id */ "TS2", /* type_prefix */ "ts2", /* type_string */ "-ts2", /* type_option */ "Teamspeak 2", /* game_name */ 0, /* master */ 0, /* default_port */ 0, /* port_offset */ TF_TCP_CONNECT | TF_QUERY_ARG_REQUIRED | TF_QUERY_ARG, /* flags */ "N/A", /* game_rule */ "TS2PROTOCOL", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_ts2_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_ts2_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_ts2_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_ts2_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_ts2_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_ts2_packet, /* packet_func */ }, { /* QUAKE 4 */ QUAKE4_SERVER, /* id */ "Q4S", /* type_prefix */ "q4s", /* type_string */ "-q4s", /* type_option */ "Quake 4", /* game_name */ 0, /* master */ QUAKE4_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_QUAKE3_NAMES, /* flags */ "fs_game", /* game_rule */ "QUAKE4", /* template_var */ (char *)&doom3_serverinfo, /* status_packet */ sizeof(doom3_serverinfo), /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_doom3_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_doom3_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_doom3_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_doom3_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_qwserver_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_quake4_packet, /* packet_func */ }, { /* QUAKE 4 MASTER */ QUAKE4_MASTER, /* id */ "Q4M", /* type_prefix */ "q4m", /* type_string */ "-q4m", /* type_option */ "Quake 4 Master", /* game_name */ QUAKE4_SERVER, /* master */ QUAKE4_MASTER_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_OUTFILE | TF_QUERY_ARG, /* flags */ "", /* game_rule */ "QUAKE4MASTER", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_qwmaster, /* display_player_func */ NULL, /* display_rule_func */ NULL, /* display_raw_player_func */ NULL, /* display_raw_rule_func */ NULL, /* display_xml_player_func */ NULL, /* display_xml_rule_func */ NULL, /* display_json_player_func */ NULL, /* display_json_rule_func */ send_quake4master_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_doom3master_packet, /* packet_func */ }, { /* GAMESPY V4 PROTOCOL */ GAMESPY4_PROTOCOL_SERVER, /* id */ "GS4", /* type_prefix */ "gs4", /* type_string */ "-gs4", /* type_option */ "Gamespy V4 Protocol", /* game_name */ 0, /* master */ 0, /* default_port */ 0, /* port_offset */ TF_SINGLE_QUERY, /* flags */ "gametype", /* game_rule */ "GPS4PROTOCOL", /* template_var */ (char *)&gs3_challenge, /* status_packet */ sizeof(gs3_challenge), /* status_len */ (char *)&gs3_challenge, /* player_packet */ sizeof(gs3_challenge), /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_gs2_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_gs2_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_gs3_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_gs3_packet, /* packet_func */ }, { /* PREY */ PREY_SERVER, /* id */ "PREYS", /* type_prefix */ "preys", /* type_string */ "-preys", /* type_option */ "PREY", /* game_name */ 0, /* master */ PREY_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_QUAKE3_NAMES, /* flags */ "fs_game", /* game_rule */ "PREY", /* template_var */ (char *)&doom3_serverinfo, /* status_packet */ sizeof(doom3_serverinfo), /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_doom3_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_doom3_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_doom3_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_doom3_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_qwserver_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_prey_packet, /* packet_func */ }, { /* TRACKMANIA PROTOCOL */ TM_PROTOCOL_SERVER, /* id */ "TM", /* type_prefix */ "tm", /* type_string */ "-tm", /* type_option */ "TrackMania", /* game_name */ 0, /* master */ 0, /* default_port */ 0, /* port_offset */ TF_TCP_CONNECT | TF_QUERY_ARG | TF_TM_NAMES, /* flags */ "N/A", /* game_rule */ "TMPROTOCOL", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_tm_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_tm_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_tm_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_tm_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_tm_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_tm_packet, /* packet_func */ }, { /* Enemy Territory : QuakeWars */ ETQW_SERVER, /* id */ "ETQWS", /* type_prefix */ "etqws", /* type_string */ "-etqws", /* type_option */ "QuakeWars", /* game_name */ 0, /* master */ ETQW_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_QUAKE3_NAMES, /* flags */ "fs_game", /* game_rule */ "QUAKE4", /* template_var */ (char *)&doom3_serverinfo, /* status_packet */ sizeof(doom3_serverinfo), /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_doom3_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_doom3_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_doom3_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_doom3_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_qwserver_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_etqw_packet, /* packet_func */ }, { /* HAZE PROTOCOL */ HAZE_SERVER, /* id */ "HAZES", /* type_prefix */ "hazes", /* type_string */ "-hazes", /* type_option */ "Haze Protocol", /* game_name */ 0, /* master */ 0, /* default_port */ 0, /* port_offset */ TF_SINGLE_QUERY, /* flags */ "gametype", /* game_rule */ "HAZE", /* template_var */ (char *)&haze_status_query, /* status_packet */ sizeof(haze_status_query), /* status_len */ (char *)&haze_player_query, /* player_packet */ sizeof(haze_player_query), /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_gs2_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_gs2_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_haze_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_haze_packet, /* packet_func */ }, { /* World in Confict PROTOCOL */ WIC_PROTOCOL_SERVER, /* id */ "WICS", /* type_prefix */ "wics", /* type_string */ "-wics", /* type_option */ "World in Conflict", /* game_name */ 0, /* master */ 0, /* default_port */ 0, /* port_offset */ TF_TCP_CONNECT | TF_QUERY_ARG_REQUIRED | TF_QUERY_ARG, /* flags */ "N/A", /* game_rule */ "WICPROTOCOL", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_wic_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_wic_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_wic_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_wic_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_wic_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_wic_packet, /* packet_func */ }, { /* openTTD */ OTTD_SERVER, /* id */ "OTTDS", /* type_prefix */ "ottds", /* type_string */ "-ottds", /* type_option */ "OpenTTD", /* game_name */ 0, /* master */ 3979, /* default_port */ 0, /* port_offset */ 0, /* flags */ "", /* game_rule */ "OPENTTD", /* template_var */ (char *)&ottd_serverinfo, /* status_packet */ sizeof(ottd_serverinfo), /* status_len */ NULL, /* player_packet */ 0, /* player_len */ (char *)&ottd_serverdetails, /* rule_packet */ sizeof(ottd_serverdetails), /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_q2_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_q2_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_ottd_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_ottd_packet, /* packet_func */ }, { /* openTTD Master */ OTTD_MASTER, /* id */ "OTTDM", /* type_prefix */ "ottdm", /* type_string */ "-ottdm", /* type_option */ "openTTD Master", /* game_name */ OTTD_SERVER, /* master */ 3978, /* default_port */ 0, /* port_offset */ TF_OUTFILE | TF_QUERY_ARG, /* flags */ "", /* game_rule */ "OTTDMASTER", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ ottd_master_query, /* master_packet */ sizeof(ottd_master_query), /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_qwmaster, /* display_player_func */ NULL, /* display_rule_func */ NULL, /* display_raw_player_func */ NULL, /* display_raw_rule_func */ NULL, /* display_xml_player_func */ NULL, /* display_xml_rule_func */ NULL, /* display_json_player_func */ NULL, /* display_json_rule_func */ send_ottdmaster_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_ottdmaster_packet, /* packet_func */ }, { /* Frontlines-Fuel of War */ FL_SERVER, /* id */ "FLS", /* type_prefix */ "fls", /* type_string */ "-fls", /* type_option */ "Frontlines-Fuel of War", /* game_name */ 0, /* master */ FL_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_QUAKE3_NAMES, /* flags */ "gamedir", /* game_rule */ "FLS", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_fl_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_fl_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_fl_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_fl_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_fl_request_packet, /* status_query_func */ send_fl_rule_request_packet, /* rule_query_func */ NULL, /* player_query_func */ deal_with_fl_packet, /* packet_func */ }, { /* Wolfenstein */ WOLF_SERVER, /* id */ "WOLF", /* type_prefix */ "wolfs", /* type_string */ "-wolfs", /* type_option */ "Wolfenstein", /* game_name */ 0, /* master */ WOLF_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_QUAKE3_NAMES, /* flags */ "fs_game", /* game_rule */ "QUAKE4", /* template_var */ (char *)&doom3_serverinfo, /* status_packet */ sizeof(doom3_serverinfo), /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_doom3_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_doom3_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_doom3_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_doom3_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_qwserver_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_wolf_packet, /* packet_func */ }, { /* Teeworlds */ TEE_SERVER, /* id */ "TEES", /* type_prefix */ "tees", /* type_string */ "-tees", /* type_option */ "Teeworlds", /* game_name */ 0, /* master */ 35515, /* default_port */ 0, /* port_offset */ 0, /* flags */ "gametype", /* game_rule */ "TEEWORLDS", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_tee_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_tee_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_tee_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_tee_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_teeserver_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_teeserver_packet, /* packet_func */ }, { /* TEAMSPEAK 3 PROTOCOL */ TS3_PROTOCOL_SERVER, /* id */ "TS3", /* type_prefix */ "ts3", /* type_string */ "-ts3", /* type_option */ "Teamspeak 3", /* game_name */ 0, /* master */ 0, /* default_port */ 0, /* port_offset */ TF_TCP_CONNECT | TF_QUERY_ARG_REQUIRED | TF_QUERY_ARG, /* flags */ "N/A", /* game_rule */ "TS3PROTOCOL", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_ts3_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_ts3_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_ts3_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_ts3_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_ts3_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_ts3_packet, /* packet_func */ }, { /* BATTLEFIELD BAD COMPANY 2 PROTOCOL */ BFBC2_PROTOCOL_SERVER, /* id */ "BFBC2", /* type_prefix */ "bfbc2", /* type_string */ "-bfbc2", /* type_option */ "Battlefield Bad Company 2", /* game_name */ 0, /* master */ BFBC2_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_TCP_CONNECT, /* flags */ "gametype", /* game_rule */ "BFBC2PROTOCOL", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_bfbc2_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_bfbc2_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_bfbc2_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_bfbc2_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_bfbc2_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_bfbc2_packet, /* packet_func */ }, { /* VENTRILO PROTOCOL */ VENTRILO_PROTOCOL_SERVER, /* id */ "VENTRILO", /* type_prefix */ "ventrilo", /* type_string */ "-vent", /* type_option */ "Ventrilo", /* game_name */ 0, /* master */ VENTRILO_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ 0, /* flags */ "gametype", /* game_rule */ "VENTRILO", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_ventrilo_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_ventrilo_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_ventrilo_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_ventrilo_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_ventrilo_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_ventrilo_packet, /* packet_func */ }, { /* Cube 2/Sauerbraten/Blood Frontier */ CUBE2_SERVER, /* id */ "CUBE2", /* type_prefix */ "cube2", /* type_string */ "-cubes", /* type_option */ "Sauerbraten", /* game_name */ 0, /* master */ CUBE2_DEFAULT_PORT, /* default_port */ 1, /* port_offset */ 0, /* flags */ "", /* game_rule */ "CUBE2", /* template_var */ cube2_serverstatus, /* status_packet */ sizeof(cube2_serverstatus), /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ NULL, /* display_player_func */ display_server_rules, /* display_rule_func */ NULL, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ NULL, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ NULL, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_cube2_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_cube2_packet, /* packet_func */ }, { /* MUMBLE PROTOCOL */ MUMBLE_PROTOCOL_SERVER, /* id */ "MUMBLE", /* type_prefix */ "mumble", /* type_string */ "-mumble", /* type_option */ "Mumble", /* game_name */ 0, /* master */ MUMBLE_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ 0, /* flags */ "gametype", /* game_rule */ "MUMBLEPROTOCOL", /* template_var */ mumble_serverstatus, /* status_packet */ 12, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ NULL, /* display_player_func */ display_server_rules, /* display_rule_func */ NULL, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_mumble_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_mumble_packet, /* packet_func */ }, { /* TERRARIA PROTOCOL */ TERRARIA_PROTOCOL_SERVER, /* id */ "TERRARIA", /* type_prefix */ "terraria", /* type_string */ "-terraria", /* type_option */ "Terraria", /* game_name */ 0, /* master */ TERRARIA_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ TF_TCP_CONNECT, /* flags */ "gametype", /* game_rule */ "TERRARIPROTOCOL", /* template_var */ terraria_serverstatus, /* status_packet */ sizeof(terraria_serverstatus), /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ NULL, /* display_player_func */ display_server_rules, /* display_rule_func */ NULL, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_mumble_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_terraria_packet, /* packet_func */ }, { /* CRYSIS PROTOCOL */ CRYSIS_PROTOCOL_SERVER, /* id */ "CRYSIS", /* type_prefix */ "crysis", /* type_string */ "-crysis", /* type_option */ "Crysis", /* game_name */ 0, /* master */ 0, /* default_port */ 0, /* port_offset */ TF_TCP_CONNECT | TF_QUERY_ARG_REQUIRED | TF_QUERY_ARG, /* flags */ "gamerules", /* game_rule */ "CRYSISPROTOCOL", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ NULL, /* display_player_func */ display_server_rules, /* display_rule_func */ NULL, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_crysis_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_crysis_packet, /* packet_func */ }, { /* DIRTYBOMB PROTOCOL */ DIRTYBOMB_PROTOCOL_SERVER, /* id */ "DIRTYBOMB", /* type_prefix */ "dirtybomb", /* type_string */ "-dirtybomb", /* type_option */ "DirtyBomb", /* game_name */ 0, /* master */ DIRTYBOMB_DEFAULT_PORT, /* default_port */ 0, /* port_offset */ 0, /* flags */ "gamerules", /* game_rule */ "DIRYTBOMBPROTOCOL", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ NULL, /* display_player_func */ display_server_rules, /* display_rule_func */ NULL, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_dirtybomb_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_dirtybomb_packet, /* packet_func */ }, { /* STARMADE PROTOCOL */ STARMADE_PROTOCOL_SERVER, /* id */ "STARMADE", /* type_prefix */ "starmade", /* type_string */ "-starmade", /* type_option */ "StarMade", /* game_name */ 0, /* master */ 0, /* default_port */ 0, /* port_offset */ TF_TCP_CONNECT, /* flags */ "N/A", /* game_rule */ "STARMADEPROTOCOL", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ display_starmade_player_info, /* display_player_func */ display_server_rules, /* display_rule_func */ raw_display_starmade_player_info, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_starmade_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_starmade_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_starmade_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_starmade_packet, /* packet_func */ }, { /* FARMSIM PROTOCOL */ FARMSIM_PROTOCOL_SERVER, /* id */ "FARMSIM", /* type_prefix */ "farmsim", /* type_string */ "-farmsim", /* type_option */ "FarmingSimulator", /* game_name */ 0, /* master */ 0, /* default_port */ 0, /* port_offset */ TF_TCP_CONNECT | TF_QUERY_ARG_REQUIRED | TF_QUERY_ARG, /* flags */ "gamerules", /* game_rule */ "FARMSIMPROTOCOL", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ NULL, /* display_player_func */ display_server_rules, /* display_rule_func */ NULL, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_farmsim_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_farmsim_packet, /* packet_func */ }, { /* KSP PROTOCOL */ KSP_PROTOCOL_SERVER, /* id */ "KSP", /* type_prefix */ "ksp", /* type_string */ "-ksp", /* type_option */ "Kerbal Space Program", /* game_name */ 0, /* master */ 0, /* default_port */ 0, /* port_offset */ TF_TCP_CONNECT, /* flags */ "gamerules", /* game_rule */ "KSPPROTOCOL", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ NULL, /* display_player_func */ display_server_rules, /* display_rule_func */ NULL, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_ksp_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_ksp_packet, /* packet_func */ }, { /* TF PROTOCOL */ TF_PROTOCOL_SERVER, /* id */ "TF", /* type_prefix */ "tf", /* type_string */ "-tf", /* type_option */ "Titanfall", /* game_name */ 0, /* master */ 0, /* default_port */ 0, /* port_offset */ TF_QUERY_ARG, /* flags */ "game_mode", /* game_rule */ "TFPROTOCOL", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ NULL, /* display_player_func */ display_server_rules, /* display_rule_func */ NULL, /* display_raw_player_func */ raw_display_server_rules, /* display_raw_rule_func */ xml_display_player_info, /* display_xml_player_func */ xml_display_server_rules, /* display_xml_rule_func */ json_display_player_info, /* display_json_player_func */ json_display_server_rules, /* display_json_rule_func */ send_tf_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_tf_packet, /* packet_func */ }, { /* Teeworlds master */ TEE_MASTER, /* id */ "TEEM", /* type_prefix */ "teem", /* type_string */ "-teem", /* type_option */ "Teeworlds Master", /* game_name */ TEE_SERVER, /* master */ 8300, /* default_port */ 0, /* port_offset */ 0, /* flags */ "gametype", /* game_rule */ "TEEWORLDSMASTER", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ NULL, /* display_player_func */ NULL, /* display_rule_func */ NULL, /* display_raw_player_func */ NULL, /* display_raw_rule_func */ NULL, /* display_xml_player_func */ NULL, /* display_xml_rule_func */ NULL, /* display_json_player_func */ NULL, /* display_json_rule_func */ send_teemaster_request_packet, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ deal_with_teemaster_packet, /* packet_func */ }, { Q_UNKNOWN_TYPE, /* id */ "", /* type_prefix */ "", /* type_string */ "", /* type_option */ "", /* game_name */ 0, /* master */ 0, /* default_port */ 0, /* port_offset */ 0, /* flags */ "", /* game_rule */ "", /* template_var */ NULL, /* status_packet */ 0, /* status_len */ NULL, /* player_packet */ 0, /* player_len */ NULL, /* rule_packet */ 0, /* rule_len */ (char *)NULL, /* master_packet */ 0, /* master_len */ NULL, /* master_protocol */ NULL, /* master_query */ NULL, /* display_player_func */ NULL, /* display_rule_func */ NULL, /* display_raw_player_func */ NULL, /* display_raw_rule_func */ NULL, /* display_xml_player_func */ NULL, /* display_xml_rule_func */ NULL, /* display_json_player_func */ NULL, /* display_json_rule_func */ NULL, /* status_query_func */ NULL, /* rule_query_func */ NULL, /* player_query_func */ NULL, /* packet_func */ } }; #endif /* QUERY_PACKETS */ /* Structures for keeping information about Quake servers, server * rules, and players. */ struct player; #define FLAG_BROADCAST (1 << 1) #define FLAG_PLAYER_TEAMS (1 << 2) #define FLAG_DO_NOT_FREE_GAME (1 << 3) #define PLAYER_TYPE_NORMAL 1 #define PLAYER_TYPE_BOT 2 #define PLAYER_TYPE_ALIAS 4 #define PLAYER_FLAG_DO_NOT_FREE_TEAM 1 struct player { int number; char *name; int frags; int team; /* Unreal and Tribes only */ char *team_name; /* Tribes, BFRIS only, do not free() */ int connect_time; int shirt_color; int pants_color; char *address; int ping; short flags; short type_flag; /* Tribes 2 only */ int packet_loss; /* Tribes only */ char *tribe_tag; /* Tribes 2 / Quake 4 clan name */ char *skin; char *mesh; /* Unreal only */ char *face; /* Unreal only */ int score; /* BFRIS only */ int ship; /* BFRIS only */ int room; /* BFRIS only */ int deaths; /* Descent3 only */ char *next_info; int n_info; struct info *info; struct info **last_info; int missing_rules; struct player *next; }; struct rule { char *name; char *value; struct rule *next; }; struct info { char *name; char *value; struct info *next; }; extern char *qstat_version; extern char *DOWN; extern char *SYSERROR; extern char *TIMEOUT; extern char *MASTER; extern char *SERVERERROR; extern char *HOSTNOTFOUND; extern int n_retries; extern struct timeval packet_recv_time; #define DEFAULT_RETRIES 3 #define DEFAULT_RETRY_INTERVAL 500 /* milli-seconds */ #define MAXFD_DEFAULT 20 #define SORT_GAME 1 #define SORT_PING 2 extern int first_sort_key; extern int second_sort_key; #define SECONDS 0 #define CLOCK_TIME 1 #define STOPWATCH_TIME 2 #define DEFAULT_TIME_FMT_RAW SECONDS #define DEFAULT_TIME_FMT_DISPLAY CLOCK_TIME extern int time_format; extern int color_names; extern int show_errors; extern int get_player_info; extern int get_server_rules; extern int no_port_offset; /* Definitions for the original Quake network protocol. */ #define PACKET_LEN 0xffff /* Quake packet formats and magic numbers */ struct qheader { unsigned char flag1; unsigned char flag2; unsigned short length; unsigned char op_code; }; #define Q_NET_PROTOCOL_VERSION 3 #define HEXEN2_NET_PROTOCOL_VERSION 4 #define Q_CCREQ_CONNECT 0x01 #define Q_CCREP_ACCEPT 0x81 #define Q_CCREP_REJECT 0x82 #define Q_CCREP_SERVER_INFO 0x83 #define Q_CCREP_PLAYER_INFO 0x84 #define Q_CCREP_RULE_INFO 0x85 #define Q_DEFAULT_SV_MAXSPEED "320" #define Q_DEFAULT_SV_FRICTION "4" #define Q_DEFAULT_SV_GRAVITY "800" #define Q_DEFAULT_NOEXIT "0" #define Q_DEFAULT_TEAMPLAY "0" #define Q_DEFAULT_TIMELIMIT "0" #define Q_DEFAULT_FRAGLIMIT "0" /* Definitions for the QuakeWorld network protocol */ /* #define QW_GET_SERVERS 'c' */ #define QW_SERVERS 'd' #define HL_SERVERS 'f' /* * HL master: send 'a', master responds with a small 'l' packet containing * the text "Outdated protocol" * HL master: send 'e', master responds with a small 'f' packet * HL master: send 'g', master responds with a small 'h' packet containing * name of master server * HL master: send 'i', master responds with a small 'j' packet */ #define QW_GET_USERINFO 'o' #define QW_USERINFO 'p' #define QW_GET_SEENINFO 'u' #define QW_SEENINFO 'v' #define QW_NACK 'm' #define QW_NEWLINE '\n' #define QW_RULE_SEPARATOR '\\' #define QW_REQUEST_LENGTH 20 int is_default_rule(struct rule *rule); char *xform_name(char *, struct qserver *server); char *quake_color(int color); char *play_time(int seconds, int show_seconds); char *ping_time(int ms); char *get_qw_game(struct qserver *server); /* * Query status and packet handling functions */ int cleanup_qserver(struct qserver *server, int force); void change_server_port(struct qserver *server, unsigned short port, int force); int server_info_packet(struct qserver *server, struct q_packet *pkt, int datalen); int player_info_packet(struct qserver *server, struct q_packet *pkt, int datalen); int rule_info_packet(struct qserver *server, struct q_packet *pkt, int datalen); int time_delta(struct timeval *later, struct timeval *past); char *strherror(int h_err); int connection_refused(); int connection_would_block(); int connection_reset(); void add_file(char *filename); int add_qserver(char *arg, server_type *type, char *outfilename, char *query_arg); struct qserver *add_qserver_byaddr(unsigned int ipaddr, unsigned short port, server_type *type, int *new_server); void init_qserver(struct qserver *server, server_type *type); int bind_qserver(struct qserver *server); int bind_sockets(); void send_packets(); struct qserver *find_server_by_address(unsigned int ipaddr, unsigned short port); void add_server_to_hash(struct qserver *server); void quicksort(void **array, int i, int j, int (*compare)(void *, void *)); int type_option_compare(server_type *one, server_type *two); int type_string_compare(server_type *one, server_type *two); #define NO_FLAGS 0 #define NO_VALUE_COPY 1 #define CHECK_DUPLICATE_RULES 2 #define NO_KEY_COPY 4 #define COMBINE_VALUES 8 #define OVERWITE_DUPLICATES 16 struct player *get_player_by_number(struct qserver *server, int player_number); struct rule *add_rule(struct qserver *server, char *key, char *value, int flags); struct player *add_player(struct qserver *server, int player_number); struct info *player_add_info(struct player *player, char *key, char *value, int flags); void players_set_teamname(struct qserver *server, int teamid, char *teamname); /* * Output template stuff */ int read_qserver_template(char *filename); int read_rule_template(char *filename); int read_header_template(char *filename); int read_trailer_template(char *filename); int read_player_template(char *filename); int have_server_template(); int have_header_template(); int have_trailer_template(); void template_display_server(struct qserver *server); void template_display_header(); void template_display_trailer(); void template_display_players(struct qserver *server); void template_display_player(struct qserver *server, struct player *player); void template_display_rules(struct qserver *server); void template_display_rule(struct qserver *server, struct rule *rule); /* * Host cache stuff */ int hcache_open(char *filename, int update); void hcache_write(char *filename); void hcache_invalidate(); void hcache_validate(); unsigned long hcache_lookup_hostname(char *hostname); char *hcache_lookup_ipaddr(unsigned long ipaddr); void hcache_write_file(char *filename); void hcache_update_file(); unsigned int swap_long_from_little(void *l); unsigned short swap_short_from_little(void *l); float swap_float_from_little(void *f); /** \brief write four bytes in little endian order */ void put_long_little(unsigned val, char *buf); /* * Exported Globals */ extern int show_game_port; extern int up_servers_only; extern int hostname_lookup; #define NA_INT -32767 #define NO_PLAYER_INFO 0xffff #define NO_SERVER_RULES NULL #define FORCE 1 #define NO_FORCE 0 #endif qstat-2.17/qstatdoc.html000066400000000000000000002217551412457473700153410ustar00rootroot00000000000000 QStat 2.10 documentation

NAME

qstat - Get statistics from on-line game servers

SYNOPSIS

qstat [options ...] [-f file] [-of|-af output-file] [-server-option host[:port]]
[-raw delimiter] [-default server-type] host[:port[-port_max]] ...

Version 2.10

DESCRIPTION

QStat is a command-line program that displays information about Internet game servers. The servers are either down, non-responsive, or running a game. For servers running a game, the server name, map name, current number of players, and response time are displayed. Server rules and player information may also be displayed.

Games supported include Quake, QuakeWorld, Hexen II, Quake II, HexenWorld, Unreal, Half-Life, Sin, Shogo, Tribes, Tribes 2, Quake III: Arena, BFRIS, Kingpin, and Heretic II, Unreal Tournament, Soldier of Fortune, Rogue Spear, Redline, Turok II, Blood 2, Descent 3, Drakan, KISS, Nerf Arena Blast, Rally Master, Terminous, Wheel of Time, and Daikatana and many more. Note for Tribes 2: QStat only supports Tribes 2 builds numbered 22075 or higher. Note for Ghost Recon QStat only supports GhostRecon patch 1.2, 1.3, 1.4, Desert Siege, and Island Thunder.

Some games use query protocols compatible with an existing game. These servers can be queried using the flags for the compatible game. For instance, Turok2 should work using the -uns flag. Unreal Tournament is also supported by the -uns but is not really a different game. You can distinguish Unreal Tournament games with the "minnetver" server rule (standard Unreal servers have a "mingamever" server rule).

The Quake servers can be divided into two categories: POQS (Plain Old Quake Server) and QuakeWorld. Quake shareware, Quake commercial (from CD), winquake, winded, unixded, and Hexen II are all POQS. The various versions of QuakeWorld and Quake II use a QuakeWorld type server. The distinction is based on network protocol used to query the servers, and affects the kind of information available for display.

The different server types can be queried simultaneously. If QStat detects that this is being done, the output is keyed by the type of server being displayed. See DISPLAY OPTIONS.

The game server may be specified as an IP address or a hostname. Servers can be listed on the command-line or, with the use of the -f option, a text file.

DISPLAY MODES

One line will be displayed for each server queried. The first component of the line will be the server's address as given on the command-line or the file. This can be used as a key to match input addresses to server status. Server rules and player information are displayed under the server info, indented by one tab stop.

QStat supports three additional display modes: raw, templates, and XML. In raw mode, the server information is displayed using simple delimiters and no formatting. This mode is good for programs that parse and reformat QStat's output. The template mode uses text files to layout the server information within existing text. This is ideal for generating web pages. The XML mode outputs server information wrapped in simple XML tags. The raw mode is enabled using the -raw option, template output is enabled using -Ts, and XML output is enabled with -xml.

GAME OPTIONS

These options select which servers to query and what game type they are running. Servers are specified by IP address (for example: 199.2.18.4) or hostname. Servers can be listed on the command-line or in a file (see option -f.) The game type of a server can be specified with its address, or a default game type can be set for all addresses that don't have a game type.

The following table shows the command-line option and type strings for the supported game types. The type string is used with the -default option and in files with the -f option.

OptionType StringDefault PortGame Server
-qsqs26000Quake
-h2sh2s26900Hexen II
-qwsqws27500QuakeWorld
-hwshws26950HexenWorld
-q2sq2s27910Quake II
-unsuns7777Unreal
-ut2sut2s7777Unreal Tournament 2003
-ut2004mut2004m28902Unreal Tournament 2004 Master requires CD Key
-hlshls27015Half-Life
-snssns22450Sin
-sgssgs27888Shogo: Mobile Armor Division
-tbstbs28001Starsiege: Tribes
-t2st2s28000Tribes 2
-qwmqwm27000QuakeWorld master
-hwmhwm26900HexenWorld master
-q2mq2m27900Quake II master
-hlmhlm27010Half-Life master
-stmstm27010Half-Life master (Steam)
-tbmtbm28000Tribes master
-t2mt2m28002Tribes 2 master
-q3sq3s27960Quake III
-q3mq3m27950Quake III master
-dm3sdm3s27666Doom 3
-dm3mdm3m27650Doom 3 master
-bfsbfs44001BFRIS
-kpskps31510Kingpin
-hrshrs28910Heretic II
-sfssfs28910Soldier of Fortune
-gsmgsm28900Gamespy master
-gpsgps-Game using "Gamespy style" protocol
-gpsgs2-Game using "Gamespy2 style" protocol
-d3md3m3445Descent 3 PXO master
-d3pd3p2092Descent 3, PXO server
-d3sd3s2092Descent 3, LAN server
-d3gd3g20142Descent 3, Gamespy protocol
-rwsrws27960Return to Castle Wolfestein
-rwmrwm27950Return to Castle Wolfestein master
-efsefs27960Star Trek: Elite Force
-efmefm27953Star Trek: Elite Force master
-efsefs29070Jedi Knight: Jedi Academy
-efmefm29060Jedi Knight: Jedi Academy master
-grsgrs2346Ghost Recon
-etqwsetqws27733QuakeWars server

The command-line options can be specified multiple times, one for each server to be queried.

Configuration Files

The games supported by QStat can be customized with configuration files. The query parameters of built-in game types can be modified and new games can be defined.

For built-in game types, certain parameters can be modified. The parameters are limited to the master server protocol and master server query string.

New game types can be defined as a variation on an existing game type. Most new games use a Quake 3 or Gamespy/Unreal based network engine. These games can already be queried using -q3s or -gps, but they don't have game specific details such as the correct default port, the game name, and the correct "game" or "mod" server rule. And, mostly importantly, they don't get their own game type string (e.g. q3s, rws, t2s). All of these details can be specified in the QStat config file.

QStat comes with a default configuration file called 'qstat.cfg'. If this file is found in the directory where qstat is run, the file will be loaded. Configuration files can also be specified with the QSTAT_CONFIG environment variable and the -cfg command-line option. See Appendix B for a description of the configuration file format.

Descent 3

Support for Descent 3 is a bit fragmented. There are three different protocols for getting status information from a Descent 3 server: PXO, LAN, and Gamespy. If the server was acquired from the PXO master server, then the PXO protocol is used. If the server is running on the local LAN (not reporting to a master server), then the LAN protocol should be used. Finally, if the server's Gamespy query port is known (default is 20142) then the Gamespy protcol can be used. The gamespy protocol can be used on servers listed in the PXO master.

Each protocol reports different information. The Gamespy protocol provides player names, frags, deaths, and ping. The PXO and LAN protocols only provide player names.

The ideal solution would be a PXO server list paired with each server's gamespy query port. Most servers will use the default gamespy query port, unless there are multiple servers on the same machine. A possible approach is to get the server list from the PXO master like this:

qstat -d3m,outfile gt.pxo.net,d3pxo.txt
Then convert the file from "d3p" to "d3g" and remove the port numbers:
sed -e 's/d3p/d3g/' -e 's/:.*$//' d3pxo.txt > d3gs.txt
Then run the servers in d3gs.txt with -f:
qstat -f d3gs.txt
This technique will retrieve the full player info for servers using the default gamespy query port.

Broadcast Queries

QStat has limited support for broadcast queries. Broadcast queries use one network packet to find all the game servers on a local network. A broadcast returns servers of one type on one port. You may only broadcast to networks to which you computer is directly attached (ie. local networks).

A broadcast query is specified by prefixing an address with a '+' (plus sign). The address should be 255.255.255.255 or a valid broadcast address for your local network. On Unixes, 'ifconfig -a' will display the broadcast address for all attached networks.

Port Ranges

Broadcast Queries can scan a range of ports to find game servers that don't run on the default port. Specify the minimum and maximum port of a range separated by a dash.

Query Arguments

Some game types support customized server queries. For example, many master servers can return a select list of servers based on the mod, number of players, or region. Each query can be customized differently.

Server queries are customized by attaching query arguments to the server type option. Each argument is separated by a comma (','). The argument has a name followed by an optional value separated with an equal-sign ('='). The general format looks like this:

-server-option,query-arg[=arg-value][, ...]
See the Master Server sections below for more examples.
General Query Arguments
The follow query arguments can be applied to any server type.
Query ArgumentDescription
showgameportAlways display the game port in QStat output. If the query port was different from the game port, then the query port will be saved in the "_queryport" server rule. This is the same as the -showgameport command-line option, but only applies to one server query.
gpShort-hand for showgameport.
noportoffsetDo not apply the "status port offset" when sending the server query. Some games use different ports for game play and status queries (for example, Unreal, Medal of Honor, etc). QStat will normally add an offset to the game port to find the query port. But not all servers use the standard offset. This option allows QStat to query servers where the query port is known. The server address should be an IP address and a query port (not the game port). This is the same as the -noportoffset command-line option, but only applies to one server query.
qpShort-hand for noportoffset.

Some examples of the general query arguments.

qstat -uns,noportoffset 1.2.3.4:7787
ADDRESS           PLAYERS      MAP   RESPONSE TIME    NAME
1.2.3.4:7787        0/16      Kansas    119 / 0       Example server
The default Unreal game port is 7777 and the query port is usually at offset 1 (port 7778). But this server has a different query port. Note that qstat displays the query port. To display the game port instead:
qstat -uns,noportoffset,showgameport 1.2.3.4:7787
ADDRESS           PLAYERS      MAP   RESPONSE TIME    NAME
1.2.3.4:7777        0/16      Kansas    119 / 0       Example server
Another common usage for "showgameport" is with broadcast queries:
qstat -uns,showgameport +255.255.255.255

Master Servers

Master server addresses don't change very often, but some times they go off-line. The following is a table of some of the master servers I know about.

GameMaster Servers
QuakeWorldsatan.idsoftware.com (ports 27000, 27002, 27003, 27004, 27006), 204.182.161.2, 194.217.251.40, 203.34.140.1, 200.245.221.200, 194.87.251.3
Quake IIsatan.idsoftware.com, q2master.planetquake.com
Half-Life (hlm)half-life.west.won.net, half-life.east.won.net
Half-Life (stm)steam1.steampowered.com:27010, steam2.steampowered.com:27010
Tribestribes.dynamix.com
Quake IIImaster3.idsoftware.com
Doom 3idnet.ua-corp.com
Gamespymaster0.gamespy.com
Tribes 2211.233.32.77:28002, 217.6.160.205:28002
Descent 3gt.pxo.net
Return to Castle Wolfensteinwolfmaster.idsoftware.com
Star Trek: Elite Forcemaster.stef1.ravensoft.com
UT2004ut2004master1.epicgames.com, ut2004master2.epicgames.com, ut2004master3.epicgames.com

Gamespy Master

Access to the gamespy masters has been disabled by Gamespy Inc.

Server lists can be fetched from Gamespy masters by using the gsm game type. A query argument is required to use the Gamespy master. This extra argument indicates which server list to get from the master. The query argument can be one of the QStat supported game types or any other string that will fetch a server list. The following game types can be used as query arguments: qws, q2s, q3s, tbs, uns, sgs, hls, kps, hrs, sfs. For each of the game types, QStat will fetch the appropriate server list and get status from each server.

The query argument can also be any string that the Gamespy master responds to. Most of these games support a "standard" server status protocol that I'll call the "Gamespy status protocol". Not surprisingly, it is almost identical to the Unreal server status protocol. This means that QStat can support any game that supports this protocol. In QStat these games are queried using the gps game type. Through experimentation I've found the following query arguments.

Query ArgumentGame
roguespearRainbow Six: Rogue Spear
redlineRedline Racer
turok2Turok 2: Seeds of Evil
blood2Blood 2: The Chosen
drakanDrakan: Order of the Flame
kissKISS Psycho Circus: The Nightmare Child
nerfarenaNerf Arena Blast
rallyRally Masters: Michelin Race Of Champions
terminousTerminous (?)
wotThe Wheel of Time
daikatanaDaikatana

Tribes 2 Master

The Tribes 2 master server supports a number of filtering options. You can set these filters with QStat by appending query arguments to the server type. The general syntax is:
t2m,query-arg=value, ...
Query ArgumentValuesDescription
gamemod pathMod path the server is using. The mod path of unaltered servers is "base". Use query=types to get the list of known game types.
missionBounty, Capture the Flag, CnH, Deathmatch, Hunters, Rabbit, Siege, TeamHuntersMission type the server is currently running. Use query=types to get the list of known mission types.
minplayers0 - 255Servers with fewer players than this will not be returned.
maxplayers1 - 255Servers with more players than this will not be returned.
regionslist of regions or 0xhex-value Limit servers to those in the given geographical regions. See Region List table. The regionlist is sent as a bit mask to the Tribes 2 master. If you know the bit mask for a region QStat doesn't support, you can specify the bitmask directly by supplying a hex value: regions=0x11.
buildbuild version # Only return servers matching this build version number. [4/20/2001] This filter only seems to work if the build version # is 22337. If the filter is 22228, then the master returns 0 servers. This appears to be a bug in the T2 master.
statuslist of dedicated, linux, nopassword
or 0xhex-value
Limit servers to those with these status flags set. The list is one or more status flags separated by colons (':'). To filter on dedicated Linux servers, specify status=dedicated:linux
If you know a status flag that QStat doesn't support, you can specify the status flags directly by supplying a hex value: status=0x3
maxbots0 - 255Servers with more bots than this will not be returned.
mincpu0 - 65535Servers with lower CPU speed than this will not be returned.
querytypesGet the list of game and mission types. This is not a filter but a different master request. Using this query argument overrides any other query arguments. The list of game and mission types known to the master server will be displayed. In raw mode, the first line is the list of game types and the second line is the list of mission types. There is no output template support for game and mission lists.
Region List
The region list is one or more region arguments separated by colons (':'). For example, to filter on North American servers specify regions=naeast:nawest

Region ArgumentGeography
naeastNorth America East
nawestNorth America West
saSouth America
ausAustralia
asiaAsia
eurEurope

The Tribes 2 master query arguments can be used on the command-line or in a server list file (via the -f option). If the values contain spaces, be sure to quote the arguments for your shell. The second example below demonstrates this usage for most common shells. To query for Capture the Flag servers that have more than 6 players:
qstat -t2m,mission=Capture the Flag,minplayers=6 master-server-ip
qstat -t2m,mission="Capture the Flag",minplayers=6 master-server-ip
If you want to do this in a server list file, that would look like this:
t2m,mission=Capture the Flag,minplayers=6 master-server-ip
Master server filters can be combined with the "outfile" option. Just put outfile some where in the query argument list and put the name of the output file after the master IP address:
t2m,outfile,mission=Siege,minplayers=4 master-server-ip,siegeservers.txt
Warning: There is a bug in the 22075 build of Tribes 2 that doesn't return the game name. For those builds, QStat will use the game info in place of the game name. The bug is fixed in the 22228 build. In fixed servers, the game info can be found in the "info" server rule.

Half-Life Master

The Half-Like master server supports a number of filtering options. You can set these filters with QStat by appending query arguments to the server type. The general syntax is:
hlm,query-arg=value, ...
Query ArgumentValuesDescription
gamemod pathServers running this "mod".
mapmap nameServers running this map.
statuslist of dedicated, linux, notempty, notfull Limit servers to those matching this status. The list is one or more status flags separated by colons (':'). To filter on dedicated servers that are not empty, specify status=dedicated:notempty
See the Tribes 2 master server above for example usage.

Half-Life Master (Steam)

The Half-Life steam master server supports a number of filtering options different from the old WON master.
stm,query-arg=value, ...
Query ArgumentValuesDescription
gamemod pathServers running this "mod".
mapmap nameServers running this map.
regionname or number of regionGeographical Area of the server. You can specify the name or number of the region:
  • 0: US East coast
  • 1: US West coast
  • 2: South America
  • 3: Europe
  • 4: Asia
  • 5: Australia
  • 6: Middle East
  • 7: Africa
statuslist of dedicated, linux, notempty, notfull, secure, proxy Limit servers to those matching this status. The list is one or more status flags separated by colons (':'). To filter on dedicated servers that are not empty, specify status=dedicated:notempty
See the Tribes 2 master server above for example usage.

Doom 3 Master

The Doom 3 master server supports a number of filtering options. You can set these filters with QStat by appending query arguments to the server type. The general syntax is:
dm3m,query-arg=value, ...
Query ArgumentValuesDescription
statuscolon separated list of password, nopassword, notfull, notfullnotempty Limit servers to those matching this status. The list is one or more status flags separated by colons (':'). To filter on servers without password that are not full, specify status=nopassword:notfull
gametypeone of dm, tdm or tourney limit servers to those mathing the specified gametype
See the Tribes 2 master server above for example usage.
A special option is a number major.minor which specifies the protocol version.

UT2004 Master

The UT2004 master server supports a number of filtering options. You can set these filters with QStat by appending query arguments to the server type. The general syntax is:
ut2004m,query-arg=value, ...
Query Argument Values Description
cdkey path to the UT2004 cdkey file You MUST specify a valid CD key to be able to query the master server
status colon separated list of password, nopassword, notfull, notempty, standard, nostandard, nobots, stats, nostats, weaponstay, noweaponstay, transloc, notransloc Limit servers to those matching this status. The list is one or more status flags separated by colons (':'). To filter on standard servers without password that are not full, specify status=standard:nopassword:notfull
gametype any UT2004 gametype, e.g. xMutantGame limit servers to those mathing the specified gametype
mutator colon separated list of Mutators. limit servers to those running the specified mutators. Prepend a dash to include servers that do NOT run the specified mutator
You may need to specify at least one filter option like status or gametype for the master to actually return any server.
Example:
qstat -ut2004m,cdkey=/usr/local/games/ut2004/System/cdkey,status=nostandard ut2004master1.epicgames.com:28902

Option Usage

-cfg configuration-file
Load the QStat configuration file. New game types defined in the config file can be used in subsequent command-line options.
-server-option host[:port]
Query game server host for status. The GAME OPTIONS table lists the available server-options and their default port.
-nocfg Ignore qstat configuration loaded from any default location (see Appendix B for a list of default locations). Must be the first option on the command-line. Use this option to have complete control over the configured game types.
-master-server-option host[:port]
Query a game master for its server list and then query all the servers. The GAME OPTIONS table lists the available master-server-options and their default port.
-master-server-option,outfile host[:port],file
Query a game master for its server list and store it in file. If the master cannot be contacted, then file is not changed. If file is - (a single dash), then stdout is used. The GAME OPTIONS table lists the available master-server-options and their default port.
-gsm,query-argument host[:port]
Query a Gamespy master for a server list and then query all the servers. The Gamespy Master section details the supported values for query-argument.
-gsm,query-argument,outfile host[:port],file
Query a Gamespy master for a server list and store it in file. If the master cannot be contacted, then file is not changed. If file is - (a single dash), then stdout is used. The Gamespy Master section details the supported values for query-argument.
-q3m,query-argument host[:port]
Query a Quake 3 Arena master for a protocol-specific server list and then query all the servers. The query-argument should be a Quake 3 protocol version. Protocol version 48 is Quake 3 version 1.27, protocol 46 is Quake 3 version 1.25, protocol 45 is Quake 3 1.17, protocol 43 is Quake 3 version 1.11. The default is protocol version 48.
-q3m,query-argument,outfile host[:port],file
Query a Quake 3 Arena master for a protocol-specific server list and store it in file. The query-argument should be a Quake 3 protocol version. Protocol version 46 is Quake 3 version 1.25, protocol 45 is Quake 3 1.17, protocol 43 is Quake 3 version 1.11. The default is protocol version 45.
-f file
Read host addresses from the given file. If file is -, then read from stdin. Multiple -f options may be used. The file should contain host names or IP addresses separated by white-space (tabs, new-lines, spaces, etc). If an address is preceded by a server type string, then QStat queries the address according to the server type. Otherwise QS is assumed, unless -default is used. The GAME OPTIONS table lists the available server type strings and their default port.
-default type-string
Set the default server type for addresses where the type is not obvious. This affects the addresses at the end of the qstat command-line and those in a file not prefixed by a server type (see -f). The GAME OPTIONS table lists the available server type strings and their default port.
-noportoffset
Do not apply the "status port offset" when sending server queries. Some games use different ports for game play and status queries (for example, Unreal, Medal of Honor, etc). QStat will normally add an offset to the game port to find the query port. But not all servers use the standard offset. This option allows QStat to query servers where the query port is known. The server addresses should be an IP address and a query port (not the game port). This is the same as the noportoffset server query argument, but applies to all servers being queried.

INFO OPTIONS

-R
Fetch and display server rules.
-P
Fetch and display player information.

DISPLAY OPTIONS

The QStat output should be self explanatory. However, the type of information returned is different between game types. If QStat queries multiple server types, then each server status line is prefixed with its type string. The GAME OPTIONS table lists the available type strings.

-of file
Write output to file instead of stdout or the console. file is over written if it already exists.
-af file
Like -of, but append to the file. If file does not exist, it is created.
-u
Only display hosts that are up and running a game server. Does not affect template output.
-nf
Do not display full servers. Does not affect template output.
-ne
Do not display empty servers. Does not affect template output.
-nh
Do not display header line (does not apply to raw or template output.)
-cn
Display color names instead of numbers. This is the default. Only applies to Quake, QuakeWorld, Hexen II, and HexenWorld.
-ncn
Display color numbers instead of color names. This is the default for -raw mode. Only applies to Quake, QuakeWorld, Hexen II, and HexenWorld.
-hc
Display colors in #rrggbb format. This is nice for HTML output. Only applies to Quake, QuakeWorld, Hexen II, and HexenWorld.
-nx
Perform name transforms. Transform game specific player and server name escape sequences into more readable text. This setting is ON by default.
-nnx
No name transforms. Do not transform player and server names. Option -utf8 implies -nnx.
-tc
Display time in clock format (DhDDmDDs). This is the default.
-tsw
Display time in stop-watch format (DD:DD:DD).
-ts
Display time in seconds. This is the default for -raw mode.
-pa
Display player addresses. This is the default for -raw mode. Only available for Quake and Hexen II.
-sort sort-keys
Sort servers and/or players. Servers and players are sorted according to sort-keys. Lower case sort keys are for servers and upper case keys are for players. The following sort keys are supported:
  • p - Sort by ping
  • g - Sort by game (mod)
  • i - Sort by IP address
  • h - Sort by hostname
  • n - Sort by number of players
  • l - Sort by list order
  • P - Sort by player ping
  • F - Sort by frags
  • T - Sort by team

The 'l' (ell) sort key displays servers in the order they were provided to qstat. For example, the order in which they are listed on the command-line or in a file. The 'l' sort key cannot be combined with other server sort keys, but it can be be combined with player sort keys. If the 'l' sort key is used with other sort keys, then the 'l' sort key is ignored.

-hpn
Display player names in hex.
-old
Use pre-qstat 1.5 display style.
-raw delimiter
Display data in "raw" mode. The argument to -raw is used to separate columns of information. All information returned by the game server is displayed.
POQS output -- General server information is displayed in this order: command-line arg (IP address or host name), server name, server address (as returned by Quake server), protocol version, map name, maximum players, current players, average response time, number of retries. Server rules are displayed on one line as rule-name=value. If significant packet loss occurs, rules may be missing. Missing rules are indicated by a "?" as the last rule. Player information is displayed one per line: player number, player name, player address, frags, connect time, shirt color, pants color. A blank line separates each set of server information.
QuakeWorld and HexenWorld server output -- General server information is displayed in this order: command-line arg (IP address or host name), server name, map name, maximum players, current players, average response time, number of retries, game (mod). Server rules are displayed on one line as rule-name=value. Player information is displayed one per line: player number, player name, frags, connect time, shirt color, pants color, ping time (milliseconds), skin name. A blank line separates each set of server information.
All master server output -- Master server information is displayed in this order: command-line arg (IP address or host name), number of servers. No other information is displayed about master servers.
Quake II, Quake III, Half-Life, Sin, BFRIS, Kingpin, Heretic II, Unreal, Tribes 2, and Shogo server output -- General server information and server rules are the same as a QuakeWorld server. The player information varies for each game:
  • Quake II/III, Sin, Kingpin, Heretic II, Shogo: player name, frags, ping time
  • Half-Life: player name, frags, connect time
  • Tribes: player name, frags, ping time, team number, packet loss
  • Tribes 2: player name, frags, team number, team name, player type, tribe tag
  • Unreal: player name, frags, ping time, team number, skin, mesh, face
  • BFRIS: player number, ship, team name, ping time, score, frags, player name
  • Descent 3: player name, frags, deaths, ping time, team
Ping time is in milli-seconds. Connect time is in seconds. A blank line separates each set of server information.
Ghost Recon server output -- General server information and server rules are the detailed in the GhostRecon.txt file. Servers queried using the "Gamespy style" protocol use the same raw output format as Unreal servers.

-raw,game delimiter
Same as -raw but adds the game or mod name as the last item of server info.
-raw-arg
When used with -raw, always display the server address as it appeared in a file or on the command-line. Note that when -H is used with -raw, the first field of the raw output could be a hostname if the server IP address was resolved. This can make matching up input servers addresses with raw output lines fairly difficult. When -raw-arg is also used, an additional field, the unresolved server address, is added at the beginning of all raw output lines.
-progress,count
Print a progress meter. Displays total servers processed, including timeouts and down servers. The meter is just a line of text that writes over itself with <cr>. Handy for interactive use when you are redirecting output to a file (the meter is printed on stderr).
By default, the progress meter is updated for every server. The updates can be limited by appending ,count to the option where count is a number. The meter will be updated every count servers. For example, -progress,10 will update every ten servers.
-Tserver file
-Tplayer file
-Trule file
-Theader file
-Ttrailer file
Output templates. Each template should be a text file containing QStat variables that are substituted for results from the server query. The -Tserver flag must present to enable template output. The other -T flags are optional. The server template is output once for each server queried. The player template, if present, is output once for each player (if -P is also used). The rule template is output once for each server rule (the -R option may be required for some game types). The header template is output once before any servers are output. The trailer template is output once after all servers are output. See Appendix A for the output template formatting and variables.
NOTE: All of of the -T flags may be abbreviated with two characters: -Ts, -Tp, -Tr, -Th, and -Tt.
-htmlnames
Colorize Quake 3 and Tribes 2 player names using html font tags. Enabled by default if $HTML is used in an output template.
-nohtmlnames
Do not colorize Quake 3 and Tribes 2 player names even if $HTML is used in an output template. The $HTMLPLAYERNAME variable will always colorize player names.
-htmlmode
Convert <, >, and & to the equivalent HTML entities. This is the same as $HTML in an output template, but works for raw display mode. Using -htmlmode with -xml will result in double-escaping.
-carets
Display carets in Quake 3 player names. Carets are used for colorized player names and are remove by default. This option has no effect if -htmlnames is enabled.
-xml
Output server information wrapped in XML tags.
-utf8
Use the UTF-8 character encoding for XML output.
-showgameport
Always display the game port in QStat output. If the query port was different from the game port, then the query port will be saved in the "_queryport" server rule. This is the same as the showgameport server query argument, but applies to all server queries.
-errors
Display errors.
-d
Enable debug options. By default, enables printing of all received packets to stderr.

SEARCH OPTIONS

-H
Resolve IP addresses to host names. Use with caution as many game servers do not have registered host names. QStat may take up to a minute to timeout on each unregistered IP address. The duration of the timeout is controlled by your operating system. Names are resolved before attempting to query any servers.
-Hcache cache-file
Cache host name and IP address resolutions in cache-file. If the file does not exist, it is created. If -Hcache is used without -H, then the cache is only used for host to IP address resolution. WARNING A host cache file should not be shared by QStat programs running at the same time. If you run several QStats at the same time, each should have its own cache file.
-interval seconds
Interval in seconds between server retries. Specify as a floating point number. Default interval is 0.5 seconds. This option does not apply to master servers (see -mi.)
-mi seconds
Interval in seconds between master server retries. Specify as a floating point number. Default interval is 2 seconds.
-retry number
Number of retries. QStat will send this many packets to a host before considering it non-responsive. Default is 3 retries.
-maxsimultaneous number
Number of simultaneous servers to query. Unix systems have an operating system imposed limit on the number of open sockets per process. This limit varies between 32 and 100 depending on the platform. On Windows 95 and Windows NT, the "select" winsock function limits the number of simultaneous queries to 64. These limits can be increased by minor changes to the code, but the change is different for each platform. Default is 20 simultaneous queries. This option may be abbreviated -maxsim.
-timeout seconds
Total run time in seconds before giving up. Default is no timeout.

NETWORK OPTIONS

-srcport port-number | port-range
Specify the source ports for sending packets. The ports can be a single number or a range. A range is two numbers separated by a dash ('-'). The numbers should be positive and less than 65535. Use this option to get through a firewall that has source port restrictions. Set -srcport to the range of ports allowed by the firewall.

Example: If your firewall will allow outgoing UDP packets on ports 26000-30000, the qstat option would be -srcport 26000-30000

Note: The number of source ports given should be greater than or equal to the -maxsim (defaults to 20). The number of source ports will limit the number of simultaneous server queries.

-srcip IP-address
Specify a local IP address from which to send packets. This is useful on machines that have multiple IP addresses where the source IP of a packet is checked by the receiver. Normally this option is never needed.

NOTES

The response time is a measure of the expected playability of the server. The first number is the server's average time in milli-seconds to respond to a request packet from QStat. The second number is the total number of retries required to fetch the displayed information. More retries will cause the average response time to be higher. The response time will be more accurate if more requests are made to the server. For POQS, a request is made for each server rule and line of player information. So setting the -P and -R options will result in a more accurate response time. Quake and Hexen II are POQS. For most other game servers, QStat makes just one request to retrieve all the server status information, including server rules and player status. The -P and -R options do not increase the number of requests to the server. Half-Life supports three different requests for information; general status, players, and server rules. Each requires a separate request packet, so a total of three are used to retrieve player and rules.

Quake supports a number of control codes for special effects in player names. QStat normalizes the codes into the ASCII character set before display. The graphic codes are not translated except the orange brackets (hex 90, 10, 91, and 11) which are converted to '[' and ']'. Use the hex-player-names option -hpn to see the complete player name.

POQS do not return version information. But some small amount of info can be gathered from the server rules. The noexit rule did not appear until version 1.01. The Quake II server rules include a "version" key that contains the id build number. Recent releases of QuakeWorld have a "*version" key in the server rules. Unreal servers include a "gamever" key in the server rules that contains the server version without the decimal point. Most other game servers include some kind of version info in the server rules.

EXAMPLES

The following is an example address file that queries a QuakeWorld master, several Hexen II servers, some POQS, and a few Quake II servers.

QWM 192.246.40.12:27004
H2S 207.120.210.4
H2S 204.145.225.124
H2S 207.224.190.21
H2S 165.166.140.154
H2S 203.25.60.3
QS 207.25.198.110
QS 206.154.207.104
QS 205.246.42.31
QS 128.164.136.171
Q2S sm.iquest.net
Q2S 209.39.134.5
Q2S 209.39.134.3

If the above text were in a file called QSERVER.TXT, then the servers could be queried by running:
qstat -f QSERVER.TXT

IMPLEMENTATION NOTES

QStat sends packets to each host and waits for return packets. After some interval, another packet is sent to each host which has not yet responded. This is done several times before the host is considered non-responsive. QStat can wait for responses from up to 20 hosts at a time. For host lists longer than that, QStat checks more hosts as results are determined.

The following applies only applies to POQS. If QStat exceeds the maximum number of retries when fetching server information, it will give up and try to move on to the next information. This means that some rules or player info may occasionally not appear. Player info may also be missing if a player drops out between getting the general server info and requesting the player info. If QStat times out on one rule request, no further rules can be fetched. This is a side-effect of the Quake protocol design.

The number of available file descriptors limits the number of simultaneous servers that can be checked. QStat reuses file descriptors so it can never run out. The macro MAXFD in qstat.c determines how many file descriptors will be simultaneously opened. Raise or lower this value as needed. The default is 20 file descriptors.

Operating systems which translate ICMP Bad Port (ICMP_PORT_UNREACHABLE) into a ECONNREFUSED will display some hosts as DOWN. These hosts are up and connected to the network, but there is no program on the port. Solaris 2.5 and Irix 5.3 correctly support ICMP_PORT_UNREACHABLE, but Solaris 2.4 does not. See page 442 of "Unix Network Programming" by Richard Stevens for a description of this ICMP behavior.

Operating systems without correct ICMP behavior will just report hosts without Quake servers as non-responsive. Windows NT and Windows 95 don't seem to support this ICMP.

For hosts with multiple IP addresses, QStat will only send packets to the first address returned from the name service.

QStat supports Unreal version 2.15 or greater.

BUGS

PORTABILITY

UNIX - QStat has been compiled and tested on Solaris 2.x, Irix 5.3/6.2/6.3/6.4, FreeBSD 2.2/3.0, BSDi, HP-UX 10.20/11.0, and various flavors of Linux.

WINDOWS - The Windows version of QStat (win32/qstat.exe) runs on Windows 95 and Windows NT as a console application. On Windows 95 and NT 4.0, short-cuts can be used to set the arguments to qstat. On Windows NT 3.51, use a batch file.

OS/2 - An OS/2 binary is no longer included. Try contacting Per Hammer for an OS/2 Warp binary. per@mindbend.demon.co.uk.

VMS - The source includes a VMS patch from John Ross Hunt. This patch was tested on QStat 2.0b, but has not been tested on the current version. See COMPILE.txt for instructions.

VERSION

This is QStat version 2.10 The QStat webpage is updated for each new version and contains links to Quake server listings and pages about the Quake and Unreal network protocols. The page can be found at
http://www.qstat.org

Quake, Quake II, QuakeWorld, and Quake III created by id Software. Hexen II, HexenWorld, and Heretic II created by Raven Software. Unreal created by Epic Games. Half-Life created by Valve Software. Sin created by Ritual Entertainment. Shogo: Mobile Armor Division was created by Monolith Productions Inc. Tribes and Tribes 2 created by Dynamix, Inc. BFRIS created by Aegis Simulation Technologies. Kingpin created by Xatrix Entertainment Inc.

AUTHOR

Steve Jankowski
steve@qstat.org

COPYRIGHT

Copyright © 1996,1997,1998,1999,2000,2001,2002 by Steve Jankowski

LICENSE

QStat is covered by the terms of the Artistic License. The license terms can be found in LICENSE.txt of the QStat package.


APPENDIX A - Output Templates

QStat output templates provide greater control of the appearance of server status information. The results of a server query can be organized, formatted, and wrapped within any other text. The most obvious use is to generate HTML for web pages. However, it could also generate custom output for redisplay within another tool.

There are four output templates:
TemplateOption 
server-TsOutput once for each server queried. (required)
player-TpOutput once for each player. Must be used with -P. Invoked by the $PLAYERTEMPLATE variable.
rule-TrOutput once for each server rule. Invoked by the $RULETEMPLATE variable.
header-ThOutput once before any servers are queried.
trailer-TtOutput once after all servers are output.

The server template must be specified to enable template output. The other templates are optional.

Each output template is a file containing text and QStat variables. The text is output unchanged by QStat, but the variables are processed and replaced by QStat. Most variables are replaced by values from a queried server. Some variables have hardcoded values, and some generate no output, but affect how the template is processed.

Variables are grouped according to the templates where they can be used. General variables may be used in any of the templates. Server variables may be used in the server or player templates. Player variables may be used in the player template. Expression variables may only be used with the $IF and $IFNOT variables. If a variable is used where it doesn't make sense, it is ignored and generates no output.

Variables are specified using one of several syntaxes:

    $VAR
    $VAR:OPTION
    $(VAR)
    $(VAR:OPTION)
    $(VAR:OPTION(ARGUMENT))
The syntax used does not affect the output. However using the $() syntax is somewhat more readable when the text gets cluttered. If you want the variable to be followed immediately by text, then the $() syntax must be used.

Download considerations

If you are generating output to be downloaded, then you'll want to make your output as small as possible. In the case of HTML, you can reduce the size of your pages by excluding stuff.
  • Remove unneeded spaces (indenting and newlines)
  • Remove unneeded end tags. The HTML spec says the following tags can always be left out: </TD> </TR> </TH>
  • When creating a table, "width" modifiers are only needed on one cell of a column. Put them on the cells of the first row of the table.

    Display options

    The display options -u, -ne, and -nf have no affect on template output. Use the $IF:UP, $IF:ISEMPTY, and $IF:ISFULL conditions to accomplish the same thing.

    General Variables

    $QSTATURLOutput the web address of the QStat home page.
    $QSTATVERSIONOutput the version of QStat being run.
    $QSTATAUTHOROutput the name of the QStat programmer.
    $QSTATAUTHOREMAILOutput the email address of the QStat programmer.
    $HTMLEnable HTML friendly string output. Server results may include characters that have special meaning in HTML. These are replaced by equivalent SGML entities. QStat converts '<', '>', and '&' to '&lt;', '&gt;', and '&amp;'. Use this variable once in the header template.
    $CLEARNEWLINESConvert line feeds and carriage returns into spaces. Applies to all variables that output strings. Use this variable once in the header template.
    $RULENAMESPACESAllow spaces in rule names. Use this variable once in the header template.
    $IFConditional output. If the variable option is "true," the template is output up to a matching $ENDIF variable. If the variable option is "false," the template is ignored until after a matching $ENDIF. See Conditional Options for a list of supported conditional options.
    $IFNOTConditional output. Same as $IF, but the opposite sense.
    $ENDIFEnd conditional output. There must be one $ENDIF for each $IF and $IFNOT within a template.
    $NOWOutput the current local time.
    $TOTALSERVERSThe total number of servers to be queried.
    $TOTALUPThe number of servers up and running.
    $TOTALNOTUPThe number of servers either DOWN or TIMEOUT.
    $TOTALPLAYERSThe number of players found on all servers.
    $TOTALMAXPLAYERSThe sum of the maximum player values for all servers.
    $TOTALUTILIZATIONThe ratio of $TOTALPLAYERS to $TOTALMAXPLAYERS expressed as a percentage (a number between 0 and 100). Reports how full the servers are.
    $\Ignore the next newline. Not really a variable, but a way to curtail the output of extra newlines. Saves space in the output while the template remains readable. Must be the last thing on the line.
    $DEFAULTTYPEThe full name of the default server type specified with -default.

    Server Variables

    $HOSTNAMEOutput the host name of the server if known, otherwise the server address as given to QStat.
    $SERVERNAMEOutput the name of the server.
    $PINGThe time in milli-seconds to get a response from the server. If the server is DOWN or TIMEOUT, nothing is output.
    $PLAYERSThe number of players on the server.
    $MAXPLAYERSThe maximum number of players allowed on the server.
    $MAPThe name of the map being played.
    $GAMEThe name of the game being played. This is usually the name of the "mod" run by the server.
    $GAMETYPEThe type of game being played. Only applies to Quake 3. Typical values are Free For All, Capture the Flag, and Arena.
    $RETRIESThe number of retries needed to get the server status. This is a measure of packet loss.
    $IPADDRThe IP address of the server. Does not include the port number.
    $PORTThe port the server is running on.
    $ARGThe server address as given to QStat.
    $TYPEOutput one of the following depending on the server type:
        Quake
        Quake II
        Quake II Master
        QuakeWorld
        QuakeWorld Master
        Hexen II
        HexenWorld
        Unreal
        Unreal Tournament 2003
        Half-Life
        Half-Life Master
        Sin
        Tribes
        Tribes Master
        Tribes 2
        Tribes 2 Master
        Shogo: Mobile Armor Division
        Quake III: Arena
        Quake III Master
        BFRIS
        Kingpin
        Heretic II
        Soldier of Fortune
        Gamespy Master
        Gamespy Protocol
    
    If the server type is not known, nothing is output.
    $TYPESTRINGThe server's type string (see GAME OPTIONS table.)
    $TYPEPREFIXThe server's type prefix (same as $TYPESTRING but in all-caps.)
    $RULE:nameThe value of a server rule. If the rule is not returned by the server, nothing is output. Must be used with the -R flag. Server rule names can include any alpha-numeric character plus '*', '_', '.', or ' ' (space). The use of space in a rule name will require use of the parenthesized format: $(RULE:name)
    $ALLRULESOutput all the server rules in the format name=value separated by commas. Must be used with the -R flag.
    $PLAYERTEMPLATEInvoke the player template. The player template is output once for each player on the server. Must be used with the -P flag.
    $RULETEMPLATEInvoke the rule template. The rule template is output once for each server rule.

    Player Variables

    The player template is only invoked if $PLAYERTEMPLATE is used in the server template.

    $PLAYERNAMEThe name of the player. If -htmlnames or $HTML is used, then HTML color font tags will be added for Quake 3 and Tribes 2 player names. If $HTML is used but -nohtmlnames is set, then player names will not be colorized.
    $HTMLPLAYERNAMEThe name of the player with HTML color font tags. Only Quake 3 and Tribes 2 are supported.
    $FRAGSThe number of frags scored.
    $DEATHSDescent 3 - The number of times player has died. Ghost Recon - Indicates if the player is dead. Only available for Descent 3 or Ghost Recon.
    $PLAYERPINGThe player's ping time to the server. This value is not available from Half-Life or Ghost Recon servers.
    $CONNECTTIMEHow long the player has been playing. This value is only available from Quake, QuakeWorld, Hexen II, and Half-Life servers.
    $SKINThe name of the player's skin texture. This value is not available from ?? and Ghost Recon servers.
    $MESHThe name of the player's mesh (model). This value is only available from Unreal servers.
    $FACEThe name of the player's face texture. This value is only available from Unreal version 405+ servers.
    $SHIRTCOLORColor of the player's shirt. This value is only available from Quake, QuakeWorld, and Hexen II servers.
    $PANTSCOLORColor of the player's pants. This value is not available from Quake, QuakeWorld, and Hexen II servers.
    $PLAYERIPThe IP address of the player's computer. This value is only available from Quake and Hexen II servers.
    $TEAMNUMThe player's team number. This value is only available from Unreal, Tribes, Tribes 2 and Ghost Recon servers.
    $TEAMNAMEThe player's team name. This value is only available from Tribes and Tribes 2 servers.
    $TRIBETAGThe player's tribe tag. This value is only available from Tribes 2 servers.
    $PLAYERSTATIDThe player's global statistics id. This value is only available from Unreal Tournament 2003 servers.
    $PACKETLOSSThe player's packet loss. This value is only available from Tribes servers.
    $COLORNUMBERSDisplay $SHIRTCOLOR and $PANTSCOLOR as numbers. Equivalent to -ncn command-line option. No output.
    $COLORNAMESDisplay $SHIRTCOLOR and $PANTSCOLOR using color names. Equivalent to -cn command-line option. No output.
    $COLORRGBDisplay $SHIRTCOLOR and $PANTSCOLOR using #rrggbb format. Equivalent to -hc command-line option. No output.
    $TIMESECONDSDisplay $CONNECTTIME as number of seconds. Equivalent to -ts command-line option. No output.
    $TIMECLOCKDisplay $CONNECTTIME in clock format (DhDDmDDs). Equivalent to -tc command-line option. No output.
    $TIMESTOPWATCHDisplay $CONNECTTIME in stop-watch format (DD:DD:DD). Equivalent to -tsw command-line option. No output.

    Rule Variables

    The rule template is only invoked if $RULETEMPLATE is used in the server template. The rule template supports equality tests on rule names and values. See RULENAME and RULEVALUE under Conditional Options.

    $RULENAMEThe server rule name.
    $RULEVALUEThe server rule value.

    Conditional Options

    These options maybe used with the $IF and $IFNOT variables. For example, to display player information, the following could be used in the server template:

        $(IF:PLAYERS)$(IF:FLAG(-P))
        The server has $(PLAYERS) players:
        $(PLAYERTEMPLATE)
        $(ENDIF)$(ENDIF)
    
    The template between the $IF and $ENDIF variables will only be displayed if the server has one or more players and the -P flag was given to QStat.

    GAMETrue if the server is running a "mod."
    PLAYERSTrue if the server has one or more players.
    QUAKETrue if the server is running Quake (the original).
    QUAKE2True if the server is running Quake II.
    Q2MASTERTrue if the server is a Quake II master.
    QUAKEWORLDTrue if the server is running QuakeWorld.
    QWMASTERTrue if the server is a QuakeWorld master.
    HEXEN2True if the server is running Hexen II.
    HEXENWORLDTrue if the server is running HexenWorld.
    UNREALTrue if the server is running Unreal.
    UNREALTOURNAMENT2003True if the server is running Unreal Tournament 2003.
    HALFLIFETrue if the server is running Half-Life.
    HLMASTERTrue if the server is a Half-Life master.
    SINTrue if the server is running Sin.
    TRIBESTrue if the server is running Tribes.
    TRIBESMASTERTrue if the server is a Tribes master.
    TRIBES2True if the server is running Tribes 2.
    TRIBES2MASTERTrue if the server is a Tribes 2 master.
    SHOGOTrue if the server is running Shogo.
    QUAKE3True if the server is running Quake III.
    Q3MASTERTrue if the server is a Quake III master.
    BFRISTrue if the server is running BFRIS.
    KINGPINTrue if the server is running Kingpin.
    HERETIC2True if the server is running Heretic II.
    SOLDIEROFFORTUNETrue if the server is running Soldier of Fortune.
    DESCENT3True if the server is running Descent 3.
    GAMESPYMASTERTrue if the server is a Gamespy Master.
    GAMESPYPROTOCOLTrue if the server is running a "Gamespy style" status protocol.
    RULE(name)True if the rule name is set on the server. Server rule names can include any alpha-numeric character plus '*', '_', or '.'. If $RULENAMESPACES is enabled, then rule names may contain a ' ' (space).
    FLAG(name)True if the flag name was used on the QStat command-line. The only flag names supported are: -H, -P, and -R. Any other flag name returns false.
    UPTrue if the server is up and running.
    DOWNTrue if the server is known to be not running. This is true if the server computer returns an ICMP indicating that nothing is running on the port. Only supported by some operating systems.
    TIMEOUTTrue if the server never responded to a status query.
    HOSTNOTFOUNDTrue if the host name lookup failed.
    ISEMPTYTrue if the server has no players.
    ISMASTERTrue if this is a master server.
    ISFULLTrue if the server has the maximum players.
    ISTEAMTrue if the player is a team. Only available with Tribes and Tribes 2 servers. Only applies to the player template.
    ISBOTTrue if the player is a bot. Only available with Tribes 2 servers. Only applies to the player template.
    ISALIASTrue if the player is using an alias. Only available with Tribes 2 servers. Only applies to the player template.
    TRIBETAGTrue if the player has a tribe tag. Only available with Tribes 2 servers. Only applies to the player template.
    RULENAMETrue if the rule name matches the variable argument. For example $(IF:RULENAME(version)) will be true when the rule template is outputing a "version" server rule. Only applies to the rule template.
    RULEVALUETrue if the rule value matches the variable argument. For example $(IF:RULEVALUE(1)) will be true when the rule template is outputing a server rule whose value is "1". Only applies to the rule template.
    DEATHSTrue if the player has recorded DEATHS in Descent 3 or if the player is dead in Ghost Recon. NOTE if the Ghost Recon player has spawns available they can go from dead to alive.

    APPENDIX B - QStat Configuration File

    QStat configuration files modify built-in game types or create new game types. New command-line options and template variables are created for new game types.

    Please refer to the default configuration file for examples. The default configuration file qstat.cfg can be found in the QStat package.

    Config File Load Order

    QStat will load one default configuration file and zero or more command-line configuration files. The default configuration file will be the first readable file found by the following search.
    1. File named in $QSTAT_CONFIG environment variable.
    2. Unix: $HOME/.qstatrc
      Windows: $HOME/qstat.cfg
    3. Unix: sysconfdir/qstat.cfg
      Windows: location-of-qstat.exe/qstat.cfg
    The default configuration file will be loaded before reading any command-line parameters. Configuration files specified on the command line will be merged with the contents of the default config file. In the case of duplicate game types, the command-line config files will be used. The QStat package includes a qstat.cfg that defines several new game types. If you want to use these game types, you need to place the file where it can be found by the default config file search. Or use the -cfg option.
    Unix Note: The sysconfdir is determined when qstat is compiled. For Unix compiles, the QStat makefiles default to /etc. To compile with a different sysconfdir, set SYSCONFDIR when compiling with gmake. For example, to set sysconfdir to /usr/local/etc
    % gmake SYSCONFDIR=/usr/local/etc
    Windows Note: The location-of-qstat.exe is the directory where the QStat executable (qstat.exe) is located. Just put the default qstat.cfg in the same directory as qstat.exe.

    General Syntax

    QStat configuration files describe game types using "stanzas". A stanza begins with a "gametype" line and is followed by several parameter lines ending with an "end" line. The general syntax looks like this:
    gametype type-string (modify | new extend type-string)
        parameter-name = parameter-value
        ...
    end
    
    The text in bold are keywords that must be used as shown.

    Parameter names are one or more words separated by spaces. The supported parameters and their meaning are listed in Gametype Parameters. Extra white space before, after and within a parameter name is ignored. An equal sign ('=') must separate the parameter name and the parameter value. There can be one parameter setting per line.

    Parameter values are one more characters or escape sequences. Leading and trailing spaces are ignored. All characters are used as-is except for backslash ('\') which begins an escape sequence.

    \\A single backslash ('\')
    \nA newline (ASCII char 10)
    \rA carriage return (ASCII char 13)
    \(space) A space (ASCII char 32). This escape should be entered as two characters: backslash followed by one space.
    \xHHA single character represented by the two-digit hexadecimal code. The hex digits H must be 0-9, A-F, or a-f.
    \DDDA single character represented by the three-digit octal code. The octal digits D must be 0-7.

    Defining New Game Types

    New game types are defined with the new keyword.
    gametype new-type-string new extend existing-type-string
        parameter-name = parameter-value
        ...
    end
    
    The new-type-string must not be a built-in type string. If a new gametype is defined multiple times in configuration files, only the last definition is used. The existing-type-string can be any built-in or configuration defined game type. However, QStat has the best support for extending Q3S, Q2S, GPS, UNS, and Q3M game types.

    The new game type has command-line option, type string and type prefix derived from new-type-string. The case of new-type-string is ignored. The command-line option and type string are always lower-case and the type prefix is always upper case.

    The new game type starts with the same parameters as the existing-type-string except for the type string itself. Game type parameters are set by the following parameter setting lines. Some parameters may only be used with master server and some only with game servers.

    We suggest that new-type-strings be as short as possible and end with an 's' for game servers and an 'm' for master servers. New game types should, but are not required to, set the name, default port, and template var parameters. The template var should be all upper-case and should not contain any spaces.

    Modifying Game Types

    Existing game types can be modified to update their query parameters.
    gametype existing-type-string modify
        parameter-name = parameter-value
        ...
    end
    
    The existing-type-string can be a built-in game type or a configuration defined game type.

    Only certain parameters can be modified: master protocol, master query, and master packet.

    Request Packets

    The request packets used for game server queries can be set with status packet, status2 packet, player packet, and rule packet. Request packets for master servers can be set with the master packet parameter.

    A request packet can only be set if the extended game type uses the same type of request packet. If a game type only uses the status packet, then an extending game type can only set the status packet.

    Request packet typically contain binary characters (those beyond the printable ASCII character set). These can be specified using the hex and octal character escapes.

    If the master packet parameter is set, the master protocol and master query parameters will be ignored.

    Game Type Parameters

    nameSets the name of the game type. Should be the full game name as used by the publisher.
    flagsset the query flags. Bitwise OR of the following constants:
    • TF_SINGLE_QUERY
    • TF_OUTFILE
    • TF_MASTER_MULTI_RESPONSE
    • TF_TCP_CONNECT
    • TF_QUERY_ARG
    • TF_QUERY_ARG_REQUIRED
    • TF_QUAKE3_NAMES
    • TF_TRIBES2_NAMES
    • TF_SOF_NAMES
    • TF_U2_NAMES
    • TF_RAW_STYLE_QUAKE
    • TF_RAW_STYLE_TRIBES
    • TF_RAW_STYLE_GHOSTRECON
    • TF_NO_PORT_OFFSET
    • TF_SHOW_GAME_PORT
    default portDefault network port used by the status protocol.
    status port offsetOffset of the status/query port from the game port.
    game ruleThe server rule containing the name of the game style or game mod.
    template varThe template variable used to test whether a server is of this game type.
    status packetThe status request packet. This is the first packet sent to a server of this game type.
    status2 packetThe second status request packet. If the server responded to the first status packet, then this packet is sent, but only if player or rule info is needed (command-line options -P or -R).
    player packetThe player request packet. Requests player information.
    rule packetThe rule request packet. Requests server rule information.
    master for gametypeSets the type of game returned by this master. The value must be a built-in or configuration defined game type.
    master protocolThe protocol number to use in the master request. The master server will respond with servers that match the protocol number. The numbers change with each version of the game that uses an incompatible network protocol. The master protocol is used mainly with Quake 3 based games.

    The master request packet will combine the master protocol and master query values.

    master queryThe query string to use in the master request. The master query string provides additional filtering for the master server.

    The default master request packet will combine the master protocol and master query values.

    master packetThe master request packet. Requests a server list from the master server. If master packet is set, master protocol and master query are ignored.
    qstat-2.17/scripts/000077500000000000000000000000001412457473700143045ustar00rootroot00000000000000qstat-2.17/scripts/gen-version.cmd000077500000000000000000000026021412457473700172300ustar00rootroot00000000000000@echo off :: Default file paramters SETLOCAL EnableDelayedExpansion SET "VERSION_FILE=.version" SET "TEMPLATE_FILE=version.h.tmpl" SET "HEADER_FILE=version.h" IF NOT [%1] == [] ( SET "VERSION_FILE=%1" ) IF NOT [%2] == [] ( SET "TEMPLATE_FILE=%2" ) IF NOT [%3] == [] ( SET "HEADER_FILE=%3" ) IF [!QSTAT_VERSION!] == [] ( :: QSTAT_VERSION not set determine from git. FOR /F "usebackq" %%v IN (`cmd /c "git status --porcelain | grep -Fv 'gnuconfig.h.in~' | grep -E '^(M^| M^|\?\?)' | wc -l | sed -e 's/^[[:space:]]*//'"`) DO ( IF NOT %%v == 0 ( :: We have modifications so indicate so in the version. SET "QSTAT_VERSION=!QSTAT_VERSION!-modified" ) ) ) IF EXIST "!VERSION_FILE!" ( :: .version file exists load the value. set /p OLD_VERSION=<"!VERSION_FILE!" ) IF NOT [!OLD_VERSION!] == [!QSTAT_VERSION!] ( :: version has changed update the file. echo|set /p="!QSTAT_VERSION!" > "!VERSION_FILE!" ) :: Update version.h if needed. IF NOT EXIST "!HEADER_FILE!" ( :: File doesn't exist to just create. sed "s/CHANGEME/!QSTAT_VERSION!/g" "!TEMPLATE_FILE!" > "!HEADER_FILE!" ) else ( :: File exists compare with new. SET "TMP_FILE=!HEADER_FILE!.tmp" sed "s/CHANGEME/!QSTAT_VERSION!/g" "!TEMPLATE_FILE!" > "!TMP_FILE!" :: cmp doesn't correctly set ERRORLEVEL so we use redirection. cmp -s "!HEADER_FILE!" "!TMP_FILE!" && rm -f "!TMP_FILE!" || mv "!TMP_FILE!" "!HEADER_FILE!" ) ENDLOCAL qstat-2.17/scripts/version.sh000077500000000000000000000006301412457473700163270ustar00rootroot00000000000000#!/bin/sh if [ "$QSTAT_VERSION" = "" ]; then QSTAT_VERSION=`git describe --tags --always` fi # Detect if our git repo is in a dirty state (uncommitted changes) GIT_DIRTY_FILE=`git status --porcelain 2>/dev/null| grep -Fv 'gnuconfig.h.in~' | egrep "^(M| M|\?\?)" | wc -l | sed -e 's/^[[:space:]]*//'` if [ ${GIT_DIRTY_FILE} -ne 0 ]; then QSTAT_VERSION="$QSTAT_VERSION-modified" fi echo -n $QSTAT_VERSION qstat-2.17/starmade.c000066400000000000000000000126621412457473700145700ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * StarMade query protocol * Copyright 2013 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms * */ #include #ifndef _WIN32 #include #include #include #else #include #endif #include #include #include #include "debug.h" #include "qstat.h" #include "utils.h" #include "packet_manip.h" typedef enum { SM_INT = 0x01, SM_LONG, SM_FLOAT, SM_STRING, SM_BOOLEAN, SM_BYTE, SM_SHORT, SM_BYTE_ARRAY } starmade_param_type; query_status_t send_starmade_request_packet(struct qserver *server) { char buf[13] = { 0x00, 0x00, 0x00, 0x09, 0x2a, 0xff, 0xff, 0x01, 0x6f, 0x00, 0x00, 0x00, 0x00 }; /* * Header * int(4) - packet length * byte - packet type : 0x2a = packet, 0x20 = ping, test = 0x64, logout = 0x41 * short(2) - packet id * byte - command id : 0x01 = status, 0x02 = exec * byte - command type : 0x6f = parameterised command, 0x84 = stream command * * Paramters * int(4) - paramter count * byte - paramter type : 1 = int, 2 = long, 3 = float, 4 = UTF8 string, 5 = boolean, 6 = byte, 7 = short, 8 = byte array * ... */ debug(3, "send_starmade_request_packet: state = %ld", server->challenge); return (send_packet(server, buf, 13)); } static query_status_t starmade_read_parameter(char **datap, int *datalen, void *val, int vlen, starmade_param_type type) { int size; if (**datap != type) { /* unexpected type */ debug(2, "Invalid type detected, expected 0x%02x got 0x%02x", type, **datap); return (REQ_ERROR); } (*datap)++; (*datalen)--; switch (type) { case SM_INT: size = 4; if ((size > vlen) || (size > *datalen)) { return (MEM_ERROR); } *(uint32_t *)val = ((uint32_t)(*datap)[3]) | ((uint32_t)(*datap)[2] << 8) | ((uint32_t)(*datap)[1] << 16) | ((uint32_t)(*datap)[0] << 24); break; case SM_LONG: size = 8; if ((size > vlen) || (size > *datalen)) { return (MEM_ERROR); } *(uint64_t *)val = ((uint64_t)(*datap)[7]) | ((uint64_t)(*datap)[6] << 8) | ((uint64_t)(*datap)[5] << 16) | ((uint64_t)(*datap)[4] << 24) | ((uint64_t)(*datap)[3] << 32) | ((uint64_t)(*datap)[2] << 40) | ((uint64_t)(*datap)[1] << 48) | ((uint64_t)(*datap)[0] << 56); break; case SM_FLOAT: size = 4; if ((size > vlen) || (size > *datalen)) { return (MEM_ERROR); } *(uint32_t *)val = (uint32_t)(*datap)[3] | ((uint32_t)(*datap)[2] << 8) | ((uint32_t)(*datap)[1] << 16) | ((uint32_t)(*datap)[0] << 24); break; case SM_STRING: size = ((short)(*datap)[1]) | ((short)(*datap)[0] << 8); if ((size > vlen) || (size > *datalen)) { return (MEM_ERROR); } (*datap) += 2; memcpy(val, *datap, size); ((char *)val)[size] = 0x00; break; case SM_BOOLEAN: size = 1; if ((size > vlen) || (size > *datalen)) { return (MEM_ERROR); } *(unsigned char *)val = **datap; break; case SM_BYTE: size = 1; if ((size > vlen) || (size > *datalen)) { return (MEM_ERROR); } *(unsigned char *)val = **datap; break; case SM_SHORT: size = 2; if ((size > vlen) || (size > *datalen)) { return (MEM_ERROR); } *(short *)val = (short)(*datap)[1] | ((short)(*datap)[0] << 8); break; case SM_BYTE_ARRAY: debug(2, "Unsupport type 0x%02x requested / received", type); return (REQ_ERROR); default: debug(2, "Unknown type 0x%02x requested / received", type); return (REQ_ERROR); } (*datap) += size; (*datalen) -= size; return (INPROGRESS); } query_status_t deal_with_starmade_packet(struct qserver *server, char *rawpkt, int pktlen) { char *s, buf[256]; int ret; debug(2, "processing..."); if (pktlen < 54) { // Invalid password return (REQ_ERROR); } s = rawpkt; // TODO: continue reading / combining packets until we've read enough. debug(3, "packet: combined = %d, challenge = %ld", server->combined, server->challenge); // Packet Header // int - response size excluding header (12 bytes) s += 4; // long - ts?? s += 8; // Response Header // TODO: validate header details // byte - packet type s += 1; // short - packet id s += 2; // byte - command id s += 1; // byte - command type s += 1; // Parameters // int - paramater count s += 4; // byte - info version ret = starmade_read_parameter(&s, &pktlen, buf, sizeof(buf), SM_BYTE); if (ret != INPROGRESS) { return (ret); } // float - version ret = starmade_read_parameter(&s, &pktlen, buf, sizeof(buf), SM_FLOAT); if (ret != INPROGRESS) { return (ret); } // string - server name ret = starmade_read_parameter(&s, &pktlen, buf, sizeof(buf), SM_STRING); if (ret != INPROGRESS) { return (ret); } server->server_name = strdup(buf); // string - description ret = starmade_read_parameter(&s, &pktlen, buf, sizeof(buf), SM_STRING); if (ret != INPROGRESS) { return (ret); } // long - start time ret = starmade_read_parameter(&s, &pktlen, buf, sizeof(buf), SM_LONG); if (ret != INPROGRESS) { return (ret); } // int - player count ret = starmade_read_parameter(&s, &pktlen, &server->num_players, sizeof(server->num_players), SM_INT); if (ret != INPROGRESS) { return (ret); } // int - max players ret = starmade_read_parameter(&s, &pktlen, &server->max_players, sizeof(server->max_players), SM_INT); if (ret != INPROGRESS) { return (ret); } gettimeofday(&server->packet_time1, NULL); server->map_name = strdup("default"); return (DONE_FORCE); } qstat-2.17/starmade.h000066400000000000000000000006711412457473700145720ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * StarMade protocol * Copyright 2013 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_STARMADE_H #define QSTAT_STARMADE_H #include "qserver.h" // Packet processing methods query_status_t deal_with_starmade_packet(struct qserver *server, char *pkt, int pktlen); query_status_t send_starmade_request_packet(struct qserver *server); #endif qstat-2.17/tee.c000066400000000000000000000344021412457473700135410ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * Teeworlds protocol * Thanks to Emiliano Leporati for the first Teeworlds server patch (2008) * Thanks to Thomas Debesse for the server rewrite and master addition (2014-2016) * Thanks to Steven Hartland for some parts and generous help (2014) * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #include #include #include #include "debug.h" #include "qstat.h" #include "packet_manip.h" /* See "scripts/tw_api.py" from Teeworlds project */ /* query server */ static char teeserver_request_packet[15] = { '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', 'g', 'i', 'e', '\x00', '\x00' }; static int len_teeserver_request_packet = sizeof(teeserver_request_packet) / sizeof(char); /* server response */ static char teeserver_info_headerprefix[13] = { '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', 'i', 'n', 'f' }; static int len_teeserver_info_headerprefix = sizeof(teeserver_info_headerprefix) / sizeof(char); /* * To request, we will try 3 request packets, only one character and the size differ, so no need to declare 3 strings * * char teeserver_request_packet[14] = { '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', 'g', 'i', 'e', 'f' }; * char teeserver_request_packe2[15] = { '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', 'g', 'i', 'e', '2', '\x00' }; * char teeserver_request_packe3[15] = { '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', 'g', 'i', 'e', '3', '\x00' }; * * To analyze response, we will compare the same string without the last character, then the last character, so no need to declare 3 strings * * char teeserver_info_header[14] = { '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', 'i', 'n', 'f', 'o' }; * char teeserver_inf2_header[14] = { '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', 'i', 'n', 'f', '2' }; * char teeserver_inf3_header[14] = { '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', 'i', 'n', 'f', '3' }; */ /* * For information, Teeworlds packet samples per header, explained * * Header "info": \xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', 'i', 'n', 'f', 'o' * Answer sample: '\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffinfo0.5.1\x00.:{TeeKnight|Catch16}:. Catch16 hosted by TeeKnight.de\x00lightcatch\x00Catch16\x000\x00-1\x000\x0016\x00' * Answer format: (*char)header,(*char)version,(*char)name,(*char)map,(*char)gametype,(int)flags,(int)progression,(int)num_players,(int)max_players,*players[] * * Header "inf2": \xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', 'i', 'n', 'f', '2' * Answer sample: '\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffinf20\x000.5.1\x00.:{TeeKnight|Catch16}:. Catch16 hosted by TeeKnight.de\x00lightcatch\x00Catch16\x000\x00-1\x000\x0016\x00' * Answer format: (*char)header,(*char)token,(*char)version),(*char)name,(*char)map,(*char)gametype,(int)flags,(int)progression,(int)num_players,(int)max_players,*players[] * * Header "inf3": \xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', 'i', 'n', 'f', '3' * Answer sample: '\xff\xff\xff\xff\xff\xff\xff\xff\xff\xffinf30\x000.6.1\x00[Bigstream.ru] CTF5 only\x00ctf5\x00CTF\x000\x000\x0016\x000\x0016\x00' * Answer format: (*char)header,(*char)token,(*char)version),(*char)name,(*char)map,(*char)gametype,(int)flags,(int)num_players,(int)max_players,(int)num_clients,(int)max_clients,*players[] */ /* query master */ static char teemaster_packet[14] = { '\x20', '\x00', '\x00', '\x00', '\x00', '\x00', '\xFF', '\xFF', '\xFF', '\xFF', 'r', 'e', 'q', 't' }; static int len_teemaster_packet = sizeof(teemaster_packet) / sizeof(char); /* master response header, the last char defines the protocol version, everything before that last char is the same */ static char teemaster_list_headerprefix[13] = { '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', 'l', 'i', 's' }; static int len_teemaster_list_headerprefix = sizeof(teemaster_list_headerprefix) / sizeof(char); static int len_teemaster_list_header = 1 + sizeof(teemaster_list_headerprefix) / sizeof(char); /* * To request, we will try 2 request packets, only one character differs, so no need to declare 2 strings * * char teemaster_packet[14] = { '\x20', '\x00', '\x00', '\x00', '\x00', '\x00', '\xFF', '\xFF', '\xFF', '\xFF', 'r', 'e', 'q', 't' }; * char teemaster_packe2[14] = { '\x20', '\x00', '\x00', '\x00', '\x00', '\x00', '\xFF', '\xFF', '\xFF', '\xFF', 'r', 'e', 'q', '2' }; * * To analyze response, we will compare the same string without the last character, so no need to declare 3 strings * * char teemaster_list_header[14] = { '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', 'l', 'i', 's', 't' }; * char teemaster_lis2_header[14] = { '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', '\xFF', 'l', 'i', 's', '2' }; */ /* ipv4 header for ipv4 addresses stored in ipv6 slots in normal server list */ static char tee_ipv4_header[12] = { '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\x00', '\xFF', '\xFF' }; static int len_tee_ipv4_header = sizeof(tee_ipv4_header) / sizeof(char); query_status_t send_teeserver_request_packet(struct qserver *server) { debug(2, "send_teeserver_request_packet %p", server); query_status_t ret; /* * Try first then second then third... * In fact the master server said which server use which protocol, but how qstat can transmit this information? */ /* send getinfo1 packet (legacy server first query) */ teeserver_request_packet[13] = 'f'; ret = send_packet(server, teeserver_request_packet, 13); if (ret != INPROGRESS) { return (ret); } /* send getinfo2 packet (legacy server second query for additional data) */ teeserver_request_packet[13] = '2'; ret = send_packet(server, teeserver_request_packet, len_teeserver_request_packet); if (ret != INPROGRESS) { return (ret); } /* send getinfo3 packet (normal server lone query) */ teeserver_request_packet[13] = '3'; return (send_packet(server, teeserver_request_packet, len_teeserver_request_packet)); } query_status_t deal_with_teeserver_packet(struct qserver *server, char *rawpkt, int rawpktlen) { int i; char last_char; char *current = NULL, *end = NULL, *version = NULL, *tok = NULL; struct player *player; debug(2, "deal_with_teeserver_packet %p, %d", server, rawpktlen); server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); if (rawpktlen < len_teeserver_info_headerprefix) { malformed_packet(server, "packet too short"); return (PKT_ERROR); } /* not null-terminated packet */ if ((strnlen(rawpkt, rawpktlen) == rawpktlen) && (rawpkt[rawpktlen] != 0)) { malformed_packet(server, "not null-terminated packet"); return (PKT_ERROR); } /* get the last character */ last_char = rawpkt[len_teeserver_info_headerprefix]; /* compare the response without the last character */ if (memcmp(rawpkt, teeserver_info_headerprefix, len_teeserver_info_headerprefix) != 0) { malformed_packet(server, "unknown packet header"); return (PKT_ERROR); } /* and verify if the last character is 'o', '2' or '3' */ if ((last_char != 'o') && (last_char != '2') && (last_char != '3')) { malformed_packet(server, "unknown packet format"); return (PKT_ERROR); } current = rawpkt; end = rawpkt + rawpktlen; /* header, skip */ current += len_teeserver_info_headerprefix + sizeof(last_char); /* if inf2 or inf3 */ if ((last_char == '2') || (last_char == '3')) { /* token, skip */ current += strnlen(current, end - current) + 1; } /* version */ version = current; current += strnlen(current, end - current) + 1; /* server name */ server->server_name = strdup(current); current += strnlen(current, end - current) + 1; /* map name */ server->map_name = strdup(current); current += strnlen(current, end - current) + 1; /* game type */ add_rule(server, server->type->game_rule, current, NO_FLAGS); current += strnlen(current, end - current) + 1; /* flags, skip */ current += strnlen(current, end - current) + 1; /* if info or inf2 */ if ((last_char == 'o') || (last_char == '2')) { // progression, skip current += strnlen(current, end - current) + 1; } /* num players */ server->num_players = atoi(current); current += strnlen(current, end - current) + 1; /* max players */ server->max_players = atoi(current); current += strnlen(current, end - current) + 1; /* if inf3 */ if (last_char == '3') { /* is there a difference between a Teeworlds spectator and what qstat calls a "client"? */ /* num clients, skip */ current += strnlen(current, end - current) + 1; /* max clients, skip */ current += strnlen(current, end - current) + 1; } /* players */ for (i = 0; i < server->num_players; i++) { player = add_player(server, i); player->name = strdup(current); current += strnlen(current, end - current) + 1; player->score = atoi(current); current += strnlen(current, end - current) + 1; } /* version reprise */ server->protocol_version = 0; tok = strtok(version, "."); if (tok == NULL) { malformed_packet(server, "malformed server version string"); return (PKT_ERROR); } server->protocol_version |= (atoi(tok) & 0x000F) << 12; tok = strtok(NULL, "."); if (tok == NULL) { malformed_packet(server, "malformed server version string"); return (PKT_ERROR); } server->protocol_version |= (atoi(tok) & 0x000F) << 8; tok = strtok(NULL, "."); if (tok == NULL) { malformed_packet(server, "malformed server version string"); return (PKT_ERROR); } server->protocol_version |= (atoi(tok) & 0x00FF); return (DONE_FORCE); } query_status_t send_teemaster_request_packet(struct qserver *server) { debug(2, "send_teemaster_request_packet %p", server); query_status_t ret_packet, ret_packe2; /* query for legacy list (ipv4 only list) of legacy servers (using legacy getinfo and getinfo2 queries) */ teemaster_packet[13] = 't'; ret_packet = send_packet(server, teemaster_packet, len_teemaster_packet); if (ret_packet != INPROGRESS) { return (ret_packet); } /* query for normal list (mixed ipv4 and ipv6 list) of normal servers (using normal getinfo3 query) */ teemaster_packet[13] = '2'; ret_packe2 = send_packet(server, teemaster_packet, len_teemaster_packet); return (ret_packe2); } query_status_t process_legacy_teemaster_packet_data(struct qserver *server, char *rawpkt, int rawpktlen) { int num_servers, previous_len, len_address_packet, i; char *current, *dumb_pointer; /* six bytes address */ debug(1, "teeworlds master response uses legacy packet format (ipv4 server list)"); server->server_name = MASTER; /* length of an address packet in rawpktlen with legacy packet format */ len_address_packet = 6; num_servers = rawpktlen / len_address_packet; server->n_servers += num_servers; previous_len = server->master_pkt_len; server->master_pkt_len = server->n_servers * 6; dumb_pointer = (char *)realloc(server->master_pkt, server->master_pkt_len); if (dumb_pointer == NULL) { free(server->master_pkt); debug(0, "Failed to realloc memory for internal master packet"); return (MEM_ERROR); } server->master_pkt = dumb_pointer; current = server->master_pkt + previous_len; for (i = 0; i < num_servers; i++) { if (rawpktlen < len_address_packet) { malformed_packet(server, "packet too short"); return (PKT_ERROR); } memcpy(current, rawpkt, 4); memcpy(current + 5, rawpkt + 4, 1); memcpy(current + 4, rawpkt + 5, 1); /* 6 is the internal length of an address in qstat's server->master_pkt */ current += 6; rawpkt += len_address_packet; rawpktlen -= len_address_packet; } return (INPROGRESS); } query_status_t process_new_teemaster_packet_data(struct qserver *server, char *rawpkt, int rawpktlen) { int num_servers, previous_len, len_address_packet, i; char *current, *dumb_pointer; /* eighteen bytes address */ debug(1, "teeworlds master response uses normal packet format (mixed ipv4/ipv6 server list)"); server->server_name = MASTER; len_address_packet = 18; num_servers = rawpktlen / len_address_packet; for (i = 0; i < num_servers; i++) { if (rawpktlen < len_address_packet) { malformed_packet(server, "packet too short"); return (PKT_ERROR); } if (memcmp(rawpkt, tee_ipv4_header, len_tee_ipv4_header) == 0) { debug(3, "teeworlds ipv4 server found in normal teeworlds master packet"); previous_len = server->master_pkt_len; server->n_servers += 1; server->master_pkt_len += 6; dumb_pointer = (char *)realloc(server->master_pkt, server->master_pkt_len); if (dumb_pointer == NULL) { free(server->master_pkt); debug(0, "Failed to realloc memory for internal master packet"); return (MEM_ERROR); } server->master_pkt = dumb_pointer; current = server->master_pkt + previous_len; memcpy(current, rawpkt + len_tee_ipv4_header, 6); } else { /* currently unsuported */ debug(3, "teeworlds ipv6 server found in normal teeworlds master packet, discarding"); } rawpkt += len_address_packet; rawpktlen -= len_address_packet; } return (INPROGRESS); } query_status_t deal_with_teemaster_packet(struct qserver *server, char *rawpkt, int rawpktlen) { char last_char; debug(2, "deal_with_teemaster_packet %p, %d", server, rawpktlen); server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); if (rawpktlen < len_teemaster_list_headerprefix) { malformed_packet(server, "packet too short"); return (PKT_ERROR); } /* compare the response without the last character */ if (memcmp(rawpkt, teemaster_list_headerprefix, len_teemaster_list_headerprefix) != 0) { malformed_packet(server, "unknown packet header"); return (PKT_ERROR); } /* get the last header character */ last_char = rawpkt[len_teemaster_list_headerprefix]; /* jump to the data */ rawpkt += len_teemaster_list_header; rawpktlen -= len_teemaster_list_header; if (last_char == 't') { return process_legacy_teemaster_packet_data(server, rawpkt, rawpktlen); } else if (last_char == '2') { return process_new_teemaster_packet_data(server, rawpkt, rawpktlen); } /* unknown server list format */ malformed_packet(server, "unknown packet format"); return (PKT_ERROR); } qstat-2.17/tee.h000066400000000000000000000011341412457473700135420ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * Teeworlds protocol * Copyright 2008 ? Emiliano Leporati * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_TEE_H #define QSTAT_TEE_H #include "qserver.h" // Packet processing methods query_status_t send_teeserver_request_packet(struct qserver *server); query_status_t deal_with_teeserver_packet(struct qserver *server, char *rawpkt, int pktlen); query_status_t send_teemaster_request_packet(struct qserver *server); query_status_t deal_with_teemaster_packet(struct qserver *server, char *rawpkt, int pktlen); #endif qstat-2.17/template.c000066400000000000000000000647651412457473700146160ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * steve@qstat.org * http://www.qstat.org * * Thanks to Per Hammer for the OS/2 patches (per@mindbend.demon.co.uk) * Thanks to John Ross Hunt for the OpenVMS Alpha patches (bigboote@ais.net) * Thanks to Scott MacFiggen for the quicksort code (smf@webmethods.com) * * Inspired by QuakePing by Len Norton * * Copyright 1996,1997,1998,1999,2000,2001,2002 by Steve Jankowski * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #include #include #include #include #include #if defined(__hpux) || defined(_AIX) || defined(__FreeBSD__) || defined(__MidnightBSD__) #include #endif #ifndef _WIN32 #include #endif #include "qstat.h" #include "xform.h" #ifdef _WIN32 #define strcasecmp stricmp #define strncasecmp strnicmp #endif #ifdef __hpux #define STATIC static #else #define STATIC #endif /* #ifdef __cplusplus * extern "C" { #endif #ifndef _AIX * extern unsigned int ntohl(unsigned int n); #endif #ifdef __cplusplus * } #endif */ extern int hostname_lookup; extern int num_servers_total; extern int num_servers_timed_out; extern int num_servers_down; extern int num_players_total; extern int max_players_total; extern int xform_html_names; extern int xform_hex_player_names; extern FILE *OF; /* output file */ static char *server_template; static char *rule_template; static char *header_template; static char *trailer_template; static char *player_template; static void display_server_var(struct qserver *server, int var); static void display_player_var(struct player *player, int var, struct qserver *server); static void display_rule_var(struct rule *rule, int var, struct qserver *server); static void display_generic_var(int var); static int parse_var(char *varname, int *varlen); static int read_template(char *filename, char **template_text); static void display_string(char *str); static int is_true(struct qserver *server, struct player *player, struct rule *rule, char *expr); #define VARIABLE_CHAR '$' static char *variable_option; static int if_skip; static int if_level; static int if_skip_save; static int if_level_save; int html_mode = 0; int clear_newlines_mode = 0; int rule_name_spaces = 0; struct vardef { char *var; int varcode; int options; }; #define NO_OPTIONS 0 #define OPTIONS_OK 1 #define EXPR 2 struct vardef variable_defs[] = { #define V_HOSTNAME 1 { "HOSTNAME", V_HOSTNAME, NO_OPTIONS }, #define V_SERVERNAME 2 { "SERVERNAME", V_SERVERNAME, NO_OPTIONS }, #define V_PING 3 { "PING", V_PING, NO_OPTIONS }, #define V_PLAYERS 4 { "PLAYERS", V_PLAYERS, NO_OPTIONS | EXPR }, #define V_MAXPLAYERS 5 { "MAXPLAYERS", V_MAXPLAYERS, NO_OPTIONS }, #define V_MAP 6 { "MAP", V_MAP, NO_OPTIONS }, #define V_GAME 7 { "GAME", V_GAME, NO_OPTIONS | EXPR }, #define V_RETRIES 8 { "RETRIES", V_RETRIES, NO_OPTIONS }, #define V_IPADDR 9 { "IPADDR", V_IPADDR, NO_OPTIONS }, #define V_PORT 10 { "PORT", V_PORT, NO_OPTIONS }, #define V_ARG 11 { "ARG", V_ARG, NO_OPTIONS }, #define V_QSTATURL 12 { "QSTATURL", V_QSTATURL, NO_OPTIONS }, #define V_QSTATVERSION 13 { "QSTATVERSION", V_QSTATVERSION, NO_OPTIONS }, #define V_QSTATAUTHOR 14 { "QSTATAUTHOR", V_QSTATAUTHOR, NO_OPTIONS }, #define V_QSTATAUTHOREMAIL 15 { "QSTATAUTHOREMAIL", V_QSTATAUTHOREMAIL, NO_OPTIONS }, #define V_TYPE 16 { "TYPE", V_TYPE, NO_OPTIONS }, #define V_RULE 17 { "RULE", V_RULE, OPTIONS_OK | EXPR }, #define V_ALLRULES 18 { "ALLRULES", V_ALLRULES, NO_OPTIONS }, #define V_PLAYERTEMPLATE 19 { "PLAYERTEMPLATE", V_PLAYERTEMPLATE, NO_OPTIONS }, #define V_PLAYERNAME 20 { "PLAYERNAME", V_PLAYERNAME, NO_OPTIONS }, #define V_FRAGS 21 { "FRAGS", V_FRAGS, NO_OPTIONS }, #define V_PLAYERPING 22 { "PLAYERPING", V_PLAYERPING, NO_OPTIONS }, #define V_CONNECTTIME 23 { "CONNECTTIME", V_CONNECTTIME, NO_OPTIONS }, #define V_SKIN 24 { "SKIN", V_SKIN, NO_OPTIONS }, #define V_SHIRTCOLOR 25 { "SHIRTCOLOR", V_SHIRTCOLOR, NO_OPTIONS }, #define V_PANTSCOLOR 26 { "PANTSCOLOR", V_PANTSCOLOR, NO_OPTIONS }, #define V_PLAYERIP 27 { "PLAYERIP", V_PLAYERIP, NO_OPTIONS }, #define V_IF 28 { "IF", V_IF, OPTIONS_OK }, #define V_ENDIF 29 { "ENDIF", V_ENDIF, NO_OPTIONS }, #define V_HTML 35 { "HTML", V_HTML, NO_OPTIONS }, #define V_FLAG 36 { "FLAG", V_FLAG, OPTIONS_OK | EXPR }, #define V_UP 37 { "UP", V_UP, NO_OPTIONS | EXPR }, #define V_DOWN 38 { "DOWN", V_DOWN, NO_OPTIONS | EXPR }, #define V_IFNOT 39 { "IFNOT", V_IFNOT, OPTIONS_OK }, #define V_TIMEOUT 40 { "TIMEOUT", V_TIMEOUT, NO_OPTIONS | EXPR }, #define V_NOW 41 { "NOW", V_NOW, NO_OPTIONS }, #define V_TOTALSERVERS 42 { "TOTALSERVERS", V_TOTALSERVERS, NO_OPTIONS }, #define V_TOTALUP 43 { "TOTALUP", V_TOTALUP, NO_OPTIONS }, #define V_TOTALNOTUP 44 { "TOTALNOTUP", V_TOTALNOTUP, NO_OPTIONS }, #define V_TOTALPLAYERS 45 { "TOTALPLAYERS", V_TOTALPLAYERS, NO_OPTIONS }, #define V_TOTALMAXPLAYERS 46 { "TOTALMAXPLAYERS", V_TOTALMAXPLAYERS, NO_OPTIONS }, #define V_TEAMNUM 49 { "TEAMNUM", V_TEAMNUM, NO_OPTIONS }, #define V_BACKSLASH 50 { "\\", V_BACKSLASH, NO_OPTIONS }, #define V_HOSTNOTFOUND 51 { "HOSTNOTFOUND", V_HOSTNOTFOUND, NO_OPTIONS | EXPR }, #define V_MESH 52 { "MESH", V_MESH, NO_OPTIONS }, #define V_ISEMPTY 56 { "ISEMPTY", V_ISEMPTY, NO_OPTIONS | EXPR }, #define V_ISFULL 57 { "ISFULL", V_ISFULL, NO_OPTIONS | EXPR }, #define V_PACKETLOSS 58 { "PACKETLOSS", V_PACKETLOSS, NO_OPTIONS }, #define V_ISTEAM 59 { "ISTEAM", V_ISTEAM, NO_OPTIONS | EXPR }, #define V_TEAMNAME 60 { "TEAMNAME", V_TEAMNAME, NO_OPTIONS }, #define V_DEFAULTTYPE 61 { "DEFAULTTYPE", V_DEFAULTTYPE, NO_OPTIONS }, #define V_TYPESTRING 62 { "TYPESTRING", V_TYPESTRING, NO_OPTIONS }, #define V_FACE 63 { "FACE", V_FACE, NO_OPTIONS }, #define V_SOLDIEROFFORTUNE 64 { "SOLDIEROFFORTUNE", V_SOLDIEROFFORTUNE, NO_OPTIONS }, #define V_COLORNUMBERS 65 { "COLORNUMBERS", V_COLORNUMBERS, NO_OPTIONS }, #define V_COLORNAMES 66 { "COLORNAMES", V_COLORNAMES, NO_OPTIONS }, #define V_COLORRGB 67 { "COLORRGB", V_COLORRGB, NO_OPTIONS }, #define V_TIMESECONDS 68 { "TIMESECONDS", V_TIMESECONDS, NO_OPTIONS }, #define V_TIMECLOCK 69 { "TIMECLOCK", V_TIMECLOCK, NO_OPTIONS }, #define V_TIMESTOPWATCH 70 { "TIMESTOPWATCH", V_TIMESTOPWATCH, NO_OPTIONS }, #define V_NOWINT 71 { "NOWINT", V_NOWINT, NO_OPTIONS }, #define V_ISMASTER 72 { "ISMASTER", V_ISMASTER, NO_OPTIONS | EXPR }, #define V_HTMLPLAYERNAME 73 { "HTMLPLAYERNAME", V_HTMLPLAYERNAME, NO_OPTIONS }, #define V_ISBOT 74 { "ISBOT", V_ISBOT, NO_OPTIONS | EXPR }, #define V_ISALIAS 75 { "ISALIAS", V_ISALIAS, NO_OPTIONS | EXPR }, #define V_TRIBETAG 76 { "TRIBETAG", V_TRIBETAG, NO_OPTIONS | EXPR }, #define V_CLEARNEWLINES 77 { "CLEARNEWLINES", V_CLEARNEWLINES, NO_OPTIONS }, #define V_GAMETYPE 78 { "GAMETYPE", V_GAMETYPE, NO_OPTIONS }, #define V_DEATHS 79 { "DEATHS", V_DEATHS, NO_OPTIONS | EXPR }, #define V_TYPEPREFIX 80 { "TYPEPREFIX", V_TYPEPREFIX, NO_OPTIONS }, #define V_RULENAMESPACES 81 { "RULENAMESPACES", V_RULENAMESPACES, NO_OPTIONS }, #define V_RULETEMPLATE 82 { "RULETEMPLATE", V_RULETEMPLATE, NO_OPTIONS }, #define V_RULENAME 83 { "RULENAME", V_RULENAME, OPTIONS_OK | EXPR }, #define V_RULEVALUE 84 { "RULEVALUE", V_RULEVALUE, OPTIONS_OK | EXPR }, #define V_PLAYERSTATID 85 { "PLAYERSTATID", V_PLAYERSTATID, NO_OPTIONS }, #define V_PLAYERLEVEL 86 { "PLAYERLEVEL", V_PLAYERLEVEL, NO_OPTIONS }, #define V_TOTALUTILIZATION 87 { "TOTALUTILIZATION", V_TOTALUTILIZATION, NO_OPTIONS }, #define V_SCORE 88 { "SCORE", V_SCORE, NO_OPTIONS }, }; int read_qserver_template(char *filename) { return (read_template(filename, &server_template)); } int read_rule_template(char *filename) { return (read_template(filename, &rule_template)); } int read_header_template(char *filename) { return (read_template(filename, &header_template)); } int read_trailer_template(char *filename) { return (read_template(filename, &trailer_template)); } int read_player_template(char *filename) { return (read_template(filename, &player_template)); } STATIC int read_template(char *filename, char **template_text) { FILE *file; int length, rc; file = fopen(filename, "r"); if (file == NULL) { perror(filename); return (-1); } fseek(file, 0, SEEK_END); length = ftell(file); fseek(file, 0, SEEK_SET); *template_text = (char *)malloc(length + 1); rc = fread(*template_text, 1, length, file); if ((rc == 0) && (length > 0)) { perror(filename); fclose(file); free(*template_text); *template_text = NULL; return (-1); } (*template_text)[rc] = '\0'; return (0); } int have_server_template() { return (server_template != NULL); } int have_header_template() { return (header_template != NULL); } int have_trailer_template() { return (trailer_template != NULL); } void template_display_server(struct qserver *server) { char *t = server_template; int var, varlen; if_level = 0; if_skip = 0; for ( ; *t; t++) { if (*t != VARIABLE_CHAR) { if (!if_skip) { putc(*t, OF); } continue; } var = parse_var(t, &varlen); if (var == -1) { if (!if_skip) { putc(VARIABLE_CHAR, OF); } continue; } if (var == V_BACKSLASH) { t += 2; if (*t == '\r') { if (*++t == '\n') { t++; } } else if (*t == '\n') { t++; } t--; continue; } if (((var == V_IF) || (var == V_IFNOT)) && (variable_option != NULL)) { int truth = (var == V_IF) ? 1 : 0; if (!if_skip && (is_true(server, NULL, NULL, variable_option) == truth)) { if_level++; } else { if_skip++; } } else if (var == V_ENDIF) { if (if_skip) { if_skip--; } else if (if_level) { if_level--; } } if (!if_skip) { display_server_var(server, var); } t += varlen; } } void template_display_players(struct qserver *server) { struct player *player; for (player = server->players; player != NULL; player = player->next) { template_display_player(server, player); } } void template_display_player(struct qserver *server, struct player *player) { char *t = player_template; int var, varlen; if (player_template == NULL) { return; } if_level = 0; if_skip = 0; for ( ; *t; t++) { if (*t != VARIABLE_CHAR) { if (!if_skip) { putc(*t, OF); } continue; } var = parse_var(t, &varlen); if (var == -1) { if (!if_skip) { putc(VARIABLE_CHAR, OF); } continue; } if (var == V_BACKSLASH) { t += 2; if (*t == '\r') { if (*++t == '\n') { t++; } } else if (*t == '\n') { t++; } t--; continue; } if (((var == V_IF) || (var == V_IFNOT)) && (variable_option != NULL)) { int truth = (var == V_IF) ? 1 : 0; if (!if_skip && (is_true(server, player, NULL, variable_option) == truth)) { if_level++; } else { if_skip++; } } else if (var == V_ENDIF) { if (if_skip) { if_skip--; } else if (if_level) { if_level--; } } if (!if_skip) { display_player_var(player, var, server); } t += varlen; } } void template_display_rules(struct qserver *server) { struct rule *rule; for (rule = server->rules; rule != NULL; rule = rule->next) { template_display_rule(server, rule); } } void template_display_rule(struct qserver *server, struct rule *rule) { char *t = rule_template; int var, varlen; if (rule_template == NULL) { return; } if_level = 0; if_skip = 0; for ( ; *t; t++) { if (*t != VARIABLE_CHAR) { if (!if_skip) { putc(*t, OF); } continue; } var = parse_var(t, &varlen); if (var == -1) { if (!if_skip) { putc(VARIABLE_CHAR, OF); } continue; } if (var == V_BACKSLASH) { t += 2; if (*t == '\r') { if (*++t == '\n') { t++; } } else if (*t == '\n') { t++; } t--; continue; } if (((var == V_IF) || (var == V_IFNOT)) && (variable_option != NULL)) { int truth = (var == V_IF) ? 1 : 0; if (!if_skip && (is_true(server, NULL, rule, variable_option) == truth)) { if_level++; } else { if_skip++; } } else if (var == V_ENDIF) { if (if_skip) { if_skip--; } else if (if_level) { if_level--; } } if (!if_skip) { display_rule_var(rule, var, server); } t += varlen; } } void template_display_header() { char *t = header_template; int var, varlen; if_level = 0; if_skip = 0; for ( ; *t; t++) { if (*t != VARIABLE_CHAR) { putc(*t, OF); continue; } var = parse_var(t, &varlen); if (var == -1) { putc(VARIABLE_CHAR, OF); continue; } if (var == V_BACKSLASH) { t += 2; if (*t == '\r') { if (*++t == '\n') { t++; } } else if (*t == '\n') { t++; } t--; continue; } display_generic_var(var); t += varlen; } } void template_display_trailer() { char *t = trailer_template; int var, varlen; if_level = 0; if_skip = 0; for ( ; *t; t++) { if (*t != VARIABLE_CHAR) { putc(*t, OF); continue; } var = parse_var(t, &varlen); if (var == -1) { putc(VARIABLE_CHAR, OF); continue; } if (var == V_BACKSLASH) { t += 2; if (*t == '\r') { if (*++t == '\n') { t++; } } else if (*t == '\n') { t++; } t--; continue; } display_generic_var(var); t += varlen; } } STATIC void display_server_var(struct qserver *server, int var) { char *game; int full_data = 1; if ((server->server_name == DOWN) || (server->server_name == TIMEOUT) || (server->server_name == SYSERROR) || (server->error != NULL) || server->type->master) { full_data = 0; } switch (var) { case V_HOSTNAME: fputs((hostname_lookup) ? server->host_name : server->arg, OF); break; case V_SERVERNAME: fputs(xform_name(server->server_name, server), OF); break; case V_PING: if ((server->server_name != TIMEOUT) && (server->server_name != DOWN) && (server->server_name != HOSTNOTFOUND) && (server->server_name != SYSERROR)) { fprintf(OF, "%d", server->n_requests ? server->ping_total / server->n_requests : 999); } break; case V_PLAYERS: if (full_data) { fprintf(OF, "%d", server->num_players); } break; case V_MAXPLAYERS: if (full_data) { fprintf(OF, "%d", server->max_players); } break; case V_MAP: if (full_data) { fputs((server->map_name) ? server->map_name : "?", OF); } break; case V_GAME: if (full_data) { game = get_qw_game(server); fputs((game) ? game : "", OF); } break; case V_GAMETYPE: { struct rule *rule; for (rule = server->rules; rule != NULL; rule = rule->next) { if (strcasecmp(rule->name, "g_gametype") == 0) { break; } } if (rule != NULL) { switch (atoi(rule->value)) { case 0: fputs("Free For All", OF); break; case 1: fputs("Tournament", OF); break; case 3: fputs("Team Deathmatch", OF); break; case 4: fputs("Capture the Flag", OF); break; case 5: fputs("Fortress or OSP", OF); break; case 6: fputs("Capture and Hold", OF); break; case 8: fputs("Arena", OF); break; default: fputs("?", OF); break; } } } break; case V_RETRIES: if ((server->server_name != TIMEOUT) && (server->server_name != DOWN) && (server->server_name != HOSTNOTFOUND)) { fprintf(OF, "%d", server->n_retries); } break; case V_IPADDR: { unsigned int ipaddr = ntohl(server->ipaddr); fprintf(OF, "%u.%u.%u.%u", (ipaddr >> 24) & 0xff, (ipaddr >> 16) & 0xff, (ipaddr >> 8) & 0xff, ipaddr & 0xff); } break; case V_PORT: fprintf(OF, "%hu", server->port); break; case V_ARG: fputs(server->arg, OF); break; case V_TYPE: fputs(server->type->game_name, OF); break; case V_TYPESTRING: fputs(server->type->type_string, OF); break; case V_TYPEPREFIX: fputs(server->type->type_prefix, OF); break; case V_RULE: { struct rule *rule; if (variable_option == NULL) { break; } for (rule = server->rules; rule != NULL; rule = rule->next) { if (strcasecmp(rule->name, variable_option) == 0) { display_string(rule->value); } } } break; case V_ALLRULES: { struct rule *rule; for (rule = server->rules; rule != NULL; rule = rule->next) { if (rule != server->rules) { fputs(", ", OF); } display_string(rule->name); putc('=', OF); display_string(rule->value); } } break; case V_PLAYERTEMPLATE: if_level_save = if_level; if_skip_save = if_skip; template_display_players(server); if_level = if_level_save; if_skip = if_skip_save; break; case V_RULETEMPLATE: if_level_save = if_level; if_skip_save = if_skip; template_display_rules(server); if_level = if_level_save; if_skip = if_skip_save; break; default: display_generic_var(var); } } STATIC void display_player_var(struct player *player, int var, struct qserver *server) { switch (var) { case V_PLAYERNAME: fputs(xform_name(player->name, server), OF); break; case V_HTMLPLAYERNAME: { int save_html_names = xform_html_names; int save_hex_player_names = xform_hex_player_names; xform_html_names = 1; xform_hex_player_names = 0; fputs(xform_name(player->name, server), OF); xform_html_names = save_html_names; xform_hex_player_names = save_hex_player_names; } break; case V_TRIBETAG: fputs(xform_name(player->tribe_tag, server), OF); break; case V_FRAGS: fprintf(OF, "%d", player->frags); break; case V_SCORE: fprintf(OF, "%d", player->score); break; case V_DEATHS: fprintf(OF, "%d", player->deaths); break; case V_PLAYERPING: fprintf(OF, "%d", player->ping); break; case V_CONNECTTIME: fputs(play_time(player->connect_time, 0), OF); break; case V_SKIN: display_string(player->skin); break; case V_MESH: display_string(player->mesh); break; case V_FACE: display_string(player->face); break; case V_SHIRTCOLOR: if (color_names) { fputs(quake_color(player->shirt_color), OF); } else { fprintf(OF, "%d", player->shirt_color); } break; case V_PANTSCOLOR: if (color_names) { fputs(quake_color(player->pants_color), OF); } else { fprintf(OF, "%d", player->pants_color); } break; case V_PLAYERIP: if (player->address) { fputs(player->address, OF); } break; case V_TEAMNUM: fprintf(OF, "%d", player->team); break; case V_PACKETLOSS: fprintf(OF, "%d", player->packet_loss); break; case V_TEAMNAME: if (player->team_name) { fprintf(OF, "%s", player->team_name); } break; case V_COLORNUMBERS: color_names = 0; break; case V_COLORNAMES: color_names = 1; break; case V_COLORRGB: color_names = 2; break; case V_TIMESECONDS: time_format = SECONDS; break; case V_TIMECLOCK: time_format = CLOCK_TIME; break; case V_TIMESTOPWATCH: time_format = STOPWATCH_TIME; break; case V_PLAYERSTATID: case V_PLAYERLEVEL: fprintf(OF, "%u", player->ship); break; default: display_server_var(server, var); } } STATIC void display_rule_var(struct rule *rule, int var, struct qserver *server) { switch (var) { case V_RULENAME: fputs(rule->name, OF); break; case V_RULEVALUE: fputs(xform_name(rule->value, server), OF); break; default: display_server_var(server, var); } } STATIC void display_generic_var(int var) { switch (var) { case V_QSTATURL: fputs("http://www.qstat.org", OF); break; case V_QSTATVERSION: fputs(qstat_version, OF); break; case V_QSTATAUTHOR: fputs("Steve Jankowski", OF); break; case V_QSTATAUTHOREMAIL: fputs("steve@qstat.org", OF); break; case V_HTML: html_mode ^= 1; if (html_mode && (xform_html_names == -1)) { xform_html_names = 1; } break; case V_CLEARNEWLINES: clear_newlines_mode ^= 1; break; case V_NOW: { time_t now = time(0); char *now_string = ctime(&now); now_string[strlen(now_string) - 1] = '\0'; fputs(now_string, OF); break; } case V_NOWINT: fprintf(OF, "%u", (unsigned int)time(0)); break; case V_TOTALSERVERS: fprintf(OF, "%d", num_servers_total); break; case V_TOTALUP: fprintf(OF, "%d", num_servers_total - num_servers_timed_out - num_servers_down); break; case V_TOTALNOTUP: fprintf(OF, "%d", num_servers_timed_out + num_servers_down); break; case V_TOTALPLAYERS: fprintf(OF, "%d", num_players_total); break; case V_TOTALMAXPLAYERS: fprintf(OF, "%d", max_players_total); break; case V_TOTALUTILIZATION: fprintf(OF, "%.1f", (100.0 / max_players_total) * num_players_total); break; case V_DEFAULTTYPE: fprintf(OF, "%s", default_server_type->game_name); break; case V_RULENAMESPACES: rule_name_spaces ^= 1; break; default: break; } } STATIC int parse_var(char *varname, int *varlen) { char *v = ++varname, *colon = NULL; int i, quote = 0; if (variable_option != NULL) { free(variable_option); variable_option = NULL; } if (*v == '(') { v++; varname++; quote++; } else if (*v == '\\') { *varlen = 1; return (V_BACKSLASH); } for ( ; *v; v++) { if ((!quote && !isalpha((unsigned char)*v)) || (quote && ((*v == ')') || (*v == ':')))) { break; } } if (v - varname == 0) { return (-1); } *varlen = v - varname; if (*v == ':') { colon = v; } else if (quote && (*v == ')')) { v++; } for (i = 0; i < sizeof(variable_defs) / sizeof(struct vardef); i++) { if ((strncasecmp(varname, variable_defs[i].var, *varlen) == 0) && (*varlen == strlen(variable_defs[i].var))) { if ((colon != NULL) && ((variable_defs[i].options & OPTIONS_OK) || quote)) { for (v++; *v; v++) { if ((!quote && !isalnum((unsigned char)*v) && (*v != '*') && (*v != '_') && (*v != '.') && ((*v == ' ') && !rule_name_spaces)) || ((quote == 1) && (*v == ')'))) { break; } if (*v == '(') { quote++; } else if (*v == ')') { quote--; } } variable_option = (char *)malloc(v - colon + 1); strncpy(variable_option, colon + 1, v - colon - 1); variable_option[v - colon - 1] = '\0'; if (quote && (*v == ')')) { v++; } } *varlen = v - varname + quote; return (variable_defs[i].varcode); } } return (-1); } STATIC int is_true(struct qserver *server, struct player *player, struct rule *rule, char *expr) { int i, len = 0, arglen = 0; char *arg = NULL, *lparen, *rparen; server_type *t; if ((lparen = strchr(expr, '(')) != NULL) { if ((rparen = strchr(lparen, ')')) != NULL) { len = lparen - expr; arg = lparen + 1; arglen = rparen - lparen - 1; } } else { len = strlen(expr); } for (i = 0; i < sizeof(variable_defs) / sizeof(struct vardef); i++) { if ((strncasecmp(expr, variable_defs[i].var, len) == 0) && (len == strlen(variable_defs[i].var))) { if (!(variable_defs[i].options & EXPR)) { fprintf(stderr, "unsupported IF expression \"%s\"\n", expr); return (1); } switch (variable_defs[i].varcode) { case V_GAME: { char *g = get_qw_game(server); if ((g == NULL) || (*g == '\0')) { return (0); } else { return (1); } } case V_PLAYERS: return (server->num_players > 0); case V_ISEMPTY: return (server->num_players == 0); case V_ISFULL: return (server->max_players ? server->num_players >= server->max_players : 0); case V_ISTEAM: return (player ? player->number == TRIBES_TEAM : 0); case V_ISBOT: return (player ? player->type_flag == PLAYER_TYPE_BOT : 0); case V_ISALIAS: return (player ? player->type_flag == PLAYER_TYPE_ALIAS : 0); case V_TRIBETAG: return (player ? player->tribe_tag != NULL : 0); case V_RULE: { struct rule *rule; if (arg == NULL) { return (0); } for (rule = server->rules; rule != NULL; rule = rule->next) { if ((strncasecmp(rule->name, arg, arglen) == 0) && (strlen(rule->name) == arglen)) { return (1); } } return (0); } case V_RULENAME: if (rule && arg && (strncmp(rule->name, arg, arglen) == 0) && (strlen(rule->name) == arglen)) { return (1); } else { return (0); } case V_RULEVALUE: if (rule && arg && (strncmp(rule->value, arg, arglen) == 0) && (strlen(rule->value) == arglen)) { return (1); } else { return (0); } case V_FLAG: if (strncmp("-H", arg, arglen) == 0) { return (hostname_lookup); } if (strncmp("-P", arg, arglen) == 0) { return (get_player_info); } if (strncmp("-R", arg, arglen) == 0) { return (get_server_rules); } return (0); case V_UP: return (server->server_name != DOWN && server->server_name != TIMEOUT && server->server_name != HOSTNOTFOUND); case V_DOWN: return (server->server_name == DOWN); case V_TIMEOUT: return (server->server_name == TIMEOUT); case V_HOSTNOTFOUND: return (server->server_name == HOSTNOTFOUND); case V_ISMASTER: return ((server->type->id & MASTER_SERVER) ? 1 : 0); case V_DEATHS: return (player->deaths); default: return (0); } } } t = &types[0]; for ( ; t->id; t++) { if (strncasecmp(expr, t->template_var, len) == 0) { return (server->type->id == t->id); } } fprintf(stderr, "bad IF expression \"%s\"\n", expr); return (0); } STATIC void display_string(char *str) { if (str == NULL) { return; } if (!html_mode && !clear_newlines_mode) { fputs(str, OF); return; } if (html_mode && !clear_newlines_mode) { for ( ; *str; str++) { switch (*str) { case '<': fputs("<", OF); break; case '>': fputs(">", OF); break; case '&': fputs("&", OF); break; case '\n': fputs("NEWLINE", OF); default: putc(*str, OF); } } return; } if (!html_mode && clear_newlines_mode) { for ( ; *str; str++) { switch (*str) { case '\n': case '\r': putc(' ', OF); break; default: putc(*str, OF); } } return; } for ( ; *str; str++) { switch (*str) { case '<': fputs("<", OF); break; case '>': fputs(">", OF); break; case '&': fputs("&", OF); break; case '\n': case '\r': putc(' ', OF); break; default: putc(*str, OF); } } } qstat-2.17/template/000077500000000000000000000000001412457473700144305ustar00rootroot00000000000000qstat-2.17/template/Makefile.am000066400000000000000000000000461412457473700164640ustar00rootroot00000000000000EXTRA_DIST = $(wildcard *.html *.txt) qstat-2.17/template/README.txt000066400000000000000000000017001412457473700161240ustar00rootroot00000000000000This directory contains the output templates used for the old broccoli server status page. ** The broccoli servers are no longer running. You should substitute ** your own server addresses into 'broc.lst'. brocTh.html header template brocTp.html player template brocTs.html server template brocTt.html trailer template broc.lst server list To generate an HTML page for the broccoli servers: qstat -P -f broc.lst -Ts brocTs.html -Th brocTh.html -Tt brocTt.html -Tp brocTp.html -sort g -of qservers.html The HTML output will be put in "qservers.html". I've also included some templates for Unreal/UT. They can be used like this: qstat -P -R -f unreal.lst -Th unrealTh.html -Tp unrealTp.html -Ts unrealTs.html -Tt unrealTt.html -sort g -of unreal.html The HTML output will be put in "unreal.html". qstat -P -R -f tribes2.lst -Th tribes2th.html -Tp tribes2tp.html -Ts tribes2ts.html -Tt tribes2tt.html -sort g -of tribes2.html qstat-2.17/template/brocTh.html000066400000000000000000000014041412457473700165360ustar00rootroot00000000000000$HTML broccoli Quake 2 servers

    broccoli Quake 2 servers

    Last update: $NOW

    The Solaris version of Quake II v 3.15 (soon 3.17) is not yet available. As soon as I can get my hands on the bits, I'll be running upgrading the server.

    I run $TOTALSERVERS servers on a Sun Ultra 2 running Solaris 2.5.1. The machine has two CPUs, so performance is good even when I'm using it for work. Enjoy!

    qstat-2.17/template/brocTp.html000066400000000000000000000001531412457473700165460ustar00rootroot00000000000000 qstat-2.17/template/brocTs.html000066400000000000000000000010231412457473700165460ustar00rootroot00000000000000 qstat-2.17/template/brocTt.html000066400000000000000000000002171412457473700165530ustar00rootroot00000000000000
    Server Name Players Map Game Address
      $PLAYERNAME $FRAGS $PLAYERPING
    $SERVERNAME $(IFNOT:QWMASTER)$PLAYERS/$MAXPLAYERS$(ENDIF) $MAP $GAME $HOSTNAME $(IF:PLAYERS)$(IF:FLAG(-P))
    $PLAYERTEMPLATE
      Player Name Frags Ping  
    $ENDIF$ENDIF
     


    $TOTALPLAYERS players on $TOTALUP servers.

    Created with QStat $QSTATVERSION

    qstat-2.17/template/ghostrecon.lst000066400000000000000000000000771412457473700173330ustar00rootroot00000000000000grs 208.144.248.104 grs,ignoreserverplayer=yes 208.144.248.105 qstat-2.17/template/ghostreconTh.html000066400000000000000000000004501412457473700177640ustar00rootroot00000000000000$HTML Ghost Recon Servers

    Ghost Recon Servers

    Last update: $NOW

    Example of Ghost Recon support in QStat.

    qstat-2.17/template/ghostreconTp.html000066400000000000000000000002611412457473700177740ustar00rootroot00000000000000 $PLAYERNAME $TEAMNAME $(IF:DEATHS)Dead$(ENDIF)$(IFNOT:DEATHS)Alive$(ENDIF)   qstat-2.17/template/ghostreconTs.html000066400000000000000000000064041412457473700200040ustar00rootroot00000000000000
    Server Name Players Map Mission  
    $SERVERNAME $PLAYERS/$MAXPLAYERS $MAP $(RULE:mission)  
    Game Mode Mission Type Dedicated Server Ip Addr  
    $(RULE:gamemode) $(RULE:missiontype) $(RULE:dedicated) $IPADDR:$PORT  
    Status Time Limit Time Played Remaining Time  
    $(RULE:status) $(RULE:gametime) $(RULE:timeplayed) $(RULE:remainingtime)  
    Version Spawn Type Spawn Count Restrictions  
    $(RULE:version) $(RULE:spawntype) $(RULE:spawncount) $(RULE:restrict)  
    Password Threat Indicator Patch Level Observing  
    $(RULE:password) $(RULE:ti) $(RULE:patch) $(RULE:allowobservers)  
    Start Timer Start Time Value Time untill Start Debrief Time  
    $(RULE:usestarttime) $(RULE:starttimeset) $(RULE:startwait) $(RULE:debrieftime)  
    Respawn Minimum Respawn Maximum Respawn Safe IFF Mode  
    $(RULE:respawnmin) $(RULE:respawnmax) $(RULE:respawnsafe) $(RULE:iff)  
    MOTD  
    $(RULE:motd)  
    Mods  
    $GAME  
    $(IF:PLAYERS)$(IF:FLAG(-P)) $PLAYERTEMPLATE
    Player Name Team Health  
    $ENDIF$ENDIF

    qstat-2.17/template/ghostreconTt.html000066400000000000000000000002171412457473700200010ustar00rootroot00000000000000

    $TOTALPLAYERS players on $TOTALUP servers.

    Created with QStat $QSTATVERSION

    qstat-2.17/template/tribes2th.html000066400000000000000000000007531412457473700172310ustar00rootroot00000000000000$HTML Tribes 2 Servers

    Tribes 2 Servers

    Last update: $NOW

    Example of Tribes 2 support in QStat.

    qstat-2.17/template/tribes2tp.html000066400000000000000000000007351412457473700172410ustar00rootroot00000000000000$HTMLPLAYERNAME $(IF:ISBOT)Bot$(ENDIF)$(IF:ISALIAS)Alias$(ENDIF)$(IF:ISTEAM) $ENDIF $(IF:TRIBETAG)$TRIBETAG$(ENDIF)$(IF:ISTEAM) $ENDIF   qstat-2.17/template/tribes2ts.html000066400000000000000000000013401412457473700172350ustar00rootroot00000000000000$(IFNOT:ISMASTER) $ENDIF qstat-2.17/template/tribes2tt.html000066400000000000000000000002171412457473700172400ustar00rootroot00000000000000
    Server Name Players Mission Map Address
      $FRAGS $TEAMNAME$(IF:ISTEAM)TEAM$ENDIF
    $SERVERNAME $PLAYERS/$MAXPLAYERS $RULE:mission $MAP $HOSTNAME $(IF:RULE(info))
    $(RULE:info)
    $(ENDIF) $(IF:PLAYERS)$(IF:FLAG(-P))
    $PLAYERTEMPLATE
      Player Name Type Tribe Score   Team  
    $ENDIF$ENDIF
     


    $TOTALPLAYERS players on $TOTALUP servers.

    Created with QStat $QSTATVERSION

    qstat-2.17/template/unrealTh.html000066400000000000000000000006661412457473700171100ustar00rootroot00000000000000$HTML Unreal Servers

    Unreal Servers

    Last update: $NOW

    Example of Unreal support in QStat.

    qstat-2.17/template/unrealTp.html000066400000000000000000000001771412457473700171150ustar00rootroot00000000000000 qstat-2.17/template/unrealTs.html000066400000000000000000000012321412457473700171110ustar00rootroot00000000000000 qstat-2.17/template/unrealTt.html000066400000000000000000000002171412457473700171140ustar00rootroot00000000000000
    Server Name Players Map Address
      $PLAYERNAME $FRAGS $SKIN $TEAMNUM
    $SERVERNAME $(IFNOT:QWMASTER)$PLAYERS/$MAXPLAYERS$(ENDIF) $MAP $HOSTNAME $(IF:RULE(AdminEMail)) $(RULE:AdminEMail)$(ENDIF) $(IF:PLAYERS)$(IF:FLAG(-P))
    $PLAYERTEMPLATE
      Player Name Frags Skin Team  
    $ENDIF$ENDIF
     


    $TOTALPLAYERS players on $TOTALUP servers.

    Created with QStat $QSTATVERSION

    qstat-2.17/terraria.c000066400000000000000000000105421412457473700145740ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * Terraria / TShock query protocol * Copyright 2012 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms * */ #include #ifndef _WIN32 #include #include #include #define strtok_ret strtok_r #else #include #define strtok_ret strtok_s #endif #include #include #include #include "debug.h" #include "qstat.h" #include "packet_manip.h" query_status_t send_terraria_request_packet(struct qserver *server) { return (send_packet(server, server->type->status_packet, server->type->status_len)); } query_status_t deal_with_terraria_packet(struct qserver *server, char *rawpkt, int pktlen) { char *s, *key, *val, *sep, *linep, *varp; int complete_response = 0; char last_char; unsigned short port = 0; debug(2, "processing..."); if (0 == pktlen) { // Disconnect? return (REQ_ERROR); } complete_response = (0 == strncmp("HTTP/1.1 200", rawpkt, 12) && '}' == rawpkt[pktlen - 1]) ? 1 : 0; last_char = rawpkt[pktlen - 1]; rawpkt[pktlen - 1] = '\0'; debug(3, "packet: combined = %d, complete = %d", server->combined, complete_response); if (!complete_response) { if (!server->combined) { // response fragment recieved int pkt_id; int pkt_max; server->retry1 = n_retries; if (0 == server->n_requests) { server->ping_total = time_delta(&packet_recv_time, &server->packet_time1); server->n_requests++; } // We're expecting more to come debug(5, "fragment recieved..."); pkt_id = packet_count(server); pkt_max = pkt_id + 1; rawpkt[pktlen - 1] = last_char; // restore the last character if (!add_packet(server, 0, pkt_id, pkt_max, pktlen, rawpkt, 1)) { // fatal error e.g. out of memory return (MEM_ERROR); } if (0 == pkt_id) { return (INPROGRESS); } // combine_packets will call us recursively return (combine_packets(server)); } // recursive call which is still incomplete return (INPROGRESS); } // find the end of the headers s = strstr(rawpkt, "\x0d\x0a\x0d\x0a"); if (NULL == s) { debug(1, "Error: missing end of headers"); return (REQ_ERROR); } s += 4; // Correct ping // Not quite right but gives a good estimate server->ping_total = (server->ping_total * server->n_requests) / 2; debug(3, "processing response..."); s = strtok_ret(s, "\x0d\x0a", &linep); // NOTE: id=XXX and msg=XXX will be processed by the mod following the one they where the response of while (NULL != s) { debug(2, "LINE: %s\n", s); if (0 == strcmp(s, "{")) { s = strtok_ret(NULL, "\x0d\x0a", &linep); continue; } s = strtok_ret(s, "\"", &varp); key = strtok_ret(NULL, "\"", &varp); sep = strtok_ret(NULL, " ", &varp); val = strtok_ret(NULL, "\"", &varp); if (NULL == val) { // world etc may be empty which results in NULL val s = strtok_ret(NULL, "\x0d\x0a", &linep); continue; } //if ( NULL == val && sep debug(2, "var: '%s' = '%s', sep: '%s'\n", key, val, sep); if (0 == strcmp(key, "name")) { server->server_name = strdup(val); } else if (0 == strcmp(key, "port")) { port = atoi(val); change_server_port(server, port, 0); add_rule(server, key, val, NO_FLAGS); } else if (0 == strcmp(key, "playercount")) { server->num_players = atoi(val); } else if (0 == strcmp(key, "maxplayers")) { server->max_players = atoi(val); } else if (0 == strcmp(key, "world")) { server->map_name = strdup(val); } else if (0 == strcmp(key, "players")) { // Players are ", " seperated but potentially player names can have spaces // so we manually check for the leading space and fix if found char *playersp; char *player_name = strtok_ret(val, ",", &playersp); while (NULL != player_name) { struct player *player = add_player(server, server->n_player_info); if (NULL != player) { if (' ' == player_name[0]) { player_name++; } player->name = strdup(player_name); debug(4, "Player: %s\n", player->name); } player_name = strtok_ret(NULL, ",", &playersp); } } else if (0 == strcmp(key, "status")) { if (200 != atoi(val)) { server->server_name = DOWN; return (DONE_FORCE); } } else { add_rule(server, key, val, NO_FLAGS); } s = strtok_ret(NULL, "\x0d\x0a", &linep); } gettimeofday(&server->packet_time1, NULL); return (DONE_FORCE); } qstat-2.17/terraria.h000066400000000000000000000007021412457473700145760ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * Terraria / TShock protocol * Copyright 2012 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_TERRARIA_H #define QSTAT_TERRARIA_H #include "qserver.h" // Packet processing methods query_status_t deal_with_terraria_packet(struct qserver *server, char *pkt, int pktlen); query_status_t send_terraria_request_packet(struct qserver *server); #endif qstat-2.17/tests/000077500000000000000000000000001412457473700137575ustar00rootroot00000000000000qstat-2.17/tests/etqw_1.4_test1000066400000000000000000000020241412457473700163620ustar00rootroot00000000000000ÿÿinfoResponseÿÿÿÿ õsi_teamForceBalance0si_readyPercent51si_versionETQW 1.4.12335.12335 linux-x86 Jan 15 2008 12:40:30fs_gamebasenet_serverPunkbusterEnabled1net_serverDedicated1si_maxplayers24si_gameReviewReadyWait0si_disableGlobalChat0si_noProficiency0si_allowLateJoin1si_minPlayers2si_disableVoting0si_adminStart0si_motd_4Hallo Luxxer ;)si_motd_3Wir suchen Membersi_motd_2^2#^pmg-42^2.^pwarsport^1ssi_motd_1welcome tosi_irc#mg-42si_emailleader@warsports.desi_adminname^2#^pmg-42^2|^petplayer^1.si_websitewww.mg-42.desi_timelimit30.000000si_rulessdGameRulesStopWatchsi_spectators1si_pure1si_needPass1si_teamDamage1si_privateClients0si_name^2Teh Hogbot_enable1gamenamebaseETQW-1si_antiLagForgiving0si_antiLagOnly0si_antiLag1si_mapmaps/valley.entities^9Crap^7Shoot^1-Monkey BaezLEIGE_AMistaaytchBFGW1ll1t5Mr. Williams^1Doom ^7MachineKillMe ^1Demona Berrett ^3buttes qstat-2.17/tests/etqw_1.4_test2000066400000000000000000000024601412457473700163670ustar00rootroot00000000000000ÿÿinfoResponseÿÿÿÿ si_versionETQW 1.4.12335.12335 linux-x86 Jan 15 2008 12:48:59net_serverPunkbusterEnabled1net_serverDedicated1si_maxPlayers24si_gameReviewReadyWait0si_disableGlobalChat0si_noProficiency0si_allowLateJoin1si_minPlayers6si_readyPercent51si_disableVoting0si_adminStart0si_motd_3^yGe^qrm^man ^1Ranked ^uS^werversi_motd_2^uP^wlay ^uH^ward and ^uF^wair ^q;-)si_motd_1^uW^welcome to ^uG^wame^uO^wn ETQW ^uS^werversi_teamForceBalance1si_timelimit20si_rulessdGameRulesCampaignsi_spectators1si_pure1si_needPass0si_teamDamage1si_privateClients0si_name^uG^wame^uO^wn ETQW ^yGe^qrm^many ^1RANKEDbot_enable1gamenamebaseETQW-1si_antiLagForgiving0si_antiLagOnly0si_antiLag1si_campaigncampaign_pacificsi_mapmaps/canyon.entitiessi_campaignInfoµeXa3L[^4AZ^7UR^1PIT^48^73^0(AZUR FRAGGERZ )^0bl1quid1I^pI^l5^0kulkII:^7N^9e^7crolisaMaxxxx715^0[TURK]^0VSystemOne ^hG^ei^dz^fm^bo^o93;LederhosenWar 2OmeRob ]ElokaBrig94 2Tomexix 2suCk3r^0.hjfz.^0 >^1GORELORD<^1I^9tch^1threaTs^?XR4x4:^9M^1u^9nd^1a^9S^7[^9d3v^7]^08^HruFF^*cOp!ae* | ^0ÌMylgrim m"qstat-2.17/tests/etqws.pl000077500000000000000000000021401412457473700154570ustar00rootroot00000000000000#!/usr/bin/perl -w # simple responder for doom3/quake4/etqw queries use strict; use IO::Socket::INET; use IO::File; my $sock = IO::Socket::INET->new( LocalAddr => 'localhost', Proto => 'udp'); print $sock->sockport(),"\n"; my $rin = ''; vec($rin, $sock->fileno, 1) = 1; my @files = @ARGV; die "USAGE: $0 " unless @files; sub _stat_size($) { my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size, $atime,$mtime,$ctime,$blksize,$blocks) = stat($_[0]); return $size; } sub readfile($) { my $fn = shift; print "reading $fn\n"; my $f = IO::File->new($fn, "r"); my $buf; $f->read($buf, _stat_size($f)); $f->close; return $buf; } while (select(my $rout = $rin, undef, undef, undef)) { my $data = ''; my $hispaddr; $hispaddr = $sock->recv($data, 0xffff, 0) || die "recv: $!"; my ($port, $hisiaddr) = sockaddr_in($hispaddr); printf '%d bytes from %s:%d'."\n", length($data), inet_ntoa($hisiaddr), $port; if($data !~ /^\xff\xffgetInfo/) { printf 'invalid packet from %s:%d'."\n", inet_ntoa($hisiaddr), $port; next; } my $buf = readfile($files[0]); $sock->send($buf, 0, $hispaddr); } qstat-2.17/tests/xml.pl000077500000000000000000000024221412457473700151170ustar00rootroot00000000000000#!/usr/bin/perl -w # simple qstat xml output parser, prints values specified as command # line arguments # # Author: Ludwig Nussel # # Usage Examples: # # Print server name: # "qstat/server/name" # # Print second server rule (numbered from zero): # "qstat/server/rules/rule/1" # # Print the rule with name "gamename": # "qstat/server/rules/rule/[name=gamename]" # # Print name of sixth player: # "qstat/server/players/player/5/name" # # Print clan of player with name "suCk3r": # "qstat/server/players/player/[name=suCk3r]/clan" use strict; use XML::Bare; use Data::Dumper; sub getvalue { my $x = shift; my @a = split(/\//, shift); for my $n (@a) { if ($n =~ /^[[:digit:]]+$/) { return undef unless exists $x->[$n]; $x = $x->[$n]; } elsif ($n =~ /\[(.*)=(.*)\]/) { my ($k, $v) = ($1, $2); my $r; for my $i (@$x) { next unless exists $i->{$k}; if($i->{$k}->{value} eq $v) { $r = $i; last; } } return undef unless $r; $x = $r; } else { return undef unless exists $x->{$n}; $x = $x->{$n}; } } return $x->{value}; } sub printvalue { my $val = getvalue(@_); $val = "(undefined)" unless defined $val; print $val, "\n" } my $xml = XML::Bare->new(text => join('', ))->parse(); for (@ARGV) { printvalue($xml, $_); } qstat-2.17/tf.c000066400000000000000000000267201412457473700134010ustar00rootroot00000000000000/* * qstat * * Titanfall Protocol * Copyright 2015 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms * */ #include "debug.h" #include "qstat.h" #include "utils.h" #include #ifndef _WIN32 #include #else #include #endif #include #include #define SERVERINFO_REQUEST_PAD 77 #define SERVERINFO_REQUEST 79 #define SERVERINFO_RESPONSE 80 #define SERVERINFO_VERSION 1 #define SERVERINFO_VERSION_KEYED 5 #define TEAM_IMC "imc" #define TEAM_MILITIA "militia" #define TEAM_UNKNOWN "unknown" #define SERVER_NAME "unknown" static char serverinfo_pkt[] = { 0xFF, 0xFF, 0xFF, 0xFF, SERVERINFO_REQUEST, SERVERINFO_VERSION }; static void pkt_inc(char **pkt, int *rem, int inc) { *pkt += inc; *rem -= inc; } static query_status_t pkt_string(struct qserver *server, char **pkt, int *rem, char **str, char *rule) { size_t len; if (*rem < 0) { malformed_packet(server, "short packet string"); return (PKT_ERROR); } *str = strndup(*pkt, *rem); if (*str == NULL) { malformed_packet(server, "out of memory"); return (MEM_ERROR); } len = strlen(*str) + 1; pkt_inc(pkt, rem, (int)len); if (rule != NULL) { // TODO: check return when it doesn't exit on failure add_rule(server, rule, *str, NO_VALUE_COPY); } return (INPROGRESS); } static query_status_t pkt_rule(struct qserver *server, char **pkt, int *rem, char *rule) { char *str; return (pkt_string(server, pkt, rem, &str, rule)); } static query_status_t pkt_data(struct qserver *server, char **pkt, int *rem, void *data, char *rule, int size, int isfloat) { if (*rem - size < 0) { malformed_packet(server, "short packet data"); return (PKT_ERROR); } memcpy(data, *pkt, size); pkt_inc(pkt, rem, size); if (rule != NULL) { char buf[64]; switch (size) { case 1: sprintf(buf, "%hhu", *(uint8_t*)data); break; case 2: sprintf(buf, "%hu", *(uint16_t*)data); break; case 4: if (isfloat) { sprintf(buf, "%f", *(float*)data); } else { sprintf(buf, "%" PRIu32, *(uint32_t*)data); } break; case 8: sprintf(buf, "%" PRIu64, *(uint64_t*)data); break; } add_rule(server, rule, buf, 0); } return (INPROGRESS); } static query_status_t pkt_byte(struct qserver *server, char **pkt, int *rem, uint8_t *data, char *rule) { return (pkt_data(server, pkt, rem, (void *)data, rule, sizeof(*data), 0)); } static query_status_t pkt_short(struct qserver *server, char **pkt, int *rem, uint16_t *data, char *rule) { return (pkt_data(server, pkt, rem, (void *)data, rule, sizeof(*data), 0)); } static query_status_t pkt_long(struct qserver *server, char **pkt, int *rem, uint32_t *data, char *rule) { return (pkt_data(server, pkt, rem, (void *)data, rule, sizeof(*data), 0)); } static query_status_t pkt_longlong(struct qserver *server, char **pkt, int *rem, uint64_t *data, char *rule) { return (pkt_data(server, pkt, rem, (void *)data, rule, sizeof(*data), 0)); } static query_status_t pkt_float(struct qserver *server, char **pkt, int *rem, float *data, char *rule) { return (pkt_data(server, pkt, rem, (void *)data, rule, sizeof(*data), 1)); } query_status_t send_tf_request_packet(struct qserver *server) { char *key, buf[1200]; size_t len; memset(buf, 0, sizeof(buf)); memcpy(buf, serverinfo_pkt, sizeof(serverinfo_pkt)); len = sizeof(serverinfo_pkt); if (server->type->status_packet != NULL) { // Custom packet type len = server->type->status_len; memcpy(buf, server->type->status_packet, len); if (buf[4] == SERVERINFO_REQUEST_PAD) { len = sizeof(buf); } } key = get_param_value(server, "key", NULL); if (key != NULL) { buf[5] = SERVERINFO_VERSION_KEYED; (void)strncpy(&buf[6], key, sizeof(buf) - 7); // strncpy doesn't guarantee to NULL terminate and strlcpy isn't portable. buf[sizeof(buf) - 1] = '\0'; if (len != sizeof(buf)) { len += strlen(key) + 1; } } return (qserver_send_initial(server, buf, len)); } query_status_t deal_with_tf_packet(struct qserver *server, char *rawpkt, int pktlen) { char *pkt, buf[64]; query_status_t ret; int rem; uint8_t ver, tmpu8; uint16_t port, tmpu16; uint32_t tmpu32; uint64_t tmpu64; float tmpf; rem = pktlen; pkt = rawpkt; if (server->server_name == NULL) { server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); server->n_requests++; } if (pktlen < 26) { malformed_packet(server, "packet too short pkt"); return (PKT_ERROR); } // Header (int32) if (memcmp(serverinfo_pkt, pkt, sizeof(int32_t)) != 0) { malformed_packet(server, "missing / invalid header"); return (PKT_ERROR); } pkt_inc(&pkt, &rem, sizeof(int32_t)); // Command (int8) debug(2, "TF type = %hhu", *pkt); if (server->type->status_packet != NULL) { if (*pkt != server->type->status_packet[4] + 1) { malformed_packet(server, "unknown custom type"); return (PKT_ERROR); } } else if (*pkt != SERVERINFO_RESPONSE) { malformed_packet(server, "unknown type"); return (PKT_ERROR); } pkt_inc(&pkt, &rem, sizeof(int8_t)); // Version (byte) debug(2, "TF version = %hhu", *pkt); ret = pkt_byte(server, &pkt, &rem, &ver, "version"); if (ret < 0) { return (ret); } if (ver > 1) { // Retail (byte) ret = pkt_byte(server, &pkt, &rem, &tmpu8, "retail"); if (ret < 0) { return (ret); } // Instance Type (byte) ret = pkt_byte(server, &pkt, &rem, &tmpu8, "instance_type"); if (ret < 0) { return (ret); } // Client DLL CRC (long) ret = pkt_long(server, &pkt, &rem, &tmpu32, "client_dll_crc"); if (ret < 0) { return (ret); } // Net Prototcol (short) ret = pkt_short(server, &pkt, &rem, &tmpu16, "net_protocol"); if (ret < 0) { return (ret); } // Random ServerID (long long) ret = pkt_longlong(server, &pkt, &rem, &tmpu64, "random_serverid"); if (ret < 0) { return (ret); } // Build Name (string) ret = pkt_rule(server, &pkt, &rem, "build_name"); if (ret < 0) { return (ret); } // Datacenter (string) ret = pkt_rule(server, &pkt, &rem, "datacenter"); if (ret < 0) { return (ret); } // Game Mode (string) ret = pkt_rule(server, &pkt, &rem, "game_mode"); if (ret < 0) { return (ret); } } // Port (short) ret = pkt_short(server, &pkt, &rem, &port, "port"); if (ret < 0) { return (ret); } change_server_port(server, port, 0); // Platform (string) ret = pkt_rule(server, &pkt, &rem, "platform"); if (ret < 0) { return (ret); } // Playlist Version (string) ret = pkt_rule(server, &pkt, &rem, "playlist_version"); if (ret < 0) { return (ret); } // Playlist Num (long) ret = pkt_long(server, &pkt, &rem, &tmpu32, "playlist_num"); if (ret < 0) { return (ret); } // Playlist Name (string) ret = pkt_rule(server, &pkt, &rem, "playlist_name"); if (ret < 0) { return (ret); } // Num Clients (byte) ret = pkt_byte(server, &pkt, &rem, (uint8_t *)&server->num_players, NULL); if (ret < 0) { return (ret); } // Max Clients (byte) ret = pkt_byte(server, &pkt, &rem, (uint8_t *)&server->max_players, NULL); if (ret < 0) { return (ret); } // Map Name (string) ret = pkt_string(server, &pkt, &rem, &server->map_name, NULL); if (ret < 0) { return (ret); } if (ver > 4) { ret = pkt_float(server, &pkt, &rem, &tmpf, "avg_frame_time"); if (ret < 0) { return (ret); } ret = pkt_float(server, &pkt, &rem, &tmpf, "max_frame_time"); if (ret < 0) { return (ret); } ret = pkt_float(server, &pkt, &rem, &tmpf, "avg_user_cmd_time"); if (ret < 0) { return (ret); } ret = pkt_float(server, &pkt, &rem, &tmpf, "max_user_cmd_time"); if (ret < 0) { return (ret); } } if (ver > 2) { // Phase (byte) ret = pkt_byte(server, &pkt, &rem, &tmpu8, "phase"); if (ret < 0) { return (ret); } // Max Rounds (byte) ret = pkt_byte(server, &pkt, &rem, &tmpu8, "max_rounds"); if (ret < 0) { return (ret); } // Rounds Won IMC (byte) ret = pkt_byte(server, &pkt, &rem, &tmpu8, "rounds_won_imc"); if (ret < 0) { return (ret); } // Rounds Won Militia (byte) ret = pkt_byte(server, &pkt, &rem, &tmpu8, "rounds_won_militia"); if (ret < 0) { return (ret); } // Time Limit Sec (short) ret = pkt_short(server, &pkt, &rem, &tmpu16, "time_limit_sec"); if (ret < 0) { return (ret); } // Time Passed Sec (short) ret = pkt_short(server, &pkt, &rem, &tmpu16, "time_passed_sec"); if (ret < 0) { return (ret); } // Max Score (short) ret = pkt_short(server, &pkt, &rem, &tmpu16, "max_score"); if (ret < 0) { return (ret); } // Team (byte) ret = pkt_byte(server, &pkt, &rem, &tmpu8, NULL); if (ret < 0) { return (ret); } while (tmpu8 != 255) { // Team Score (short) ret = pkt_short(server, &pkt, &rem, &tmpu16, NULL); if (ret < 0) { return (ret); } sprintf(buf, "%hu", tmpu16); switch (tmpu8) { case 2: add_rule(server, "imc_score", buf, 0); add_rule(server, "imc_teamid", "2", 0); break; case 3: add_rule(server, "militia_score", buf, 0); add_rule(server, "militia_teamid", "3", 0); break; } // Next team (byte) ret = pkt_byte(server, &pkt, &rem, &tmpu8, NULL); if (ret < 0) { return (ret); } } } // Client ID or EOP (uint64) ret = pkt_longlong(server, &pkt, &rem, &tmpu64, NULL); if (ret < 0) { return (ret); } while (tmpu64 > 0) { struct player *p; p = add_player(server, server->n_player_info); if (p == NULL) { return (SYS_ERROR); } p->flags = PLAYER_FLAG_DO_NOT_FREE_TEAM; sprintf(buf, "%" PRIu64, tmpu64); player_add_info(p, "id", buf, 0); // Client Name (string) ret = pkt_string(server, &pkt, &rem, &p->name, NULL); if (ret < 0) { return (ret); } // Client Team ID (byte) ret = pkt_byte(server, &pkt, &rem, &tmpu8, NULL); if (ret < 0) { return (ret); } p->team = tmpu8; switch (tmpu8) { case 2: p->team_name = TEAM_IMC; break; case 3: p->team_name = TEAM_MILITIA; break; default: p->team_name = TEAM_UNKNOWN; break; } sprintf(buf, "%hhu", tmpu8); player_add_info(p, "teamid", buf, 0); if (ver > 3) { // Client Address (string) ret = pkt_string(server, &pkt, &rem, &p->address, NULL); if (ret < 0) { return (ret); } // Client Ping (long) ret = pkt_long(server, &pkt, &rem, &tmpu32, NULL); if (ret < 0) { return (ret); } p->ping = tmpu32; // Client Incoming Packets Received (long) ret = pkt_long(server, &pkt, &rem, &tmpu32, NULL); if (ret < 0) { return (ret); } sprintf(buf, "%" PRIu32, tmpu32); player_add_info(p, "incoming_packets_received", buf, 0); // Client Incoming Packets Dropped (long) ret = pkt_long(server, &pkt, &rem, &tmpu32, NULL); if (ret < 0) { return (ret); } sprintf(buf, "%" PRIu32, tmpu32); player_add_info(p, "incoming_packets_dropped", buf, 0); } if (ver > 2) { // Client Score (long) ret = pkt_long(server, &pkt, &rem, &tmpu32, NULL); if (ret < 0) { return (ret); } p->score = tmpu32; // Client Kills (short) ret = pkt_short(server, &pkt, &rem, &tmpu16, NULL); if (ret < 0) { return (ret); } p->frags = tmpu16; // Client Deaths (short) ret = pkt_short(server, &pkt, &rem, &tmpu16, NULL); if (ret < 0) { return (ret); } p->deaths = tmpu16; } // Client ID or EOP (uint64) ret = pkt_longlong(server, &pkt, &rem, &tmpu64, NULL); if (ret < 0) { return (ret); } } // Protocol doesn't support server name server->server_name = strdup(SERVER_NAME); // Protocol doesn't support a specific rule request server->next_rule = NULL; return (DONE_AUTO); } // vim: sw=4 ts=4 noet qstat-2.17/tf.h000066400000000000000000000005611412457473700134010ustar00rootroot00000000000000/* * qstat * * Titafall protocol * Copyright 2015 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_TF_H #define QSTAT_TF_H #include "qserver.h" query_status_t send_tf_request_packet(struct qserver *server); query_status_t deal_with_tf_packet(struct qserver *server, char *rawpkt, int pktlen); #endif qstat-2.17/tm.c000066400000000000000000000203701412457473700134030ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * Gamespy query protocol * Copyright 2005 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms * */ #include #ifndef _WIN32 #include #include #endif #include #include #include #include "debug.h" #include "qstat.h" #include "packet_manip.h" #define TM_XML_PREFIX "\n\nsystem.multicall\n\n" #define TM_XML_SUFFIX "\n" #define TM_SERVERINFO "methodNameGetServerOptionsparams\nmethodNameGetCurrentChallengeInfoparams\n" #define TM_PLAYERLIST "methodNameGetPlayerListparams1000\n" #define TM_AUTH_TEMPLATE "\nmethodNameAuthenticate\nparams\n%s\n%s\n" query_status_t send_tm_request_packet(struct qserver *server) { char buf[2048]; char *xmlp = buf + 8; unsigned int len; char *user = get_param_value(server, "user", NULL); char *password = get_param_value(server, "password", NULL); if (!server->protocol_version) { // No seen the version yet wait // register_send here to ensure that timeouts function correctly return (register_send(server)); } // build the query xml len = sprintf(xmlp, TM_XML_PREFIX); if ((user != NULL) && (password != NULL)) { len += sprintf(xmlp + len, TM_AUTH_TEMPLATE, user, password); } else { // Default to User / User len += sprintf(xmlp + len, TM_AUTH_TEMPLATE, "User", "User"); } // Always get Player info otherwise player count is invalid // TODO: add more calls to get full player info? server->flags |= TF_PLAYER_QUERY | TF_RULES_QUERY; len += sprintf(xmlp + len, TM_SERVERINFO); len += sprintf(xmlp + len, TM_PLAYERLIST); len += sprintf(xmlp + len, TM_XML_SUFFIX); // First 4 bytes is the length of the request memcpy(buf, &len, 4); // Second 4 bytes is the handle identifier ( id ) memcpy(buf + 4, &server->challenge, 4); // prep the details we need for multi packet responses // we expect at least 1 packet response server->saved_data.pkt_max = 1; return (send_packet(server, buf, len + 8)); } query_status_t deal_with_tm_packet(struct qserver *server, char *rawpkt, int pktlen) { char *s; char *pkt = rawpkt; char *key = NULL, *value = NULL, *tmpp = NULL; char fullname[256]; struct player *player = NULL; int pkt_max = server->saved_data.pkt_max; unsigned total_len, expected_len; int method_response = 1; debug(2, "processing..."); s = rawpkt; // We may get the setup handle and the protocol version in one packet we may not // So we continue to parse if we see the handle if ((4 <= pktlen) && (0 == memcmp(pkt, "\x0b\x00\x00\x00", 4))) { // setup handle identifier // greater 2^31 = XML-RPC, less = callback server->challenge = 0x80000001; if (4 == pktlen) { return (0); } pktlen -= 4; pkt += 4; } if ((11 <= pktlen) && (1 == sscanf(pkt, "GBXRemote %d", &server->protocol_version))) { // Got protocol version send request send_tm_request_packet(server); return (0); } if ((8 <= pktlen) && (0 == memcmp(pkt + 4, &server->challenge, 4))) { // first 4 bytes = the length // Note: We use pkt_id to store the length of the expected packet // this could cause loss but very unlikely unsigned long len; memcpy(&len, rawpkt, 4); // second 4 bytes = handle identifier we sent in the request if (8 == pktlen) { // split packet // we have at least one more packet coming if (!add_packet(server, len, 0, 2, pktlen, rawpkt, 1)) { // fatal error e.g. out of memory return (-1); } return (0); } else { // ensure the length is stored server->saved_data.pkt_id = (int)len; s += 8; } } total_len = combined_length(server, server->saved_data.pkt_id); expected_len = server->saved_data.pkt_id; debug(2, "total: %d, expected: %d\n", total_len, expected_len); if (total_len < expected_len + 8) { // we dont have a complete response add the packet int last, new_max; if (total_len + pktlen >= expected_len + 8) { last = 1; new_max = pkt_max; } else { last = 0; new_max = pkt_max + 1; } if (!add_packet(server, server->saved_data.pkt_id, pkt_max - 1, new_max, pktlen, rawpkt, 1)) { // fatal error e.g. out of memory return (-1); } if (last) { // we are the last packet run combine to call us back return (combine_packets(server)); } return (0); } server->n_servers++; if (server->server_name == NULL) { server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); } else { gettimeofday(&server->packet_time1, NULL); } // Terminate the packet data pkt = (char *)malloc(pktlen + 1); if (NULL == pkt) { debug(0, "Failed to malloc memory for packet terminator\n"); return (MEM_ERROR); } memcpy(pkt, rawpkt, pktlen); pkt[pktlen] = '\0'; //fprintf( stderr, "S=%s\n", s ); s = strtok(pkt + 8, "\015\012"); while (NULL != s) { //fprintf( stderr, "S=%s\n", s ); if (0 == strncmp(s, "", 14)) { key = s + 14; tmpp = strstr(key, ""); if (NULL != tmpp) { *tmpp = '\0'; } s = strtok(NULL, "\015\012"); value = NULL; continue; } else if ((NULL != key) && (0 == strncmp(s, "", 7))) { // value s += 7; if (0 == strncmp(s, "", 8)) { // String value = s + 8; tmpp = strstr(s, ""); } else if (0 == strncmp(s, "", 4)) { // Int value = s + 4; tmpp = strstr(s, ""); } else if (0 == strncmp(s, "", 9)) { // Boolean value = s + 9; tmpp = strstr(s, ""); } else if (0 == strncmp(s, "", 8)) { // Double value = s + 8; tmpp = strstr(s, ""); } // also have struct and array but not interested in those if (NULL != tmpp) { *tmpp = '\0'; } if (NULL != value) { debug(4, "%s = %s\n", key, value); } } else if ((0 == strncmp(s, "", 9)) && (3 > method_response)) { // end of method response method_response++; } if ((NULL != value) && (NULL != key)) { switch (method_response) { case 1: // GetServerOptions response if (0 == strcmp("Name", key)) { server->server_name = strdup(value); } else if (0 == strcmp("CurrentMaxPlayers", key)) { server->max_players = atoi(value); } else { sprintf(fullname, "server.%s", key); add_rule(server, fullname, value, NO_FLAGS); } break; case 2: // GetCurrentChallengeInfo response if (0 == strcmp("Name", key)) { server->map_name = strdup(value); } else { sprintf(fullname, "challenge.%s", key); add_rule(server, fullname, value, NO_FLAGS); } break; case 3: // GetPlayerList response // Player info if (0 == strcmp("Login", key)) { player = add_player(server, server->n_player_info); server->num_players++; } else if (NULL != player) { if (0 == strcmp("NickName", key)) { player->name = strdup(value); } else if (0 == strcmp("PlayerId", key)) { //player->number = atoi( value ); } else if (0 == strcmp("TeamId", key)) { player->team = atoi(value); } else if (0 == strcmp("IsSpectator", key)) { player->flags = player->flags & 1; } else if (0 == strcmp("IsInOfficialMode", key)) { player->flags = player->flags & 2; } else if (0 == strcmp("LadderRanking", key)) { player->score = atoi(value); } } break; } value = NULL; } s = strtok(NULL, "\015\012"); } free(pkt); if (0 == strncmp(rawpkt + pktlen - 19, "", 17)) { // last packet seen return (DONE_FORCE); } return (INPROGRESS); } qstat-2.17/tm.h000066400000000000000000000006431412457473700134110ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * TrackMania protocol * Copyright 2005 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_TM_H #define QSTAT_TM_H #include "qserver.h" // Packet processing methods query_status_t deal_with_tm_packet(struct qserver *server, char *pkt, int pktlen); query_status_t send_tm_request_packet(struct qserver *server); #endif qstat-2.17/ts2.c000066400000000000000000000063321412457473700134750ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * Teamspeak 2 query protocol * Copyright 2005 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms * */ #include #ifndef _WIN32 #include #endif #include #include #include #include "debug.h" #include "qstat.h" #include "packet_manip.h" query_status_t send_ts2_request_packet(struct qserver *server) { char buf[256]; int serverport = get_param_i_value(server, "port", 0); change_server_port(server, serverport, 1); if (get_player_info) { server->flags |= TF_PLAYER_QUERY | TF_RULES_QUERY; sprintf(buf, "si %d\npl %d\nquit\n", serverport, serverport); server->saved_data.pkt_index = 2; } else { server->flags |= TF_STATUS_QUERY; sprintf(buf, "si %d\nquit\n", serverport); server->saved_data.pkt_index = 1; } return (send_packet(server, buf, strlen(buf))); } query_status_t deal_with_ts2_packet(struct qserver *server, char *rawpkt, int pktlen) { char *s; int ping, connect_time, mode = 0; char name[256]; debug(2, "processing..."); server->n_servers++; server->n_requests++; server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); if (0 == pktlen) { // Invalid password return (REQ_ERROR); } rawpkt[pktlen] = '\0'; s = rawpkt; s = strtok(rawpkt, "\015\012"); while (NULL != s) { if (0 == mode) { // Rules char *key = s; char *value = strchr(key, '='); if (NULL != value) { // Server Rule *value = '\0'; value++; if (0 == strcmp("server_name", key)) { server->server_name = strdup(value); } else if (0 == strcmp("server_udpport", key)) { change_server_port(server, atoi(value), 0); add_rule(server, key, value, NO_FLAGS); } else if (0 == strcmp("server_maxusers", key)) { server->max_players = atoi(value); } else if (0 == strcmp("server_currentusers", key)) { server->num_players = atoi(value); } else { add_rule(server, key, value, NO_FLAGS); } } else if (0 == strcmp("OK", s)) { // end of rules request server->saved_data.pkt_index--; mode++; } else if (0 == strcmp("[TS]", s)) { // nothing to do } else if (0 == strcmp("ERROR, invalid id", s)) { // bad server server->server_name = DOWN; server->saved_data.pkt_index = 0; } } else if (1 == mode) { // Player info if (3 == sscanf(s, "%*d %*d %*d %*d %*d %*d %*d %d %d %*d %*d %*d %*d \"0.0.0.0\" \"%255[^\"]", &ping, &connect_time, name)) { // Player info struct player *player = add_player(server, server->n_player_info); if (NULL != player) { player->name = strdup(name); player->ping = ping; player->connect_time = connect_time; } } else if (0 == strcmp("OK", s)) { // end of rules request server->saved_data.pkt_index--; mode++; } else if (0 == strcmp("[TS]", s)) { // nothing to do } else if (0 == strcmp("ERROR, invalid id", s)) { // bad server server->server_name = DOWN; server->saved_data.pkt_index = 0; } } s = strtok(NULL, "\015\012"); } gettimeofday(&server->packet_time1, NULL); if (0 == server->saved_data.pkt_index) { server->map_name = strdup("N/A"); return (DONE_FORCE); } return (INPROGRESS); } qstat-2.17/ts2.h000066400000000000000000000006501412457473700134770ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * Teamspeak 2 protocol * Copyright 2005 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_TS2_H #define QSTAT_TS2_H #include "qserver.h" // Packet processing methods query_status_t deal_with_ts2_packet(struct qserver *server, char *pkt, int pktlen); query_status_t send_ts2_request_packet(struct qserver *server); #endif qstat-2.17/ts3.c000066400000000000000000000245321412457473700135000ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * Teamspeak 3 query protocol * Copyright 2009 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms * */ #include #ifndef _WIN32 #include #include #include #else #include #endif #include #include #include #include "debug.h" #include "qstat.h" #include "utils.h" #include "packet_manip.h" char * decode_ts3_val(char *val) { val = str_replace(val, "\\\\", "\\"); val = str_replace(val, "\\/", "/"); val = str_replace(val, "\\s", " "); val = str_replace(val, "\\p", "|"); val = str_replace(val, "\\a", "\007"); val = str_replace(val, "\\b", "\010"); val = str_replace(val, "\\f", "\014"); val = str_replace(val, "\\n", "\012"); val = str_replace(val, "\\r", "\015"); val = str_replace(val, "\\t", "\011"); return (str_replace(val, "\\v", "\013")); } int all_ts3_servers(struct qserver *server) { return ((1 == get_param_i_value(server, "allservers", 0)) ? 1 : 0); } query_status_t send_ts3_all_servers_packet(struct qserver *server) { char buf[256], *password, *username; switch (server->challenge) { case 0: // Not seen a challenge yet, wait for it server->n_servers = 999; return (INPROGRESS); case 1: password = get_param_value(server, "password", ""); if (0 != strlen(password)) { username = get_param_value(server, "username", "serveradmin"); sprintf(buf, "login %s %s\015\012", username, password); break; } // NOTE: no break so we fall through server->challenge++; case 2: // NOTE: we currently don't support player info server->flags |= TF_STATUS_QUERY; server->n_servers = 3; sprintf(buf, "serverlist\015\012"); break; case 3: sprintf(buf, "quit\015\012"); break; case 4: return (DONE_FORCE); } server->saved_data.pkt_max = -1; return (send_packet(server, buf, strlen(buf))); } query_status_t send_ts3_single_server_packet(struct qserver *server) { char buf[256], *password, *username; int serverport; switch (server->challenge) { case 0: // Not seen a challenge yet, wait for it server->n_servers = 999; return (INPROGRESS); case 1: // Login if needed password = get_param_value(server, "password", ""); if (0 != strlen(password)) { username = get_param_value(server, "username", "serveradmin"); sprintf(buf, "login %s %s\015\012", username, password); break; } // NOTE: no break so we fall through server->challenge++; case 2: // Select port serverport = get_param_i_value(server, "port", 0); change_server_port(server, serverport, 1); // NOTE: we use n_servers as an indication of how many responses we are expecting to get if (get_player_info) { server->flags |= TF_PLAYER_QUERY | TF_RULES_QUERY; server->n_servers = 5; } else { server->flags |= TF_STATUS_QUERY; server->n_servers = 4; } sprintf(buf, "use port=%d\015\012", serverport); break; case 3: // Server Info sprintf(buf, "serverinfo\015\012"); break; case 4: // Player Info, Quit or Done sprintf(buf, (get_player_info) ? "clientlist\015\012" : "quit\015\012"); break; case 5: // Quit or Done if (get_player_info) { sprintf(buf, "quit\015\012"); } else { return (DONE_FORCE); } break; } server->saved_data.pkt_max = -1; return (send_packet(server, buf, strlen(buf))); } query_status_t send_ts3_request_packet(struct qserver *server) { debug(3, "send_ts3_request_packet: state = %ld", server->challenge); return ((all_ts3_servers(server)) ? send_ts3_all_servers_packet(server) : send_ts3_single_server_packet(server)); } int valid_ts3_response(struct qserver *server, char *rawpkt, int pktlen) { char *end = &rawpkt[pktlen - 1]; char *s = rawpkt; if (0 == strncmp("TS3", s, 3)) { // Challenge server->master_pkt_len = 3; return (1); } while (NULL != s) { // use response if (0 == strncmp("error", s, 5)) { // end cmd response if (0 == strncmp("error id=0", s, 9)) { server->master_pkt_len = s - rawpkt + 9; return (1); } else { // bad server server->server_name = DOWN; server->saved_data.pkt_index = 0; return (DONE_FORCE); } } s = strstr(s, "\012\015"); if (NULL != s) { s = (s + 2 < end) ? s + 2 : NULL; } } return (0); } query_status_t deal_with_ts3_packet(struct qserver *server, char *rawpkt, int pktlen) { char *s, *player_name = "unknown"; int valid_response = 0, mode = 0, all_servers = 0; char last_char; unsigned short port = 0, down = 0, auth_seen = 0; debug(2, "processing..."); if (0 == pktlen) { // Invalid password return (REQ_ERROR); } last_char = rawpkt[pktlen - 1]; rawpkt[pktlen - 1] = '\0'; s = rawpkt; all_servers = all_ts3_servers(server); debug(3, "packet: combined = %d, challenge = %ld, n_servers = %d", server->combined, server->challenge, server->n_servers); if (!server->combined) { server->retry1 = n_retries; if (0 == server->n_requests) { server->ping_total = time_delta(&packet_recv_time, &server->packet_time1); server->n_requests++; } if (server->n_servers >= server->challenge) { // response fragment recieved int pkt_id; int pkt_max; // We're expecting more to come debug(5, "fragment recieved..."); pkt_id = packet_count(server); pkt_max = pkt_id + 1; rawpkt[pktlen - 1] = last_char; // restore the last character if (!add_packet(server, 0, pkt_id, pkt_max, pktlen, rawpkt, 1)) { // fatal error e.g. out of memory return (MEM_ERROR); } // combine_packets will call us recursively return (combine_packets(server)); } } else { valid_response = valid_ts3_response(server, rawpkt + server->master_pkt_len, pktlen - server->master_pkt_len); debug(2, "combined packet: valid_response: %d, challenge: %ld, n_servers: %d, offset: %d", valid_response, server->challenge, server->n_servers, server->master_pkt_len); if (0 > valid_response) { // Error occured return (valid_response); } server->challenge += valid_response; if (valid_response) { // Got a valid response, send the next request int ret = send_ts3_request_packet(server); if (0 != ret) { // error sending packet debug(4, "Request failed: %d", ret); return (ret); } } if (server->n_servers > server->challenge) { // recursive call which is still incomplete return (INPROGRESS); } } // Correct ping // Not quite right but gives a good estimate server->ping_total = (server->ping_total * server->n_requests) / 2; debug(3, "processing response..."); s = strtok(rawpkt, "\012\015 |"); // NOTE: id=XXX and msg=XXX will be processed by the mod following the one they where the response of while (NULL != s) { debug(4, "LINE: %d, %s\n", mode, s); switch (mode) { case 0: // prompt, use or serverlist response if (0 == strcmp("TS3", s)) { // nothing to do unless in all servers mode if (1 == all_servers) { mode++; } } else if (0 == strncmp("error", s, 5)) { // end of use response mode++; } break; case 1: // serverinfo or serverlist response including condition authentication if ((0 == auth_seen) && (0 != strlen(get_param_value(server, "password", ""))) && (0 == strncmp("error", s, 5))) { // end of auth response auth_seen = 1; } else if (0 == strncmp("error", s, 5)) { // end of serverinfo response mode++; } else { // Server Rule char *key = s; char *value = strchr(key, '='); if (NULL != value) { *value = '\0'; value++; debug(6, "Rule: %s = %s\n", key, value); if (0 == strcmp("virtualserver_name", key)) { if (1 == all_servers) { struct qserver *new_server = add_qserver_byaddr(ntohl(server->ipaddr), port, server->type, NULL); if (NULL != new_server) { if (down) { // Status indicates this server is actually offline new_server->server_name = DOWN; } else { new_server->max_players = server->max_players; new_server->num_players = server->num_players; new_server->server_name = strdup(decode_ts3_val(value)); new_server->map_name = strdup("N/A"); new_server->ping_total = server->ping_total; new_server->n_requests = server->n_requests; } cleanup_qserver(new_server, FORCE); } down = 0; } else { server->server_name = strdup(decode_ts3_val(value)); } } else if (0 == strcmp("virtualserver_port", key)) { port = atoi(value); change_server_port(server, port, 0); add_rule(server, key, value, NO_FLAGS); } else if (0 == strcmp("virtualserver_maxclients", key)) { server->max_players = atoi(value); } else if (0 == strcmp("virtualserver_clientsonline", key)) { server->num_players = atoi(value); } else if (0 == strcmp("virtualserver_queryclientsonline", key)) { // clientsonline includes queryclientsonline so remove these from our count server->num_players -= atoi(value); } else if ((0 == strcmp("virtualserver_status", key)) && (0 != strcmp("online", value))) { // Server is actually offline to client so display as down down = 1; if (1 != all_servers) { server->server_name = DOWN; //server->saved_data.pkt_index = 0; return (DONE_FORCE); } } else if ((0 == strcmp("id", key)) || (0 == strcmp("msg", key))) { // Ignore details from the response code } else if (1 != all_servers) { add_rule(server, key, value, NO_FLAGS); } } } break; case 2: // clientlist response if (0 == strncmp("error", s, 5)) { // end of serverinfo response mode++; } else { // Client char *key = s; char *value = strchr(key, '='); if (NULL != value) { *value = '\0'; value++; debug(6, "Player: %s = %s\n", key, value); if (0 == strcmp("client_nickname", key)) { player_name = value; } else if (0 == strcmp("clid", key)) { } else if ((0 == strcmp("client_type", key)) && (0 == strcmp("0", value))) { struct player *player = add_player(server, server->n_player_info); if (NULL != player) { player->name = strdup(decode_ts3_val(player_name)); } } else if ((0 == strcmp("id", key)) || (0 == strcmp("msg", key))) { // Ignore details from the response code } } } break; } s = strtok(NULL, "\012\015 |"); } gettimeofday(&server->packet_time1, NULL); server->map_name = strdup("N/A"); return (DONE_FORCE); } qstat-2.17/ts3.h000066400000000000000000000006501412457473700135000ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * Teamspeak 3 protocol * Copyright 2005 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_TS3_H #define QSTAT_TS3_H #include "qserver.h" // Packet processing methods query_status_t deal_with_ts3_packet(struct qserver *server, char *pkt, int pktlen); query_status_t send_ts3_request_packet(struct qserver *server); #endif qstat-2.17/uncrustify.cfg000066400000000000000000000534101412457473700155140ustar00rootroot00000000000000# Uncrustify 0.64 newlines = lf input_tab_size = 8 output_tab_size = 8 string_escape_char = 92 string_escape_char2 = 0 string_replace_tab_chars = false tok_split_gte = false disable_processing_cmt = "" enable_processing_cmt = "" enable_digraphs = false utf8_bom = ignore utf8_byte = false utf8_force = false indent_columns = 8 indent_continue = 4 indent_with_tabs = 2 indent_cmt_with_tabs = false indent_align_string = false indent_xml_string = 0 indent_brace = 0 indent_braces = false indent_braces_no_func = false indent_braces_no_class = false indent_braces_no_struct = false indent_brace_parent = false indent_paren_open_brace = false indent_cs_delegate_brace = false indent_namespace = false indent_namespace_single_indent = false indent_namespace_level = 0 indent_namespace_limit = 0 indent_extern = false indent_class = true indent_class_colon = true indent_class_on_colon = false indent_constr_colon = false indent_ctor_init_leading = 2 indent_ctor_init = 0 indent_else_if = false indent_var_def_blk = 0 indent_var_def_cont = false indent_shift = false indent_func_def_force_col1 = false indent_func_call_param = false indent_func_def_param = false indent_func_proto_param = false indent_func_class_param = false indent_func_ctor_var_param = false indent_template_param = false indent_func_param_double = false indent_func_const = 0 indent_func_throw = 0 indent_member = 0 indent_sing_line_comments = 0 indent_relative_single_line_comments = false indent_switch_case = 0 indent_case_shift = 0 indent_case_brace = 0 indent_col1_comment = false indent_label = 1 indent_access_spec = 1 indent_access_spec_body = false indent_paren_nl = false indent_paren_close = 0 indent_comma_paren = false indent_bool_paren = false indent_first_bool_expr = false indent_square_nl = false indent_preserve_sql = false indent_align_assign = true indent_oc_block = false indent_oc_block_msg = 0 indent_oc_msg_colon = 0 indent_oc_msg_prioritize_first_colon = true indent_oc_block_msg_xcode_style = false indent_oc_block_msg_from_keyword = false indent_oc_block_msg_from_colon = false indent_oc_block_msg_from_caret = false indent_oc_block_msg_from_brace = false indent_min_vbrace_open = 0 indent_vbrace_open_on_tabstop = false indent_token_after_brace = true indent_cpp_lambda_body = false sp_arith = ignore sp_assign = force sp_cpp_lambda_assign = ignore sp_cpp_lambda_paren = ignore sp_assign_default = ignore sp_before_assign = ignore sp_after_assign = ignore sp_enum_paren = ignore sp_enum_assign = ignore sp_enum_before_assign = ignore sp_enum_after_assign = ignore sp_pp_concat = add sp_pp_stringify = add sp_before_pp_stringify = ignore sp_bool = force sp_compare = force sp_inside_paren = remove sp_paren_paren = remove sp_cparen_oparen = ignore sp_balance_nested_parens = false sp_paren_brace = ignore sp_before_ptr_star = force sp_before_unnamed_ptr_star = ignore sp_between_ptr_star = remove sp_after_ptr_star = remove sp_after_ptr_star_qualifier = ignore sp_after_ptr_star_func = ignore sp_ptr_star_paren = ignore sp_before_ptr_star_func = ignore sp_before_byref = remove sp_before_unnamed_byref = ignore sp_after_byref = force sp_after_byref_func = ignore sp_before_byref_func = ignore sp_after_type = force sp_before_template_paren = ignore sp_template_angle = ignore sp_before_angle = remove sp_inside_angle = remove sp_after_angle = force sp_angle_paren = ignore sp_angle_paren_empty = ignore sp_angle_word = ignore sp_angle_shift = add sp_permit_cpp11_shift = false sp_before_sparen = force sp_inside_sparen = remove sp_inside_sparen_close = ignore sp_inside_sparen_open = ignore sp_after_sparen = force sp_sparen_brace = add sp_invariant_paren = ignore sp_after_invariant_paren = ignore sp_special_semi = ignore sp_before_semi = remove sp_before_semi_for = ignore sp_before_semi_for_empty = force sp_after_semi = add sp_after_semi_for = force sp_after_semi_for_empty = ignore sp_before_square = ignore sp_before_squares = ignore sp_inside_square = remove sp_after_comma = force sp_before_comma = remove sp_after_mdatype_commas = ignore sp_before_mdatype_commas = ignore sp_between_mdatype_commas = ignore sp_paren_comma = force sp_before_ellipsis = ignore sp_after_class_colon = ignore sp_before_class_colon = ignore sp_after_constr_colon = ignore sp_before_constr_colon = ignore sp_before_case_colon = remove sp_after_operator = ignore sp_after_operator_sym = ignore sp_after_operator_sym_empty = ignore sp_after_cast = remove sp_inside_paren_cast = ignore sp_cpp_cast_paren = ignore sp_sizeof_paren = remove sp_after_tag = ignore sp_inside_braces_enum = force sp_inside_braces_struct = force sp_inside_braces = force sp_inside_braces_empty = ignore sp_type_func = force sp_func_proto_paren = remove sp_func_proto_paren_empty = ignore sp_func_def_paren = remove sp_func_def_paren_empty = ignore sp_inside_fparens = ignore sp_inside_fparen = remove sp_inside_tparen = ignore sp_after_tparen_close = ignore sp_square_fparen = ignore sp_fparen_brace = add sp_fparen_dbrace = ignore sp_func_call_paren = remove sp_func_call_paren_empty = ignore sp_func_call_user_paren = ignore sp_func_class_paren = remove sp_func_class_paren_empty = ignore sp_return_paren = force sp_attribute_paren = ignore sp_defined_paren = ignore sp_throw_paren = ignore sp_after_throw = ignore sp_catch_paren = ignore sp_version_paren = ignore sp_scope_paren = ignore sp_super_paren = remove sp_this_paren = remove sp_macro = ignore sp_macro_func = ignore sp_else_brace = ignore sp_brace_else = ignore sp_brace_typedef = force sp_catch_brace = ignore sp_brace_catch = ignore sp_finally_brace = ignore sp_brace_finally = ignore sp_try_brace = ignore sp_getset_brace = ignore sp_word_brace = add sp_word_brace_ns = add sp_before_dc = remove sp_after_dc = remove sp_d_array_colon = ignore sp_not = remove sp_inv = remove sp_addr = remove sp_member = remove sp_deref = remove sp_sign = remove sp_incdec = remove sp_before_nl_cont = add sp_after_oc_scope = ignore sp_after_oc_colon = ignore sp_before_oc_colon = ignore sp_after_oc_dict_colon = ignore sp_before_oc_dict_colon = ignore sp_after_send_oc_colon = ignore sp_before_send_oc_colon = ignore sp_after_oc_type = ignore sp_after_oc_return_type = ignore sp_after_oc_at_sel = ignore sp_after_oc_at_sel_parens = ignore sp_inside_oc_at_sel_parens = ignore sp_before_oc_block_caret = ignore sp_after_oc_block_caret = ignore sp_after_oc_msg_receiver = ignore sp_after_oc_property = ignore sp_cond_colon = ignore sp_cond_colon_before = ignore sp_cond_colon_after = ignore sp_cond_question = ignore sp_cond_question_before = ignore sp_cond_question_after = ignore sp_cond_ternary_short = ignore sp_case_label = remove sp_range = ignore sp_after_for_colon = ignore sp_before_for_colon = ignore sp_extern_paren = ignore sp_cmt_cpp_start = ignore sp_cmt_cpp_doxygen = false sp_cmt_cpp_qttr = false sp_endif_cmt = ignore sp_after_new = ignore sp_between_new_paren = ignore sp_before_tr_emb_cmt = ignore sp_num_before_tr_emb_cmt = 0 sp_annotation_paren = ignore sp_skip_vbrace_tokens = false align_keep_tabs = false align_with_tabs = true align_on_tabstop = true align_number_left = true align_keep_extra_space = false align_func_params = false align_same_func_call_params = false align_var_def_span = 0 align_var_def_star_style = 0 align_var_def_amp_style = 0 align_var_def_thresh = 0 align_var_def_gap = 0 align_var_def_colon = false align_var_def_attribute = false align_var_def_inline = false align_assign_span = 0 align_assign_thresh = 12 align_enum_equ_span = 16 align_enum_equ_thresh = 0 align_var_class_span = 0 align_var_class_thresh = 0 align_var_class_gap = 0 align_var_struct_span = 0 align_var_struct_thresh = 0 align_var_struct_gap = 0 align_struct_init_span = 3 align_typedef_gap = 0 align_typedef_span = 0 align_typedef_func = 0 align_typedef_star_style = 0 align_typedef_amp_style = 0 align_right_cmt_span = 3 align_right_cmt_mix = false align_right_cmt_gap = 0 align_right_cmt_at_col = 0 align_func_proto_span = 0 align_func_proto_gap = 0 align_on_operator = false align_mix_var_proto = false align_single_line_func = false align_single_line_brace = false align_single_line_brace_gap = 0 align_oc_msg_spec_span = 0 align_nl_cont = true align_pp_define_together = false align_pp_define_gap = 4 align_pp_define_span = 3 align_left_shift = true align_asm_colon = false align_oc_msg_colon_span = 0 align_oc_msg_colon_first = false align_oc_decl_colon = false nl_collapse_empty_body = false nl_assign_leave_one_liners = true nl_class_leave_one_liners = true nl_enum_leave_one_liners = false nl_getset_leave_one_liners = false nl_func_leave_one_liners = false nl_cpp_lambda_leave_one_liners = false nl_if_leave_one_liners = false nl_while_leave_one_liners = false nl_oc_msg_leave_one_liner = false nl_oc_block_brace = ignore nl_start_of_file = remove nl_start_of_file_min = 0 nl_end_of_file = force nl_end_of_file_min = 1 nl_assign_brace = add nl_assign_square = ignore nl_after_square_assign = ignore nl_func_var_def_blk = 1 nl_typedef_blk_start = 0 nl_typedef_blk_end = 0 nl_typedef_blk_in = 0 nl_var_def_blk_start = 0 nl_var_def_blk_end = 0 nl_var_def_blk_in = 0 nl_fcall_brace = add nl_enum_brace = remove nl_struct_brace = remove nl_union_brace = remove nl_if_brace = remove nl_brace_else = remove nl_elseif_brace = remove nl_else_brace = remove nl_else_if = remove nl_brace_finally = ignore nl_finally_brace = ignore nl_try_brace = ignore nl_getset_brace = force nl_for_brace = remove nl_catch_brace = ignore nl_brace_catch = ignore nl_brace_square = ignore nl_brace_fparen = ignore nl_while_brace = remove nl_scope_brace = ignore nl_unittest_brace = ignore nl_version_brace = ignore nl_using_brace = ignore nl_brace_brace = ignore nl_do_brace = remove nl_brace_while = remove nl_switch_brace = remove nl_synchronized_brace = ignore nl_multi_line_cond = false nl_multi_line_define = true nl_before_case = true nl_before_throw = ignore nl_after_case = true nl_case_colon_brace = ignore nl_namespace_brace = ignore nl_template_class = ignore nl_class_brace = ignore nl_class_init_args = ignore nl_constr_init_args = ignore nl_enum_own_lines = ignore nl_func_type_name = ignore nl_func_type_name_class = ignore nl_func_class_scope = ignore nl_func_scope_name = ignore nl_func_proto_type_name = ignore nl_func_paren = remove nl_func_def_paren = ignore nl_func_decl_start = ignore nl_func_def_start = ignore nl_func_decl_start_single = ignore nl_func_def_start_single = ignore nl_func_decl_start_multi_line = false nl_func_def_start_multi_line = false nl_func_decl_args = ignore nl_func_def_args = ignore nl_func_decl_args_multi_line = false nl_func_def_args_multi_line = false nl_func_decl_end = ignore nl_func_def_end = ignore nl_func_decl_end_single = ignore nl_func_def_end_single = ignore nl_func_decl_end_multi_line = false nl_func_def_end_multi_line = false nl_func_decl_empty = ignore nl_func_def_empty = ignore nl_func_call_start_multi_line = false nl_func_call_args_multi_line = false nl_func_call_end_multi_line = false nl_oc_msg_args = false nl_fdef_brace = add nl_cpp_ldef_brace = ignore nl_return_expr = ignore nl_after_semicolon = true nl_paren_dbrace_open = ignore nl_after_brace_open = true nl_after_brace_open_cmt = false nl_after_vbrace_open = false nl_after_vbrace_open_empty = false nl_after_brace_close = true nl_after_vbrace_close = false nl_brace_struct_var = ignore nl_define_macro = false nl_squeeze_ifdef = true nl_squeeze_ifdef_top_level = false nl_before_if = ignore nl_after_if = ignore nl_before_for = ignore nl_after_for = ignore nl_before_while = ignore nl_after_while = ignore nl_before_switch = ignore nl_after_switch = ignore nl_before_synchronized = ignore nl_after_synchronized = ignore nl_before_do = ignore nl_after_do = ignore nl_ds_struct_enum_cmt = false nl_ds_struct_enum_close_brace = false nl_before_func_class_def = 0 nl_before_func_class_proto = 0 nl_class_colon = ignore nl_constr_colon = ignore nl_create_if_one_liner = false nl_create_for_one_liner = false nl_create_while_one_liner = false nl_split_if_one_liner = false nl_split_for_one_liner = false nl_split_while_one_liner = false pos_arith = ignore pos_assign = ignore pos_bool = trail pos_compare = ignore pos_conditional = ignore pos_comma = ignore pos_enum_comma = ignore pos_class_comma = ignore pos_constr_comma = ignore pos_class_colon = ignore pos_constr_colon = ignore code_width = 0 ls_for_split_full = false ls_func_split_full = false ls_code_width = false nl_max = 4 nl_after_func_proto = 0 nl_after_func_proto_group = 2 nl_after_func_class_proto = 0 nl_after_func_class_proto_group = 0 nl_before_func_body_def = 0 nl_before_func_body_proto = 0 nl_after_func_body = 3 nl_after_func_body_class = 0 nl_after_func_body_one_liner = 0 nl_before_block_comment = 2 nl_before_c_comment = 0 nl_before_cpp_comment = 0 nl_after_multiline_comment = false nl_after_label_colon = false nl_after_struct = 0 nl_before_class = 0 nl_after_class = 0 nl_before_access_spec = 0 nl_after_access_spec = 0 nl_comment_func_def = 1 nl_after_try_catch_finally = 0 nl_around_cs_property = 0 nl_between_get_set = 0 nl_property_brace = ignore eat_blanks_after_open_brace = true eat_blanks_before_close_brace = true nl_remove_extra_newlines = 0 nl_before_return = false nl_after_return = true nl_after_annotation = ignore nl_between_annotation = ignore mod_full_brace_do = add mod_full_brace_for = add mod_full_brace_function = ignore mod_full_brace_if = add mod_full_brace_if_chain = false mod_full_brace_if_chain_only = false mod_full_brace_nl = 0 mod_full_brace_while = add mod_full_brace_using = ignore mod_paren_on_return = add mod_pawn_semicolon = false mod_full_paren_if_bool = true mod_remove_extra_semicolon = true mod_add_long_function_closebrace_comment = 0 mod_add_long_namespace_closebrace_comment = 0 mod_add_long_class_closebrace_comment = 0 mod_add_long_switch_closebrace_comment = 0 mod_add_long_ifdef_endif_comment = 0 mod_add_long_ifdef_else_comment = 0 mod_sort_import = false mod_sort_using = false mod_sort_include = false mod_move_case_break = false mod_case_brace = remove mod_remove_empty_return = true mod_sort_oc_properties = false mod_sort_oc_property_thread_safe_weight = 0 mod_sort_oc_property_readwrite_weight = 0 mod_sort_oc_property_reference_weight = 0 mod_sort_oc_property_getter_weight = 0 mod_sort_oc_property_setter_weight = 0 mod_sort_oc_property_nullability_weight = 0 cmt_width = 0 cmt_reflow_mode = 0 cmt_convert_tab_to_spaces = false cmt_indent_multi = true cmt_c_group = false cmt_c_nl_start = false cmt_c_nl_end = false cmt_cpp_group = false cmt_cpp_nl_start = false cmt_cpp_nl_end = false cmt_cpp_to_c = false cmt_star_cont = true cmt_sp_before_star_cont = 0 cmt_sp_after_star_cont = 0 cmt_multi_check_last = true cmt_multi_first_len_minimum = 4 cmt_insert_file_header = "" cmt_insert_file_footer = "" cmt_insert_func_header = "" cmt_insert_class_header = "" cmt_insert_oc_msg_header = "" cmt_insert_before_preproc = false cmt_insert_before_inlines = true cmt_insert_before_ctor_dtor = false pp_indent = force pp_indent_at_level = false pp_indent_count = 1 pp_space = ignore pp_space_count = 0 pp_indent_region = 0 pp_region_indent_code = false pp_indent_if = 0 pp_if_indent_code = true pp_define_at_level = true use_indent_func_call_param = true use_indent_continue_only_once = false use_options_overriding_for_qt_macros = true warn_level_tabs_found_in_verbatim_string_literals = 2 # option(s) with 'not default' value: 0 # qstat-2.17/ut2004.c000066400000000000000000000364511412457473700137300ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * UT2004 master query functions * Copyright 2004 Ludwig Nussel * * Licensed under the Artistic License, see LICENSE.txt for license terms * * This code is inspired by ideas from 'Nurulwai' * */ #include #ifndef _WIN32 #include #endif #include #include #include "debug.h" #include "qstat.h" #include "md5.h" /** \brief convert bytes into hex string * * \param in data to convert * \param len length of data * \param out location to store string to. Must be 2*len */ static void bin2hex(const char *in, size_t len, char *out); #define CD_KEY_LENGTH 23 // arbitrary #define MAX_LISTING_RECORD_LEN 0x04FF #define RESPONSE_OFFSET_CDKEY 5 #define RESPONSE_OFFSET_CHALLENGE 39 static const char challenge_response[] = { 0x68, 0x00, 0x00, 0x00, '!', 0xCD, 0xCD, 0xCD, /* length | ! MD5SUM, CD is placeholder */ 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0x00, '!', 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0xCD, 0x00, 0x0c, 'U', 'T', '2', 'K', '4', 'C', 'L', /*^^ 12 byte string */ 'I', 'E', 'N', 'T', 0x00, 0xfb, 0x0c, 0x00, /* | unknown */ 0x00, 0x06, 0x04, 'i', 'n', 't', 0x00, 0x00, /* | ^^ 4 byte string | ? */ 0x00, 0x00, 0x00, 0xee, 0xee, 0x00, 0x00, 0x11, /* unknown */ 0x00, 0x00, 0x00, 0x01 }; static const char approved[] = { 0x0e, 0x00, 0x00, 0x00, 0x09, 'A', 'P', 'P', 'R', 'O', 'V', 'E', 'D', 0x00, 0x03, 0x00, 0x00, 0x00 }; static const char approved_response[] = { 0x22, 0x00, 0x00, 0x00, '!', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', '0', 0x00 }; static const char verified[] = { 0x0a, 0x00, 0x00, 0x00, 0x09, 'V', 'E', 'R', 'I', 'F', 'I', 'E', 'D', 0x00 }; #if 0 struct server_listing_record_head { unsigned len; unsigned ip; short port; short queryport; char name[]; // char map[] }; struct server_listing_record_foot { unsigned char marker1[3]; unsigned char unknown1; unsigned char maxplayers; unsigned char unknown2[4]; unsigned char marker2[3]; }; #endif static char cdkey[CD_KEY_LENGTH + 1] = ""; enum ut2004_state { STATE_CHALLENGE = 0x00, STATE_APPROVED = 0x01, STATE_VERIFIED = 0x02, STATE_LISTING = 0x03, }; query_status_t send_ut2004master_request_packet(struct qserver *server) { int ret; if (server->n_packets) { return (DONE_FORCE); } if (!*cdkey) { char *param = get_param_value(server, "cdkey", NULL); if (!param) { debug(0, "Error: missing cdkey parameter"); server->server_name = SYSERROR; return (PKT_ERROR); } if (strstr(param, "/") != NULL || strstr(param, "\\") != NULL) { FILE *fp = fopen(param, "r"); if (!fp || (fread(cdkey, 1, CD_KEY_LENGTH, fp) != CD_KEY_LENGTH)) { debug(0, "Error: can't key from %s", param); server->server_name = SYSERROR; if (fp) { fclose(fp); } return (PKT_ERROR); } fclose(fp); } else if (strchr(param, '-') && (strlen(param) == CD_KEY_LENGTH)) { memcpy(cdkey, param, CD_KEY_LENGTH); } else if ((*param == '$') && (param = getenv(param + 1)) && // replaces param! (strlen(param) == CD_KEY_LENGTH)) { memcpy(cdkey, param, CD_KEY_LENGTH); } else { debug(0, "Error: invalid cdkey parameter"); server->server_name = SYSERROR; return (PKT_ERROR); } } ret = qserver_send(server, NULL, 0); #if 0 // XXX since we do not send but rather expect a reply directly after // connect it's pointless to retry doing nothing debug(0, "retry1: %d", server->retry1); server->retry1 = 0; #endif server->master_query_tag[0] = STATE_CHALLENGE; return (ret); } static void ut2004_server_done(struct qserver *server) { if (server->saved_data.next) { debug(0, "%d bytes of unprocessed data left. Premature EOF!?", server->saved_data.next->datalen); free(server->saved_data.next->data); free(server->saved_data.next); server->saved_data.next = NULL; } } // we use n_servers to store number of used bytes in master_pkt so // it needs to be divided by 6 when finished static void ut2004_parse_record(struct qserver *server, char *pkt) { char *dest; #if 0 unsigned ip; unsigned short port; memcpy(&ip, pkt + 4, 4); port = swap_short_from_little(pkt + 4 + 4); debug(2, "got %d.%d.%d.%d:%hu", ip & 0xff, (ip >> 8) & 0xff, (ip >> 16) & 0xff, (ip >> 24) & 0xff, port); #endif if (server->n_servers + 6 > server->master_pkt_len) { if (!server->master_pkt_len) { server->master_pkt_len = 180; } else { server->master_pkt_len *= 2; } server->master_pkt = (char *)realloc(server->master_pkt, server->master_pkt_len); } dest = server->master_pkt + server->n_servers; memcpy(dest, pkt + 4, 4); dest[4] = pkt[9]; dest[5] = pkt[8]; server->n_servers += 6; } static char * put_bytes(char *buf, const char *bytes, size_t len, size_t *left) { if (!buf || (len > *left)) { return (NULL); } memcpy(buf, bytes, len); *left -= len; return (buf + len); } static char * put_string(char *buf, const char *string, size_t *left) { size_t len = strlen(string) + 1; char l; if (!buf || (len > 0xFF) || (*left < len + 1)) { return (NULL); } l = len; buf = put_bytes(buf, &l, 1, left); return (put_bytes(buf, string, len, left)); } /** \brief assemble the server filter and send the master query * * the query consists of four bytes length (excluding the four length bytes), a * null byte and then the number of item pairs that follow. * * Each pair consists of two ut2 strings (length+null terminated string) * followed by a byte which is either zero or 0x04 which means negate the query * (e.g. not zero curplayers means not empty). */ static int ut2004_send_query(struct qserver *server) { char buf[4096] = { 0 }; size_t left = sizeof(buf); char *b = buf; char *param, *r, *sep = ""; unsigned flen = 0; unsigned char items = 0; // header is done later b += 6; left -= 6; param = get_param_value(server, "gametype", NULL); if (param) { ++items; b = put_string(b, "gametype", &left); b = put_string(b, param, &left); b = put_bytes(b, "", 1, &left); } param = get_param_value(server, "status", NULL); r = param; while (param && sep) { sep = strchr(r, ':'); if (sep) { flen = sep - r; } else { flen = strlen(r); } if ((strncmp(r, "standard", flen) == 0) || (strncmp(r, "nostandard", flen) == 0)) { ++items; b = put_string(b, "standard", &left); if (*r == 'n') { b = put_string(b, "false", &left); } else { b = put_string(b, "true", &left); } b = put_bytes(b, "", 1, &left); } else if ((strncmp(r, "password", flen) == 0) || (strncmp(r, "nopassword", flen) == 0)) { ++items; b = put_string(b, "password", &left); if (*r == 'n') { b = put_string(b, "false", &left); } else { b = put_string(b, "true", &left); } b = put_bytes(b, "", 1, &left); } else if (strncmp(r, "notempty", flen) == 0) { ++items; b = put_string(b, "currentplayers", &left); b = put_string(b, "0", &left); b = put_bytes(b, "\x04", 1, &left); } else if (strncmp(r, "notfull", flen) == 0) { ++items; b = put_string(b, "freespace", &left); b = put_string(b, "0", &left); b = put_bytes(b, "\x04", 1, &left); } else if (strncmp(r, "nobots", flen) == 0) { ++items; b = put_string(b, "nobots", &left); b = put_string(b, "true", &left); b = put_bytes(b, "", 1, &left); } else if ((strncmp(r, "stats", flen) == 0) || (strncmp(r, "nostats", flen) == 0)) { ++items; b = put_string(b, "stats", &left); if (*r == 'n') { b = put_string(b, "false", &left); } else { b = put_string(b, "true", &left); } b = put_bytes(b, "", 1, &left); } else if ((strncmp(r, "weaponstay", flen) == 0) || (strncmp(r, "noweaponstay", flen) == 0)) { ++items; b = put_string(b, "weaponstay", &left); if (*r == 'n') { b = put_string(b, "false", &left); } else { b = put_string(b, "true", &left); } b = put_bytes(b, "", 1, &left); } else if ((strncmp(r, "transloc", flen) == 0) || (strncmp(r, "notransloc", flen) == 0)) { ++items; b = put_string(b, "transloc", &left); if (*r == 'n') { b = put_string(b, "false", &left); } else { b = put_string(b, "true", &left); } b = put_bytes(b, "", 1, &left); } r = sep + 1; } param = get_param_value(server, "mutator", NULL); r = param; sep = ""; while (param && sep) { char neg = '\0'; unsigned char l; sep = strchr(r, ':'); if (sep) { flen = sep - r; } else { flen = strlen(r); } if (*r == '-') { neg = '\x04'; ++r; --flen; } if (!flen) { continue; } b = put_string(b, "mutator", &left); l = flen + 1; b = put_bytes(b, (char *)&l, 1, &left); b = put_bytes(b, r, flen, &left); b = put_bytes(b, "", 1, &left); b = put_bytes(b, &neg, 1, &left); ++items; r = sep + 1; } if (!b) { debug(0, "Error: query buffer too small. Please file a bug report!"); return (0); } put_long_little(b - buf - 4, buf); buf[5] = items; return (qserver_send(server, buf, sizeof(buf) - left) > 0); } query_status_t deal_with_ut2004master_packet(struct qserver *server, char *rawpkt, int pktlen) { unsigned char *state = (unsigned char *)&server->master_query_tag[0]; md5_state_t md5; if (!pktlen) { ut2004_server_done(server); goto cleanup_out; } server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); switch (*state) { case STATE_CHALLENGE: // ensure at least one byte challenge, fit into buffer, // match challenge, null terminated if ((pktlen < 4 + 1 + 1 + 1) || (pktlen > 4 + 1 + 8 + 1) || (rawpkt[pktlen - 1] != '\0')) { malformed_packet(server, "invalid challenge"); goto cleanup_out; } else { char response[sizeof(challenge_response)]; char *challenge = rawpkt + 5; char sum[16]; memcpy(response, challenge_response, sizeof(challenge_response)); debug(2, "challenge: %s", challenge); md5_init(&md5); md5_append(&md5, (unsigned char *)cdkey, CD_KEY_LENGTH); md5_finish(&md5, (unsigned char *)sum); bin2hex(sum, 16, response + RESPONSE_OFFSET_CDKEY); md5_init(&md5); md5_append(&md5, (unsigned char *)cdkey, CD_KEY_LENGTH); md5_append(&md5, (unsigned char *)challenge, strlen(challenge)); md5_finish(&md5, (unsigned char *)sum); bin2hex(sum, 16, response + RESPONSE_OFFSET_CHALLENGE); if (qserver_send(server, response, sizeof(response))) { goto cleanup_out; } server->server_name = MASTER; *state = STATE_APPROVED; } break; case STATE_APPROVED: if ((pktlen != sizeof(approved)) || (0 != memcmp(rawpkt, approved, pktlen))) { malformed_packet(server, "CD key not approved"); goto cleanup_out; } debug(2, "got approval, sending verify"); if (qserver_send(server, approved_response, sizeof(approved_response))) { goto cleanup_out; } *state = STATE_VERIFIED; break; case STATE_VERIFIED: if ((pktlen != sizeof(verified)) || (0 != memcmp(rawpkt, verified, pktlen))) { malformed_packet(server, "CD key not verified"); goto cleanup_out; } debug(2, "CD key verified, sending query"); if (ut2004_send_query(server)) { goto cleanup_out; } *state = STATE_LISTING; break; case STATE_LISTING: // first packet. contains number of servers to expect if (!server->saved_data.pkt_id) { /* * server->saved_data.data = malloc(pktlen); * memcpy(server->saved_data.data, rawpkt, pktlen); * server->saved_data.datalen = pktlen; */ server->saved_data.pkt_id = 1; if (pktlen == 9) { unsigned num = swap_long_from_little(rawpkt + 4); debug(2, "expect %u servers", num); #if 1 if (num < 10000) { server->master_pkt_len = num * 6; server->master_pkt = (char *)realloc(server->master_pkt, server->master_pkt_len); } #endif } } else if (pktlen < 4) { malformed_packet(server, "packet too short"); goto cleanup_out; } else { char *p = rawpkt; unsigned recordlen = 0; if (server->saved_data.next) { unsigned need = 0; SavedData *data = server->saved_data.next; // nasty, four bytes of record length are split up. since // we alloc'ed at least four bytes we just copy the 4-x // bytes to data->data if (data->datalen < 4) { need = 4 - data->datalen; debug(2, "need %d bytes more for recordlen", need); if (need > pktlen) { // XXX ok, im lazy now. Stupid server can't even // send four bytes in a row malformed_packet(server, "chunk too small"); goto cleanup_out; } memcpy(data->data + data->datalen, p, need); p += need; data->datalen = 4; } recordlen = swap_long_from_little(data->data); if (!recordlen || (recordlen > MAX_LISTING_RECORD_LEN)) { malformed_packet(server, "record lengthx %x out of range, position %d", recordlen, (int)(p - rawpkt)); goto cleanup_out; } need = 4 + recordlen - data->datalen; debug(2, "recordlen: %d, saved: %d, pkglen: %d, needed: %d", recordlen, data->datalen, pktlen, need); if (need <= pktlen) { data->data = realloc(data->data, 4 + recordlen); memcpy(data->data + data->datalen, p, need); ut2004_parse_record(server, data->data); p += need; free(data->data); free(data); server->saved_data.next = NULL; } } while (!server->saved_data.next && p - rawpkt + 4 < pktlen) { recordlen = swap_long_from_little(p); // record too large if (!recordlen || (recordlen > MAX_LISTING_RECORD_LEN)) { malformed_packet(server, "record length %x out of range, position %d", recordlen, (int)(p - rawpkt)); goto cleanup_out; } // recordlen itself is four bytes recordlen += 4; // record fully inside packet if (p - rawpkt + recordlen <= pktlen) { ut2004_parse_record(server, p); p += recordlen; } else { break; } } // record continues in next packet. save it. if (p - rawpkt < pktlen) { SavedData *data = server->saved_data.next; unsigned tosave = pktlen - (p - rawpkt); if (!data) { data = malloc(sizeof(SavedData)); data->data = malloc(tosave < 4 ? 4 : tosave); // alloc at least four bytes data->datalen = tosave; memcpy(data->data, p, data->datalen); data->next = NULL; server->saved_data.next = data; debug(1, "saved %d bytes", data->datalen); } else { data->data = realloc(data->data, data->datalen + tosave); memcpy(data->data + data->datalen, p, tosave); data->datalen += tosave; debug(1, "saved %d bytes (+)", data->datalen); } } } break; } debug(2, "%d servers total", server->n_servers / 6); return (INPROGRESS); cleanup_out: server->master_pkt_len = server->n_servers; server->n_servers /= 6; return (DONE_FORCE); } static const char hexchar[] = "0123456789abcdef"; static void bin2hex(const char *in, size_t len, char *out) { char *o = out + len * 2; in += len; do { *--o = hexchar[*--in & 0x0F]; *--o = hexchar[(*in >> 4) & 0x0F]; } while (o != out); } // vim: sw=4 ts=4 noet qstat-2.17/ut2004.h000066400000000000000000000006441412457473700137300ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * debug helper functions * Copyright 2004 Ludwig Nussel * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_UT2004_H #define QSTAT_UT2004_H #include "qstat.h" query_status_t send_ut2004master_request_packet(struct qserver *server); query_status_t deal_with_ut2004master_packet(struct qserver *server, char *rawpkt, int pktlen); #endif qstat-2.17/utils.c000066400000000000000000000073601412457473700141270ustar00rootroot00000000000000/* * Utility Functions * */ #include "utils.h" #ifndef _WIN32 #include #include #endif #if !HAVE_STRNSTR /* * strnstr - * Copyright (c) 2001 Mike Barcroft * Copyright (c) 1990, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Chris Torek. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by the University of * California, Berkeley and its contributors. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include char * qstat_strnstr(const char *s, const char *find, size_t slen) { char c, sc; size_t len; if ((c = *find++) != '\0') { len = strlen(find); do { do { if ((slen-- < 1) || ((sc = *s++) == '\0')) { return (NULL); } } while (sc != c); if (len > slen) { return (NULL); } } while (strncmp(s, find, len) != 0); s--; } return ((char *)s); } #endif /* !HAVE_STRNSTR */ #if !HAVE_STRNDUP #include #include char * strndup(const char *string, size_t len) { char *result; result = (char *)malloc(len + 1); memcpy(result, string, len); result[len] = '\0'; return (result); } #endif /* !HAVE_STRNDUP */ #if !HAVE_ERR_H #include #include #include void err(int eval, const char *fmt, ...) { va_list list; va_start(list, fmt); warn(fmt, list); va_end(list); exit(eval); } void warn(const char *fmt, ...) { va_list list; va_start(list, fmt); if (fmt) { fprintf(stderr, fmt, list); } fprintf(stderr, "%s\n", strerror(errno)); va_end(list); } #endif /* HAVE_ERR_H */ #include // NOTE: replace must be smaller or equal in size to find char * str_replace(char *source, char *find, char *replace) { char *s = strstr(source, find); int rlen = strlen(replace); int flen = strlen(find); if (rlen > flen) { err(EX_SOFTWARE, "str_replace: replace is larger than find"); } while (NULL != s) { strncpy(s, replace, rlen); // -Wstringop-truncation warning here is a false positive. if (rlen < flen) { strcpy(s + rlen, s + flen); } s += rlen; s = strstr(s, find); } return (source); } qstat-2.17/utils.h000066400000000000000000000030471412457473700141320ustar00rootroot00000000000000/* * Utility Functions * Copyright 2012 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_UTILS_H #define QSTAT_UTILS_H #ifndef _WIN32 #include #include #ifdef HAVE_CONFIG_H #include "gnuconfig.h" #endif #endif // BSD has strnstr #if defined(__FreeBSD__) || defined(__MidnightBSD__) #ifndef HAVE_STRNSTR #define HAVE_STRNSTR 1 #endif /* HAVE_STRNSTR */ #endif #ifndef _WIN32 #ifndef HAVE_ERR_H #define HAVE_ERR_H 1 #endif /* HAVE_ERR */ #endif #if !HAVE_STRNSTR #include char *qstat_strnstr(const char *s, const char *find, size_t slen); #define strnstr(s, find, slen) qstat_strnstr(s, find, slen) #endif #if !HAVE_STRNDUP #include char *strndup(const char *string, size_t len); #endif #ifndef EX_OSERR #define EX_OSERR 71 /* system error (e.g., can't fork) */ #endif #ifndef EX_SOFTWARE #define EX_SOFTWARE 70 /* An internal software error has been detected */ #endif #if !HAVE_ERR_H void err(int eval, const char *fmt, ...); void warn(const char *fmt, ...); #endif #if defined(_MSC_VER) && _MSC_VER < 1600 typedef __int8 int8_t; typedef unsigned __int8 uint8_t; typedef __int16 int16_t; typedef unsigned __int16 uint16_t; typedef __int32 int32_t; typedef unsigned __int32 uint32_t; typedef __int64 int64_t; typedef unsigned __int64 uint64_t; #elif defined(_MSC_VER) // && _MSC_VER >= 1600 #include #else #include #endif char *str_replace(char *, char *, char *); #endif qstat-2.17/ventrilo.c000066400000000000000000000260071412457473700146300ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * Ventrilo query protocol * Algorithm by Luigi Auriemma * QStat port 2010 by Michael Willigens * Fixes by Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #include #ifndef _WIN32 #include #include #include #define strtok_ret strtok_r #define VENTRILO_RAND random() #else #include #define strtok_ret strtok_s #define VENTRILO_RAND rand() #endif #include #include #include #include "debug.h" #include "qstat.h" #include "packet_manip.h" int VENTRILO_COMMAND_GENERIC_INFO = 1; int VENTRILO_COMMAND_DETAILED_INFO = 2; // includes generic infos int VENTRILO_COMMAND_DIFFERENT = 7; typedef struct { unsigned short pckkey; // key for decoding this header unsigned short zero; // ever 0 unsigned short cmd; // command number: 1 generic info, 2 for details unsigned short id; // packet ID used for tracking the replies unsigned short totlen; // total data size (for data splitted in packets) unsigned short len; // size of the data in this packet (max 492) unsigned short totpck; // total amount of packets (max 32) unsigned short pck; // current packet number unsigned short datakey; // key for decoding the data unsigned short crc; // checksum of the total plain-text data } ventrilo_udp_head; const static unsigned char ventrilo_udp_encdata_head[] = "\x80\xe5\x0e\x38\xba\x63\x4c\x99\x88\x63\x4c\xd6\x54\xb8\x65\x7e" "\xbf\x8a\xf0\x17\x8a\xaa\x4d\x0f\xb7\x23\x27\xf6\xeb\x12\xf8\xea" "\x17\xb7\xcf\x52\x57\xcb\x51\xcf\x1b\x14\xfd\x6f\x84\x38\xb5\x24" "\x11\xcf\x7a\x75\x7a\xbb\x78\x74\xdc\xbc\x42\xf0\x17\x3f\x5e\xeb" "\x74\x77\x04\x4e\x8c\xaf\x23\xdc\x65\xdf\xa5\x65\xdd\x7d\xf4\x3c" "\x4c\x95\xbd\xeb\x65\x1c\xf4\x24\x5d\x82\x18\xfb\x50\x86\xb8\x53" "\xe0\x4e\x36\x96\x1f\xb7\xcb\xaa\xaf\xea\xcb\x20\x27\x30\x2a\xae" "\xb9\x07\x40\xdf\x12\x75\xc9\x09\x82\x9c\x30\x80\x5d\x8f\x0d\x09" "\xa1\x64\xec\x91\xd8\x8a\x50\x1f\x40\x5d\xf7\x08\x2a\xf8\x60\x62" "\xa0\x4a\x8b\xba\x4a\x6d\x00\x0a\x93\x32\x12\xe5\x07\x01\x65\xf5" "\xff\xe0\xae\xa7\x81\xd1\xba\x25\x62\x61\xb2\x85\xad\x7e\x9d\x3f" "\x49\x89\x26\xe5\xd5\xac\x9f\x0e\xd7\x6e\x47\x94\x16\x84\xc8\xff" "\x44\xea\x04\x40\xe0\x33\x11\xa3\x5b\x1e\x82\xff\x7a\x69\xe9\x2f" "\xfb\xea\x9a\xc6\x7b\xdb\xb1\xff\x97\x76\x56\xf3\x52\xc2\x3f\x0f" "\xb6\xac\x77\xc4\xbf\x59\x5e\x80\x74\xbb\xf2\xde\x57\x62\x4c\x1a" "\xff\x95\x6d\xc7\x04\xa2\x3b\xc4\x1b\x72\xc7\x6c\x82\x60\xd1\x0d"; const static unsigned char ventrilo_udp_encdata_data[] = "\x82\x8b\x7f\x68\x90\xe0\x44\x09\x19\x3b\x8e\x5f\xc2\x82\x38\x23" "\x6d\xdb\x62\x49\x52\x6e\x21\xdf\x51\x6c\x76\x37\x86\x50\x7d\x48" "\x1f\x65\xe7\x52\x6a\x88\xaa\xc1\x32\x2f\xf7\x54\x4c\xaa\x6d\x7e" "\x6d\xa9\x8c\x0d\x3f\xff\x6c\x09\xb3\xa5\xaf\xdf\x98\x02\xb4\xbe" "\x6d\x69\x0d\x42\x73\xe4\x34\x50\x07\x30\x79\x41\x2f\x08\x3f\x42" "\x73\xa7\x68\xfa\xee\x88\x0e\x6e\xa4\x70\x74\x22\x16\xae\x3c\x81" "\x14\xa1\xda\x7f\xd3\x7c\x48\x7d\x3f\x46\xfb\x6d\x92\x25\x17\x36" "\x26\xdb\xdf\x5a\x87\x91\x6f\xd6\xcd\xd4\xad\x4a\x29\xdd\x7d\x59" "\xbd\x15\x34\x53\xb1\xd8\x50\x11\x83\x79\x66\x21\x9e\x87\x5b\x24" "\x2f\x4f\xd7\x73\x34\xa2\xf7\x09\xd5\xd9\x42\x9d\xf8\x15\xdf\x0e" "\x10\xcc\x05\x04\x35\x81\xb2\xd5\x7a\xd2\xa0\xa5\x7b\xb8\x75\xd2" "\x35\x0b\x39\x8f\x1b\x44\x0e\xce\x66\x87\x1b\x64\xac\xe1\xca\x67" "\xb4\xce\x33\xdb\x89\xfe\xd8\x8e\xcd\x58\x92\x41\x50\x40\xcb\x08" "\xe1\x15\xee\xf4\x64\xfe\x1c\xee\x25\xe7\x21\xe6\x6c\xc6\xa6\x2e" "\x52\x23\xa7\x20\xd2\xd7\x28\x07\x23\x14\x24\x3d\x45\xa5\xc7\x90" "\xdb\x77\xdd\xea\x38\x59\x89\x32\xbc\x00\x3a\x6d\x61\x4e\xdb\x29"; const static unsigned short ventrilo_crc_table[] = { 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef, 0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6, 0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de, 0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485, 0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d, 0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4, 0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc, 0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823, 0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b, 0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12, 0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a, 0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41, 0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49, 0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70, 0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78, 0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f, 0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067, 0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e, 0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256, 0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d, 0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, 0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c, 0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634, 0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab, 0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3, 0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a, 0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92, 0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9, 0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1, 0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8, 0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0 }; void ventrilo_udp_head_dec(unsigned char *data) { int i; unsigned short *p; unsigned char a1; unsigned char a2; p = (unsigned short *)data; data += 2; *p = ntohs(*p); a1 = *p; if (!a1) { return; } a2 = *p >> 8; for (i = 0; i < 18; i++) { data[i] -= ventrilo_udp_encdata_head[a2] + (i % 5); a2 += a1; } for (i = 0; i < 9; i++) { p++; *p = ntohs(*p); } } void ventrilo_udp_head_enc(unsigned char *data) { int i; unsigned short *p; unsigned char a1; unsigned char a2; p = (unsigned short *)data; data += 2; *p = (((VENTRILO_RAND * 0x343fd) + 0x269ec3) >> 16) & 0x7fff; a1 = *p; a2 = *p >> 8; if (!a2) { a2 = 69; *p |= (a2 << 8); } for (i = 0; i < 10; i++) { *p = htons(*p); p++; } for (i = 0; i < 18; i++) { data[i] += ventrilo_udp_encdata_head[a2] + (i % 5); a2 += a1; } } void ventrilo_udp_data_dec(unsigned char *data, int len, unsigned short key) { int i; unsigned char a1; unsigned char a2; a1 = key; if (!a1) { return; } a2 = key >> 8; for (i = 0; i < len; i++) { data[i] -= ventrilo_udp_encdata_data[a2] + (i % 72); a2 += a1; } } unsigned short ventrilo_udp_data_enc(unsigned char *data, int len) { int i; unsigned short key; unsigned char a1; unsigned char a2; key = (((VENTRILO_RAND * 0x343fd) + 0x269ec3) >> 16) & 0x7fff; a1 = key; a2 = key >> 8; if (!a2) { a2 = 1; key |= (a2 << 8); } for (i = 0; i < len; i++) { data[i] += ventrilo_udp_encdata_data[a2] + (i % 72); a2 += a1; } return (key); } unsigned short ventrilo_udp_crc(unsigned char *data, int len) { unsigned short crc = 0; while (len--) { crc = ventrilo_crc_table[crc >> 8] ^ *data ^ (crc << 8); data++; } return (crc); } int buildVentriloRequest(unsigned char *buf, int cmd, char *pass, int id) { ventrilo_udp_head *stat = (ventrilo_udp_head *)buf; unsigned char *data = buf + 20; strncpy((char *)data, pass, 16); stat->zero = 0; stat->cmd = cmd; stat->id = id; stat->totlen = 16; stat->len = 16; stat->totpck = 1; stat->pck = 0; stat->crc = ventrilo_udp_crc(data, 16); stat->datakey = ventrilo_udp_data_enc(data, 16); ventrilo_udp_head_enc(buf); return (20 + 16); } query_status_t send_ventrilo_request_packet(struct qserver *server) { char buf[1500]; char *password = get_param_value(server, "password", ""); int size = buildVentriloRequest((unsigned char *)buf, VENTRILO_COMMAND_DETAILED_INFO, password, server->challenge); server->n_requests++; gettimeofday(&server->packet_time1, NULL); debug(2, "send status request"); return (send_packet(server, buf, size)); } query_status_t deal_with_ventrilo_packet(struct qserver *server, char *rawpkt, int pktlen) { char *line, *last_line; debug(3, "deal_with_ventrilo_packet: state = %ld", server->challenge); if (20 > pktlen) { debug(2, "wrong or incomplete packet received. retrying..."); return (INPROGRESS); } /* add to the data previously saved to get a full packet*/ if (!server->combined) { ventrilo_udp_head *header = (ventrilo_udp_head *)rawpkt; unsigned char *data = (unsigned char *)header + 20; ventrilo_udp_head_dec((unsigned char *)header); ventrilo_udp_data_dec(data, header->len, header->datakey); if (header->id != server->challenge) { debug(2, "ignoring unnecessary packet..."); return (INPROGRESS); } debug(3, "decoded ventrilo fragment: id:%i - len:%i - totlen:%i - totpck:%i - zero:%i - cmd:%i", header->id, header->len, header->totlen, header->totpck, header->zero, header->cmd); add_packet(server, header->id, packet_count(server), header->totpck, header->len, (char *)data, 0); return (combine_packets(server)); } debug(4, "combined ventrilo packet: %s", rawpkt); server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); line = strtok_ret(rawpkt, "\n", &last_line); debug(3, "processing detailed response..."); while (line != NULL) { debug(4, "processing line: %s", line); if (0 == strncmp(line, "NAME: ", 6)) { server->server_name = strdup(line + 6); } else if (0 == strncmp(line, "MAXCLIENTS: ", 12)) { server->max_players = atoi(line + 12); } else if (0 == strncmp(line, "CLIENTCOUNT: ", 13)) { server->num_players = atoi(line + 13); } else if (0 == strncmp(line, "CLIENT: ", 8)) { // Client e.g. // CLIENT: UID=406,ADMIN=0,CID=17,PHAN=0,PING=73,SEC=2245,NAME=Darrimu,COMM= struct player *player = add_player(server, server->n_player_info); char *last_info = NULL; char *player_info = strtok_ret(line + 8, ",", &last_info); while (NULL != player_info) { debug(5, "player info: %s", player_info); if (0 == strncmp(player_info, "NAME=", 5)) { player->name = strdup(player_info + 5); } else if (strncmp(player_info, "PING=", 5)) { player->ping = atoi(player_info + 5); } else if (0 == strncmp(player_info, "CID=", 4)) { player->team = atoi(player_info + 4); } else if (strncmp(player_info, "SEC=", 4)) { player->connect_time = atoi(player_info + 4); } player_info = strtok_ret(NULL, ",", &last_info); } } line = strtok_ret(NULL, "\n", &last_line); } server->map_name = strdup("N/A"); return (DONE_FORCE); } qstat-2.17/ventrilo.h000066400000000000000000000010531412457473700146270ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * Ventrilo query protocol * Algorithm by Luigi Auriemma (Reversing and first version in c) * Copyright 2010 Michael Willigens * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_VENTRILO_H #define QSTAT_VENTRILO_H #include "qserver.h" // Packet processing methods query_status_t deal_with_ventrilo_packet(struct qserver *server, char *pkt, int pktlen); query_status_t send_ventrilo_request_packet(struct qserver *server); #endif qstat-2.17/version.h.tmpl000066400000000000000000000004201412457473700154220ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * Copyright 2021 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_VERSION_H #define QSTAT_VERSION_H #ifndef QSTAT_VERSION #define QSTAT_VERSION "CHANGEME" #endif #endif qstat-2.17/wic.c000066400000000000000000000104461412457473700135500ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * World in Conflict Protocol * Copyright 2007 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms * */ #include #ifndef _WIN32 #include #endif #include #include #include #include "debug.h" #include "qstat.h" #include "packet_manip.h" query_status_t send_wic_request_packet(struct qserver *server) { char buf[256]; int serverport = get_param_i_value(server, "port", 0); char *password = get_param_value(server, "password", "N/A"); change_server_port(server, serverport, 1); if (get_player_info) { server->flags |= TF_PLAYER_QUERY | TF_RULES_QUERY; sprintf(buf, "%s\x0d\x0a/listsettings\x0d\x0a/listplayers\x0d\x0a/exit\x0d\x0a", password); server->saved_data.pkt_index = 2; } else { server->flags |= TF_STATUS_QUERY; sprintf(buf, "%s\x0d\x0a/listsettings\x0d\x0a/exit\x0d\x0a", password); server->saved_data.pkt_index = 1; } return (send_packet(server, buf, strlen(buf))); } query_status_t deal_with_wic_packet(struct qserver *server, char *rawpkt, int pktlen) { char *s, *end, *team = NULL; int mode = server->n_servers, slot, score; char name[256], role[256]; debug(2, "processing n_requests %d, retry1 %d, n_retries %d, delta %d", server->n_requests, server->retry1, n_retries, time_delta(&packet_recv_time, &server->packet_time1)); server->ping_total += time_delta(&packet_recv_time, &server->packet_time1); server->n_requests++; if (0 == pktlen) { // Invalid password return (REQ_ERROR); } gettimeofday(&server->packet_time1, NULL); rawpkt[pktlen] = '\0'; end = &rawpkt[pktlen]; s = rawpkt; while (NULL != s) { int len = strlen(s); *(s + len - 2) = '\0'; // strip off \x0D\x0A debug(2, "Line[%d]: %s", mode, s); if (0 == mode) { // Settings // TODO: make parse safe if (0 == strncmp(s, "Settings: ", 9)) { // Server Rule char *key = s + 10; char *value = strchr(key, ' '); *value = '\0'; value++; debug(2, "key: '%s' = '%s'", key, value); if (0 == strcmp("myGameName", key)) { server->server_name = strdup(value); } else if (0 == strcmp("myMapFilename", key)) { server->map_name = strdup(value); } else if (0 == strcmp("myMaxPlayers", key)) { server->max_players = atoi(value); } else if (0 == strcmp("myCurrentNumberOfPlayers", key)) { server->num_players = atoi(value); } else { add_rule(server, key, value, NO_FLAGS); } } else if (0 == strcmp("Listing players", s)) { // end of rules request server->saved_data.pkt_index--; mode++; } else if (0 == strcmp("Exit confirmed.", s)) { server->n_servers = mode; return (DONE_FORCE); } } else if (1 == mode) { // Player info if (0 == strncmp(s, "Team: ", 6)) { team = s + 6; debug(2, "Team: %s", team); } else if (4 == sscanf(s, "Slot: %d Role: %s Score: %d Name: %255[^\x0d\x0a]", &slot, role, &score, name)) { // Player info struct player *player = add_player(server, server->n_player_info); if (NULL != player) { player->flags |= PLAYER_FLAG_DO_NOT_FREE_TEAM; player->name = strdup(name); player->score = score; player->team_name = team; player->tribe_tag = strdup(role); // Indicate if its a bot player->type_flag = (0 == strcmp(name, "Computer: Balanced")) ? 1 : 0; } debug(2, "player %d, role %s, score %d, name %s", slot, role, score, name); } else if (3 == sscanf(s, "Slot: %d Role: Score: %d Name: %255[^\x0d\x0a]", &slot, &score, name)) { // Player info struct player *player = add_player(server, server->n_player_info); if (NULL != player) { player->flags |= PLAYER_FLAG_DO_NOT_FREE_TEAM; player->name = strdup(name); player->score = score; player->team_name = team; // Indicate if its a bot player->type_flag = (0 == strcmp(name, "Computer: Balanced")) ? 1 : 0; } debug(2, "player %d, score %d, name %s", slot, score, name); } else if (0 == strcmp("Exit confirmed.", s)) { server->n_servers = mode; return (DONE_FORCE); } } s += len; if (s + 1 < end) { s++;// next line } else { s = NULL; } } server->n_servers = mode; if (0 == server->saved_data.pkt_index) { server->map_name = strdup("N/A"); return (DONE_FORCE); } return (INPROGRESS); } qstat-2.17/wic.h000066400000000000000000000006561412457473700135570ustar00rootroot00000000000000/* * qstat * by Steve Jankowski * * World in Conflict Protocol * Copyright 2007 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_WIC_H #define QSTAT_WIC_H #include "qserver.h" // Packet processing methods query_status_t deal_with_wic_packet(struct qserver *server, char *pkt, int pktlen); query_status_t send_wic_request_packet(struct qserver *server); #endif qstat-2.17/xform.c000066400000000000000000000356031412457473700141230ustar00rootroot00000000000000/* * xform functions * * The methods / structures provide a fully dynamic method for * manipulating server & player names for servers before output * * The original string passed in is never altered. * * We use a dynamically allocated set of buffers to ensure we have * no memory collision. These buffers are persistent for the life * of the qstat process to avoid constant malloc, realloc and free * calls. */ #include #include #include #include #include "utils.h" #include "xform.h" /* * Flag controlling if xform methods do any processing except transforming * NULL to blank string */ int xform_names = 1; /* Flag to determine if we strip all unprintable characters */ int xform_strip_unprintable = 0; /* Flag to determine if we should print hex based player names */ int xform_hex_player_names = 0; /* Flag to determine if we should print hex based server names */ int xform_hex_server_names = 0; /* Flag to determine if we should script carets */ int xform_strip_carets = 1; /* Flag to detemine if we should generate html names */ int xform_html_names = -1; extern int html_mode; /* xform buffer structure */ typedef struct xform { char *buf; size_t size; struct xform *next; } xform; /* First xform buffer */ static struct xform *xform_bufs = NULL; /* Current xform buffer */ static struct xform *xform_buf = NULL; /* Size of the used amount of the current xform buffer */ static size_t xform_used; /* Is there a currently open font tag in the xform buffer */ static int xform_font_tag; /* Min size of an xform buffer */ static const int xform_buf_min = 256; /*** Private Methods ***/ static int xform_html_entity(const char c, char *dest) { if (html_mode) { switch (c) { case '<': strcpy(dest, "<"); return (4); case '>': strcpy(dest, ">"); return (4); case '&': strcpy(dest, "&"); return (5); default: break; } } return (0); } /* * return the current xform buffer string buffer */ static char * xform_strbuf() { return (xform_buf->buf); } /* * Ensure a the current xform buffer is at least size */ static void xform_buf_resize(size_t size, char **bufp) { char *oldbuf; if (size <= xform_buf->size) { // already big enough xform_used = size; return; } oldbuf = xform_buf->buf; if ((xform_buf->buf = realloc(xform_buf->buf, size)) == NULL) { err(EX_OSERR, NULL); } if ((xform_buf->buf != oldbuf) && (bufp != NULL)) { // memory block moved update bufp *bufp = xform_buf->buf + ((*bufp) - oldbuf); } xform_buf->size = size; xform_used = size; } /* * snprintf a string into an xform buffer expanding the buffer * by the size of new string if needed */ static int xform_snprintf(char **buf, size_t size, const char *format, ...) { int ret; va_list args; // ensure buf is large enough xform_buf_resize(xform_used + size, buf); va_start(args, format); ret = vsnprintf(*buf, size, format, args); va_end(args); return (ret); } /* * Copy a string into an xform buffer expanding the buffer * by the size of new string if needed */ static int xform_strcpy(char **buf, const char *str) { size_t size; size = strlen(str); // ensure buf is large enough xform_buf_resize(xform_used + size, buf); (void)strcpy(*buf, str); return (size); } /* * Close previous html color and start a new one * Returns the size of the string added to the buffer */ static int xform_html_color(char **buf, const char *font_color) { size_t size; int inc; if (xform_html_names != 1) { return (0); } size = 15 + strlen(font_color); if (xform_font_tag) { size += 7; } inc = xform_snprintf(buf, size, "%s", xform_font_tag ? "" : "", font_color); xform_font_tag = 1; return (inc); } /* * Reset the xform buffers for re-use */ static void xform_buf_reset() { xform_buf = xform_bufs; xform_font_tag = 0; xform_used = 0; } /* * Allocate and init a new xform buffer ensuring min size is used */ static char * xform_buf_create(size_t size) { struct xform *next; char *buf; /* * We use a min size which should be reasonable for * 99.9% of server and player names */ size = (size >= xform_buf_min) ? size + 1 : xform_buf_min; if ((next = malloc(sizeof(xform))) == NULL) { err(EX_OSERR, NULL); } if ((buf = malloc(sizeof(char) * size)) == NULL) { err(EX_OSERR, NULL); } next->buf = buf; next->next = NULL; next->size = size; xform_used = size; xform_font_tag = 0; buf[0] = '\0'; if (xform_buf == NULL) { xform_bufs = next; } else { xform_buf->next = next; } xform_buf = next; return (next->buf); } /* * Get a xform buffer allocating a new one or expanding an * old one as required */ static char * xform_buf_get(size_t size) { if ((xform_buf == NULL) || (xform_buf->next == NULL)) { return (xform_buf_create(size)); } xform_buf = xform_buf->next; if (size > xform_buf->size) { xform_buf_resize(size, NULL); } else { xform_used = size; xform_font_tag = 0; } xform_buf->buf[0] = '\0'; return (xform_buf->buf); } static char *quake3_escape_colors[8] = { "black", "red", "green", "yellow", "blue", "cyan", "magenta", "white" }; /* * Transform a quake 3 string */ static char * xform_name_q3(char *string, struct qserver *server) { unsigned char *s; char *q; q = xform_strbuf(); s = (unsigned char *)string; for ( ; *s; s++) { if ((*s == '^') && (*(s + 1) != '^')) { if (*(s + 1) == '\0') { break; } if (xform_html_names == 1) { q += xform_html_color(&q, quake3_escape_colors[*(s + 1) & 0x7]); s++; } else if (xform_strip_carets) { s++; } else { *q++ = *s; } } else { int inc = xform_html_entity((char)*s, q); if (0 != inc) { q += inc; } else if (isprint(*s)) { *q++ = *s; } else if (*s == '\033') { /* skip */ } else if (*s == 0x80) { *q++ = '('; } else if (*s == 0x81) { *q++ = '='; } else if (*s == 0x82) { *q++ = ')'; } else if ((*s == 0x10) || (*s == 0x90)) { *q++ = '['; } else if ((*s == 0x11) || (*s == 0x91)) { *q++ = ']'; } else if ((*s >= 0x92) && (*s <= 0x9a)) { *q++ = *s - 98; } else if ((*s >= 0xa0) && (*s <= 0xe0)) { *q++ = *s - 128; } else if ((*s >= 0xe1) && (*s <= 0xfa)) { *q++ = *s - 160; } else if ((*s >= 0xfb) && (*s <= 0xfe)) { *q++ = *s - 128; } } } *q = '\0'; return (xform_strbuf()); } /* * Transform a tribes 2 string */ static char * xform_name_t2(char *string, struct qserver *server) { char *s, *q; q = xform_strbuf(); s = string; for ( ; *s; s++) { int inc = xform_html_entity(*s, q); if (0 != inc) { q += inc; continue; } else if (isprint(*s)) { *q++ = *s; continue; } if ((xform_html_names == 1) && (s[1] != '\0')) { char *font_color; switch (*s) { case 0x8: font_color = "white"; break; /* normal */ case 0xb: font_color = "yellow"; break; /* tribe tag */ case 0xc: font_color = "blue"; break; /* alias */ case 0xe: font_color = "green"; break; /* bot */ default: font_color = NULL; } if (font_color) { q += xform_html_color(&q, font_color); } } } *q = '\0'; return (xform_strbuf()); } static const char *unreal_rgb_colors[] = { "#F0F8FF", "#FAEBD7", "#00FFFF", "#7FFFD4", "#F0FFFF", "#F5F5DC", "#FFE4C4", "#000000", "#FFEBCD", "#0000FF", "#8A2BE2", "#A52A2A", "#DEB887", "#5F9EA0", "#7FFF00", "#D2691E", "#FF7F50", "#6495ED", "#FFF8DC", "#DC143C", "#00FFFF", "#00008B", "#008B8B", "#B8860B", "#A9A9A9", "#006400", "#BDB76B", "#8B008B", "#556B2F", "#FF8C00", "#9932CC", "#8B0000", "#E9967A", "#8FBC8F", "#483D8B", "#2F4F4F", "#00CED1", "#9400D3", "#FF1493", "#00BFFF", "#696969", "#1E90FF", "#B22222", "#FFFAF0", "#228B22", "#FF00FF", "#DCDCDC", "#F8F8FF", "#FFD700", "#DAA520", "#808080", "#008000", "#ADFF2F", "#F0FFF0", "#FF69B4", "#CD5C5C", "#4B0082", "#FFFFF0", "#F0E68C", "#E6E6FA", "#FFF0F5", "#7CFC00", "#FFFACD", "#ADD8E6", "#F08080", "#E0FFFF", "#FAFAD2", "#90EE90", "#D3D3D3", "#FFB6C1", "#FFA07A", "#20B2AA", "#87CEFA", "#778899", "#B0C4DE", "#FFFFE0", "#00FF00", "#32CD32", "#FAF0E6", "#FF00FF", "#800000", "#66CDAA", "#0000CD", "#BA55D3", "#9370DB", "#3CB371", "#7B68EE", "#00FA9A", "#48D1CC", "#C71585", "#191970", "#F5FFFA", "#FFE4E1", "#FFE4B5", "#FFDEAD", "#000080", "#FDF5E6", "#808000", "#6B8E23", "#FFA500", "#FF4500", "#DA70D6", "#EEE8AA", "#98FB98", "#AFEEEE", "#DB7093", "#FFEFD5", "#FFDAB9", "#CD853F", "#FFC0CB", "#DDA0DD", "#B0E0E6", "#800080", "#FF0000", "#BC8F8F", "#4169E1", "#8B4513", "#FA8072", "#F4A460", "#2E8B57", "#FFF5EE", "#A0522D", "#C0C0C0", "#87CEEB", "#6A5ACD", "#708090", "#FFFAFA", "#00FF7F", "#4682B4", "#D2B48C", "#008080", "#D8BFD8", "#FF6347", "#40E0D0", "#EE82EE", "#F5DEB3", "#FFFFFF", "#F5F5F5", "#FFFF00", "#9ACD32", }; /* * Transform a unreal 2 string */ static char * xform_name_u2(char *string, struct qserver *server) { unsigned char *s; char *q; q = xform_strbuf(); s = (unsigned char *)string; for ( ; *s; s++) { if (memcmp(s, "^\1", 2) == 0) { // xmp color s += 2; q += xform_html_color((char **)&s, unreal_rgb_colors[*s - 1]); } else if (memcmp(s, "\x1b", 1) == 0) { // ut2k4 color // A 3 byte array, for example { 0xF8, 0x40, 0x40 } // is encoded as a 8 char sized string including // the '#' prefix and the null-terminator: // { '#', 'F', '8', '4', '0', '4', '0', 0x00 } char color[8]; s += 1; sprintf(color, "#%02hhx%02hhx%02hhx", s[0], s[1], s[2]); q += xform_html_color(&q, color); s += 3; } else { int inc = xform_html_entity(*s, q); if (0 != inc) { q += inc; } else if (isprint(*s)) { *q++ = *s; } else if (0xa0 == *s) { *q++ = ' '; } } } *q = '\0'; return (xform_strbuf()); } /* * Transform a trackmania string */ static char * xform_name_tm(char *string, struct qserver *server) { char *s, *q; int open = 0; char c1, c2, c3; q = xform_strbuf(); s = string; for ( ; *s; s++) { if (*s == '$') { s++; switch (*s) { case 'i': case 'I': // italic if (xform_html_names == 1) { q += xform_strcpy(&q, ""); open++; } break; case 's': case 'S': // shadowed break; case 'w': case 'W': // wide break; case 'n': case 'N': // narrow break; case 'm': case 'M': // normal if (xform_html_names == 1) { q += xform_strcpy(&q, ""); open++; } break; case 'o': case 'O': // bold if (xform_html_names == 1) { q += xform_strcpy(&q, ""); open++; } break; case 'g': case 'G': // default color if (xform_html_names == 1) { q += xform_strcpy(&q, ""); open++; } break; case 'z': case 'Z': // reset all while (open) { q += xform_strcpy(&q, ""); open--; } break; case 't': case 'T': // capitalise if (xform_html_names == 1) { q += xform_strcpy(&q, ""); open++; } break; case '$': // literal $ *q++ = '$'; break; case '\0': // Unexpected end break; default: // color c3 = '\0'; c1 = *s; s++; c2 = *s; if (c2) { s++; c3 = *s; if (c3 && (xform_html_names == 1)) { q += xform_snprintf(&q, 34, "", c1, c1, c2, c2, c3, c3); open++; } } break; } } else { *q++ = *s; } } while (open) { q += xform_strcpy(&q, ""); open--; } *q = '\0'; return (xform_strbuf()); } static char *sof_colors[32] = { "FFFFFF", "FFFFFF", "FF0000", "00FF00", "FFFF00", "0000FF", "FF00FF", "00FFFF", "000000", "7F7F7F", "702D07", "7F0000", "007F00", "FFFFFF", "007F7F", "00007F", "564D28", "4C5E36", "370B65", "005572", "54647E", "1E2A63", "66097B", "705E61", "980053", "960018", "702D07", "54492A", "61A997", "CB8F39", "CF8316", "FF8020" }; /* * Transform a soldier of fortune player name */ static char * xform_name_sof(char *string, struct qserver *server) { unsigned char *s; char *q; q = xform_strbuf(); s = (unsigned char *)string; // The may not be the intention but is needed for q1 at least for ( ; *s; s++) { int inc = xform_html_entity(*s, q); if (0 != inc) { q += inc; continue; } if (*s < ' ') { q += xform_html_color(&q, sof_colors[*(s)]); } else if (isprint(*s)) { *q++ = *s; // ## more fixes below; double check against real sof servers } else if (*s >= 0xa0) { *q++ = *s & 0x7f; } else if ((*s >= 0x92) && (*s < 0x9c)) { *q++ = '0' + (*s - 0x92); } else if ((*s >= 0x12) && (*s < 0x1c)) { *q++ = '0' + (*s - 0x12); } else if ((*s == 0x90) || (*s == 0x10)) { *q++ = '['; } else if ((*s == 0x91) || (*s == 0x11)) { *q++ = ']'; } else if ((*s == 0xa) || (*s == 0xc) || (*s == 0xd)) { *q++ = ']'; } } *q = '\0'; return (xform_strbuf()); } /*** Public Methods ***/ /* * perform a printf containing xform_name based arguments */ int xform_printf(FILE *file, const char *format, ...) { int ret; va_list args; xform_buf_reset(); va_start(args, format); ret = vfprintf(file, format, args); va_end(args); return (ret); } /* * Clear out and free all memory used by xform buffers */ void xform_buf_free() { struct xform *cur; struct xform *next; for (cur = xform_bufs; cur != NULL; cur = next) { next = cur->next; if (cur->buf != NULL) { free(cur->buf); cur->buf = NULL; } free(cur); } xform_bufs = NULL; xform_buf = NULL; } /* * Transforms a string based on the details stored on the server */ char * xform_name(char *string, struct qserver *server) { char *buf, *bufp, *s; int is_server_name; if (string == NULL) { buf = xform_buf_get(1); strcpy(buf, "?"); return (buf); } if (!xform_names) { return (string); } s = string; if (xform_strip_unprintable) { buf = xform_buf_get(strlen(string)); bufp = buf; for ( ; *s; s++) { if (isprint(*s)) { *bufp = *s; bufp++; } } *bufp = '\0'; if (*buf == '\0') { strcpy(buf, "?"); return (buf); } s = buf; } is_server_name = (string == server->server_name); if ((xform_hex_player_names && !is_server_name) || (xform_hex_server_names && is_server_name)) { buf = xform_buf_get(strlen(s) * 2); bufp = buf; for ( ; *s; s++, bufp += 2) { sprintf(bufp, "%02hhx", *s); } *bufp = '\0'; return (buf); } buf = xform_buf_get(strlen(s)); if (server->type->flags & TF_QUAKE3_NAMES) { s = xform_name_q3(s, server); } else if (!is_server_name && (server->type->flags & TF_TRIBES2_NAMES)) { s = xform_name_t2(s, server); } else if (server->type->flags & TF_U2_NAMES) { s = xform_name_u2(s, server); } else if (server->type->flags & TF_TM_NAMES) { s = xform_name_tm(s, server); } else if (!is_server_name || server->type->flags & TF_SOF_NAMES) { // Catch all for NOT is_server_name OR TF_SOF_NAMES s = xform_name_sof(s, server); } if (xform_font_tag) { xform_strcpy(&s, ""); } return (s); } qstat-2.17/xform.h000066400000000000000000000005021412457473700141160ustar00rootroot00000000000000/* * xform Functions * Copyright 2013 Steven Hartland * * Licensed under the Artistic License, see LICENSE.txt for license terms */ #ifndef QSTAT_XFORM_H #define QSTAT_XFORM_H #include "qstat.h" void xform_buf_free(); int xform_printf(FILE *, const char *, ...); char *xform_name(char *, struct qserver *); #endif