Sfoglia il codice sorgente

flags after the compile object

Klumpp, Paul-Dieter 7 anni fa
commit
c4af8f96e8

+ 1368 - 0
src/masterserver/CHANGELOG

@@ -0,0 +1,1368 @@
+2005-05-28	Andre' Schulz	<chickenman@exhale.de>
+* plugins/libd3.c
+	added 3 variables to private data
+	fixed port byte-order in "servers" packet
+	minor change
+	bumped version number to 0.1.1
+
+* masterserver.h
+	bumped version number to 0.4.1
+
+* README
+	added a note on how to resolve port collision of Q3 and EF plugins
+
+* NEWS
+	added info on 0.4.1 release
+
+
+2005-05-26	Andre' Schulz	<chickenman@exhale.de>
+* docs/PROTOCOLS
+	added Enemy Territory section
+	minor changes and fixes
+
+* plugins/Makefile
+	added libet and librw targets
+
+* plugins/libet.c
+	new
+
+* plugins/librw.c
+	new
+
+* plugins/libexample.c
+	bumped version number to 0.4
+	added some comments
+
+
+2005-05-21	Andre' Schulz	<chickenman@exhale.de>
+* README
+	give credit to <XL*g0b>
+	bumped version number to 0.4
+	added Doom 3 specific information
+	miscellaneous changes
+	added STVEF1 specific information
+	added warning on STVEF1 and Q3 port collision
+
+* masterserver.c
+	update copyright notice
+	minor changes
+
+* docs/PROTOCOLS
+	added Half-Life section
+
+* plugins/libd3.c
+	minor changes
+	fixed a memory leak
+
+* plugins/libef.c
+	works now
+	rewrote the packet assembler
+	other bug fixes and changes
+	fixed a memory leak
+
+* plugins/libq3.c
+	rewrote packet assembler
+	miscellaneous fixes and changes
+	bumped version number to 0.8
+
+* tests/Makefile
+	added test-ef-heartbeat-getservers target
+	added test-ef-getmotd target
+
+* tests/test-q3-heartbeat-query.c
+	removed a debug message
+
+* tests/test-q3-heartbeat-getservers.c
+	zero packet buffer before using it
+
+* NEWS
+	updated for 0.4 release
+
+* masterserver.h
+	bumped version number to 0.4
+
+* tests/test-ef-getmotd.c
+	new
+
+* tests/test-q3-getmotd.c
+	zero buffer before using it
+	make challenge error message more verbose
+
+* tests/test-ef-heartbeat-getservers.c
+	new
+
+* plugins/libh2.c
+	bumped version number to 0.4
+
+* plugins/libq2.c
+	bumped version number to 0.4
+
+* plugins/libqw.c
+	bumped version number to 0.2
+
+
+2005-05-17	Andre' Schulz	<chickenman@exhale.de>
+* plugins/libef.c
+	new
+
+
+2005-05-15	Andre' Schulz	<chickenman@exhale.de>
+* docs/PROTOCOLS
+	added "statusResponse" packet of a populated server to STVEF1 section
+	added "getInfo" packet with challenge to Doom3 section
+	minor changes
+
+* plugins/Makefile
+	add libef target
+
+* plugins/libd3.c
+	minor changes and fixes
+
+
+2005-05-14	Andre' Schulz	<chickenman@exhale.de>
+* docs/PROTOCOLS
+	added "getservers" packet size information to STVEF1
+
+* plugins/libd3.c
+	fixed a glitch in the infoResponse parser
+	minor changes
+
+* plugins/libq3.c
+	use strdup() instead of calloc() and strncpy()
+
+* tests/test-d3-heartbeat-getServers.c
+	new
+
+
+2005-05-09	Andre' Schulz	<chickenman@exhale.de>
+* plugins/libd3.c
+	changed getinfo string
+	added challenge and fs_game variables to private data
+	hopefully fixed the infoResponse variables parser
+
+* plugins/libq2.c
+	minor change
+
+* plugins/libq3.c
+	fixed some off-by-one errors
+
+* plugins/libqw.c
+	minor change
+
+
+2005-04-29	Andre' Schulz	<chickenman@exhale.de>
+* logging.c
+	added vim modeline
+
+* masterserver.c
+	added vim modeline
+
+* plugins/libd3.c
+	added vim modeline
+	minor changes
+
+* plugins/libexample.c
+	added vim modeline
+	minor changes
+
+* plugins/libh2.c
+	added vim modeline
+	minor changes
+
+* plugins/libq2.c
+	added vim modeline
+	minor changes
+
+* plugins/libq3.c
+	added vim modeline
+	minor changes
+	new statusResponse parser
+	new getmotd parser
+
+* plugins/libqw.c
+	added vim modeline
+	minor changes
+
+
+2005-04-22	Andre' Schulz	<chickenman@exhale.de>
+* masterserver.c
+	zero the incoming packet buffer before using it
+	fixed an off-by-one error in recvfrom() call
+
+* plugins/libd3.c
+	remove dead code
+
+* plugins/libh2.c
+	remove dead code
+
+* plugins/libq2.c
+	remove dead code
+
+* plugins/libq3.c
+	remove dead code
+	simplified protocol number parsing in process_getservers()
+	minor changes
+
+* plugins/libqw.c
+	remove dead code
+
+
+2005-04-21	Andre' Schulz	<chickenman@exhale.de>
+* docs/PROTOCOLS
+	updates all over the place
+
+* Makefile
+	use $(libdir) instead of hardcoded paths
+
+* common.mk
+	use $(prefix) instead of hardcoded paths
+
+* masterserver.c
+	added -u and -g cmdline options to change user and group
+	give_up_root_privileges() renamed to change_user_and_group_to()
+	made help text more verbose
+	use the new API
+	minor changes and fixes
+
+* masterserver.h
+	removed plugin_name data type
+	introducing port_t
+
+* plugins/Makefile
+	use $(libdir) instead of hardcoded paths
+
+* plugins/libd3.c
+	use the new port_t data type
+
+* plugins/libexample.c
+	use the new port_t data type
+
+* plugins/libh2.c
+	use the new port_t data type
+
+* plugins/libq2.c
+	use the new port_t data type
+
+* plugins/libq3.c
+	use the new port_t data type
+
+* plugins/libqw.c
+	use the new port_t data type
+	add packetlen to process()
+
+* tests/Makefile
+	added test-d3-heartbeat-getServers
+	minor changes and fixes
+
+* tests/test-q2-heartbeat-query.c
+	removed hardcoded number of servers; now taken from command line
+	more verbose error message on test failure
+
+* tests/test-q2-shutdown.c
+	lots of fixes and changes
+	should work now =)
+
+
+2005-04-19	Andre' Schulz	<chickenman@exhale.de>
+* docs/PROTOCOLS
+	updated UT section
+
+
+2004-12-20	Andre' Schulz	<chickenman@exhale.de>
+* masterserver.h
+	added packet length to process()
+
+* masterserver.c
+	minor changes
+	added packet length parameter to process()
+
+* plugins/Makefile
+	added libd3
+
+* plugins/libh2.c
+	added packet length to process()
+
+* plugins/libq2.c
+	added packet length to process()
+
+* plugins/libq3.c
+	added packet length to process()
+
+* plugins/libd3.c
+	new
+
+* tests/test-q2-ping.c
+	clear reply buffer before using it
+	more verbose error message on test failure
+
+
+2004-11-30	Andre' Schulz	<chickenman@exhale.de>
+* common.mk
+	add -rdynamic to FreeBSD CFLAGS
+
+* masterserver.c
+	fixed wrong FreeBSD header <sys/limits.h>, should be <limits.h>
+
+* plugins/libq3.c
+	minor fix
+
+* docs/PROTOCOLS
+	added Doom 3 packets
+		(newVersion, heartbeat, srvAuth, auth, challenge, challengeResponse)
+
+
+2004-11-29	Andre' Schulz	<chickenman@exhale.de>
+* Makefile
+	put commonly needed options in common.mk
+
+* common.mk
+	new
+	added Solaris support
+
+* masterserver.c
+	added ifdef for Solaris and FreeBSD support
+
+* plugins/Makefile
+	use common.mk
+
+* tests/Makefile
+	use common.mk
+
+* tests/test-q2-heartbeat-query.c
+	increase timeout to 15 seconds
+	use memcpy() instead of casting and shifting (fixes endianess issue)
+	minor changes
+
+* tests/q2-test-ping.c
+	minor change
+
+* tests/test-q3-heartbeat-getservers.c
+	use memcpy() instead of casting and shifting (fixes endianess issue)
+	minor changes
+
+
+2004-11-20	Andre' Schulz	<chickenman@exhale.de>
+* README
+	give credit to <shr1k3@gmx.at> for FreeBSD patch
+
+* plugins/libq3.c
+	minor fixes and cleanups
+	sanity check packets to prevent bad stuff from happening
+
+* tests/test-q2-shutdown.c
+	minor fix
+
+* Makefile
+	fix FreeBSD CFLAGS
+
+
+2004-11-19	Andre' Schulz	<chickenman@exhale.de>
+* masterserver.c
+	minor fixes and cleanups
+	no longer trying to load files without .so suffix
+	fixed load_plugins()
+	changed ifdef option to __linux__
+
+* plugins/libh2.c
+	changed email address
+
+* plugins/libq2.c
+	changed email address
+
+* plugins/libq3.c
+	changed email address
+	include math.h
+	simplified process_getservers()
+
+* plugins/libqw.c
+	changed email address
+
+* tests/Makefile
+	add -lm to CFLAGS
+
+* tests/test-q3-heartbeat-getservers.c
+	include math.h
+	read number of servers from command line
+	increased timeout to 15 seconds
+
+
+2004-11-17	Andre' Schulz	<chickenman@exhale.de>
+* masterserver.c
+	fixed FreeBSD ifdef section
+	really install the SIGINT handler
+
+2004-11-16	Andre' Schulz	<chickenman@exhale.de>
+* logging.c
+	! undo last commit !
+
+* logging.h
+	! undo last commit !
+
+* masterserver.c
+	! undo last commit !
+	added SIGINT handler function sigint_handler()
+	using EXIT_* as return values when exiting the program
+	changed email address to <chickenman@exhale.de>
+	minor changes and fixes
+	Thanks to <shr1k3@gmx.at> for the following:
+		FreeBSD friendliness fixes; mostly header dependencies
+		ifdef'd SO_BINDTODEVICE section out for FreeBSD
+
+* masterserver.h
+	! undo last commit !
+	added global variable master_shutdown (is set on SIGINT reception)
+
+* Makefile
+	remove -pedantic from DEBUG_CFLAGS
+	add FreeBSD support; Thanks <shr1k3@gmx.at>!
+
+* plugins/libh2.c
+	FreeBSD friendliness fixes (header dependencies)
+		Thanks <shr1k3@gmx.at>!
+
+* plugins/libq2.c
+	FreeBSD friendliness fixes (header dependencies)
+		Thanks <shr1k3@gmx.at>!
+
+* plugins/libq3.c
+	FreeBSD friendliness fixes (header dependencies)
+		Thanks <shr1k3@gmx.at>!
+
+* plugins/libqw.c
+	FreeBSD friendliness fixes (header dependencies)
+		Thanks <shr1k3@gmx.at>!
+
+
+2004-11-10	Andre' Schulz	<chickenman@exhale.de>
+* Makefile
+	added -pedantic to DEBUG_CFLAGS
+
+* logging.c
+	converted comments to ISO C style
+
+* logging.h
+	converted comments to ISO C style
+
+* masterserver.c
+	converted comments to ISO C style
+	added SIGINT handler function sigint_handler()
+	using EXIT_* as return values when exiting the program
+	changed email address to <chickenman@exhale.de>
+	minor changes and fixes
+
+* masterserver.h
+	converted comments to ISO C style
+	added global variable master_shutdown (is set on SIGINT reception)
+
+* docs/PROTOCOLS
+	added description of player information block to QW section
+
+
+2004-10-24	Andre' Schulz	<andre@malchen.de>
+* README
+	fixed/added some stuff
+
+* logging.h
+	removed variable names in function prototypes
+
+* masterserver.c
+	moved header files from masterserver.h to masterserver.c
+	call plugin specific free_privdata() function to fix a memory leak
+	loading an unlimited number of plugins is now possible
+
+* masterserver.h
+	removed all header files
+	added free_privdata() to plugin API
+	removed variable names in function prototypes
+
+* docs/PROTOCOLS
+	added QuakeWorld packets
+	added RtCW packets
+	minor changes in Doom3 section
+
+* plugins/libexample.c
+	removed variable names in function prototypes
+	added free_privdata()
+
+* plugins/libh2.c
+	added header files
+	removed variable names in function prototypes
+	minor changes
+
+* plugins/libq2.c
+	added header files
+	removed variable names in function prototypes
+	minor changes
+
+* plugins/libq3.c
+	added header files
+	removed variable names in function prototypes
+	minor changes
+	added free_privdata() function to fix a memory leak
+
+* plugins/libqw.c
+	added header files
+	removed variable names in function prototypes
+	minor changes
+
+* tests/test-q2-heartbeat-query.c
+	increased timeout to 10 seconds
+
+* tests/test-q3-heartbeat-getservers.c
+	increased timeout to 10 seconds
+
+
+2004-07-29	Andre' Schulz	<andre@malchen.de>
+* docs/PROTOCOLS
+	added getIpAuthorize packet to RtCW section
+
+
+2004-07-24	Andre' Schulz	<andre@malchen.de>
+* docs/PROTOCOLS
+	added RtCW section
+
+
+2004-07-23	Andre' Schulz	<andre@malchen.de>
+* docs/PROTOCOLS
+	added ports for QW and HW
+	added QuakeForge link
+
+
+2004-06-10	Andre' Schulz	<andre@malchen.de>
+* docs/PROTOCOLS
+	added "getIpAuthorize", "ipAuthorize", "ping" and "disconnect" packets
+
+
+2004-05-01	Andre' Schulz	<andre@malchen.de>
+* masterserver.h
+	removed <math.h> inclusion
+
+* tests/Makefile
+	added test-q3-getmotd target
+
+* tests/test-q2-heartbeat-query.c
+	replaced all error messages with perror() calls
+	disable stream buffering for cosmetic reasons
+
+* tests/test-q2-ping.c
+	replaced all error messages with perror() calls
+	disable stream buffering for cosmetic reasons
+
+* tests/test-q2-shutdown.c
+	replaced all error messages with perror() calls
+	disable stream buffering for cosmetic reasons
+
+* tests/test-q3-heartbeat-getservers.c
+	fixes/changes all around
+
+* tests/test-q3-getmotd.c
+	added
+
+
+2004-04-30	Andre' Schulz	<andre@malchen.de>
+* plugins/libq3.c
+	minor clean up
+	realloc() failure for q3m.list shouldn't be fatal anymore
+
+* tests/Makefile
+	added test-q3-heartbeat-getservers target
+
+* tests/test-q3-heartbeat-getservers.c
+	added
+
+
+2004-04-29	Andre' Schulz	<andre@malchen.de>
+* docs/PROTOCOLS
+	updated STEF1 and UT section
+
+
+2004-04-27	Andre' Schulz	<andre@malchen.de>
+* tests/Makefile
+	fixed ... kinda
+
+* tests/test-q2.c
+	split up into three separate tests and deleted
+
+* tests/test-q2-ping.c
+	added; was part of test-q2.c
+
+* tests/test-q2-heartbeat-query.c
+	added; was part of test-q2.c
+
+* tests/test-q2-shutdown.c
+	added; was part of test-q2.c
+
+* docs/PROTOCOLS
+	minor fix
+
+
+2004-04-26	Andre' Schulz	<andre@malchen.de>
+* docs/PROTOCOLS
+	merge and translation done
+
+* docs/PROTOKOLLE
+	deleted
+
+* docs/PROTOKOLLE.uk_us
+	deleted
+
+* README
+	added more information on LAN usage
+
+* masterserver.h
+	version number bumped to 0.3.3
+
+* plugins/libh2.c
+	version number bumped up to 0.3.3
+
+* plugins/libq2.c
+	version number bumped up to 0.3.3
+
+* NEWS
+	added 0.3.3 release information
+
+
+2004-04-19	Andre' Schulz	<andre@malchen.de>
+* plugins/libq2.c
+	fixed "query" string again by deleting \n and setting size to 5
+
+* plugins/libh2.c
+	fixed "query" string again by deleting \n and setting size to 5
+
+	
+2004-04-06	Andre' Schulz	<andre@malchen.de>
+* docs/PROTOCOLS
+	merged more of PROTOKOLLE and PROTOKOLLE.uk_us
+
+
+2004-03-01	Andre' Schulz	<andre@malchen.de>
+* README
+	added info on LAN party/DNS name stuff
+
+* docs/PROTOCOLS
+	created
+	merged some of PROTOKOLLE and PROTOKOLLE.uk_us
+
+
+2004-02-09	Andre' Schulz	<andre@malchen.de>
+* plugins/libqw.c
+	renamed _init() function to init_plugin()
+	added __attribute__ ((constructor)) to init_plugin() declaration
+
+* plugins/libq2.c
+	renamed _init() function to init_plugin()
+	added __attribute__ ((constructor)) to init_plugin() declaration
+
+* plugins/libh2.c
+	renamed _init() function to init_plugin()
+	added __attribute__ ((constructor)) to init_plugin() declaration
+
+* plugins/libq3.c
+	renamed _init() function to init_plugin()
+	added __attribute__ ((constructor)) to init_plugin() declaration
+
+* plugins/Makefile
+	removed -rdynamic from CFLAGS
+	replaced $(LD) with $(CC) in %.so target
+
+* plugins/libexample.c
+	renamed _init() function to init_plugin()
+	added __attribute__ ((constructor)) to init_plugin() declaration
+	minor fixes and changes
+
+
+2004-02-01	Andre' Schulz	<andre@malchen.de>
+* plugins/libh2.c
+	removed H2M_PORT definition
+	minor stuff fixed/changed/cleaned up
+	version number bumped up to 0.3.2
+
+* plugins/libq3.c
+	minor stuff fixed/changed/cleaned up
+	version number bumped up to 0.7.2
+
+* plugins/libq2.c
+	minor stuff fixed/changed/cleaned up
+	version number bumped up to 0.3.2
+
+* plugins/libqw.c
+	minor stuff fixed/changed/cleaned up
+	version number bumped up to 0.1.1
+
+* masterserver.h
+	version number bumped up to 0.3.2
+
+* tests/test-q2.c
+	minor fixes
+
+* NEWS
+	added info about 0.3.2 release
+
+
+2004-01-28	Andre' Schulz	<andre@malchen.de>
+* masterserver.c
+	forgot to comment out some log level related code
+
+* tests/test-q2.c
+	timeouts implemented
+	fixed and improved test_query()
+	created test_shutdown()
+	test summary
+
+
+2004-01-20	Andre' Schulz	<andre@malchen.de>
+* plugins/libh2.c
+	fixed "query" string by deleting \0 and setting size to 6
+
+
+2004-01-15	Andre' Schulz	<andre@malchen.de>
+* tests/test-q2.c
+	fixed some stuff
+
+* tests/Makefile
+	changed some stuff
+
+
+2004-01-14	Andre' Schulz	<andre@malchen.de>
+* README
+	give credit to fatty <fatty@gmx.ch>
+
+
+2004-01-13	Andre' Schulz	<andre@malchen.de>
+* plugins/libq2.c
+	fixed "query" string by deleting \0 and setting size to 6
+		this broke Gamespy queries
+		thanks to fatty <fatty@gmx.ch>
+
+
+2004-01-12	Andre' Schulz	<andre@malchen.de>
+* Makefile
+	added "check" target
+
+* tests/Makefile
+	added
+
+
+2004-01-11	Andre' Schulz	<andre@malchen.de>
+* tests/test-q2.c
+	merged q2 header into packets
+	test_query() created
+
+
+2004-01-07	Andre' Schulz	<andre@malchen.de>
+* tests/test-q2.c
+	updated
+
+
+2004-01-06	Andre' Schulz	<andre@malchen.de>
+* masterserver.h
+	FBSD friendliness
+		include netinet/in.h before arpa/inet.h
+		include sys/types.h before everything else
+
+* masterserver.c
+	not including sys/types.h anymore
+
+
+2003-12-26	Andre' Schulz	<andre@malchen.de>
+* docs/PROTOKOLLE
+	added more heretic2 packet dumps
+	added another heretic2 info packet
+
+* plugins/libh2.c
+	fixed process() function (broken for quite some time doh!)
+	removed version defines
+	bumped version number up to 0.3.1
+	more verbose error messages
+	minor fixes all over the place
+
+* logging.c
+	added variable _log_level
+
+* logging.h
+	disabled all log level code
+
+* masterserver.c
+	disabled all log level code
+
+* plugins/libq2.c
+	removed version defines
+	bumped version number up to 0.3.1
+	minor fixes all over the place
+
+* plugins/libqw.c
+	disabled some unused code
+	minor fixes
+
+* tests/test-q2.c
+	new
+
+
+2003-12-23	Andre' Schulz	<andre@malchen.de>
+* docs/PROTOKOLLE
+	added heartbeat, shutdown and info packet dumps for heretic2
+
+
+2003-12-20	Andre' Schulz	<andre@malchen.de>
+* logging.h
+	add log level checking to macros
+
+* masterserver.c
+	added -L option to select log level
+	reformatted some lines to 80 chars width
+
+* plugins/libh2.c
+	unified heartbeat message
+
+* plugins/libq2.c
+	unified heartbeat message
+
+* plugins/libq3.c
+	unified heartbeat message
+	minor stuff
+
+* plugins/libqw.c
+	removed some dead code
+	real vars in qwm_private_data_t
+	unified heartbeat message
+
+
+2003-11-18	Andre' Schulz	<andre@malchen.de>
+* plugins/libqw.c
+	added packet definitions
+
+
+2003-11-17	Andre' Schulz	<andre@malchen.de>
+* plugins/libh2.c
+	removed superfluous if statement
+	corrected description at line 1
+
+* plugins/libq2.c
+	removed superfluous if statement
+
+* plugins/libq3.c
+	removed superfluous if statement
+
+* plugins/libexample.c
+	removed version #defines
+
+
+2003-11-16	Andre' Schulz	<andre@malchen.de>
+* docs/PROTOKOLLE
+	added STEF1 section
+	updated Quake3, Quake2, Heretic2 section
+
+* plugins/libqw.c
+	removed superfluous stuff
+	updates all around
+
+
+2003-11-15	Andre' Schulz	<andre@malchen.de>
+* plugins/Makefile
+	small changes
+	added libqw
+
+* plugins/libqw.c
+	removed process_{statusResponse,getmotd}()
+	added process_ping()
+	added process_shutdown()
+	changes in process()
+
+
+2003-11-14	Andre' Schulz	<andre@malchen.de>
+* plugins/libqw.c
+	new
+
+
+2003-10-30	Andre' Schulz	<andre@malchen.de>
+* masterserver.c
+	removed useless stuff from error messages
+	made memory related error messages more verbose
+
+
+2003-10-26	Andre' Schulz	<andre@malchen.de>
+* masterserver.c
+	print help text if unknown option was given on cmdline
+
+
+2003-10-25	Andre' Schulz	<andre@malchen.de>
+* plugins/libq3.c
+	deleted all code which had something to do with options in getservers packets
+	rewrote most of process_getservers()
+	version number bumped to 0.7.1
+	made error messages more verbose
+	other minor changes
+
+* masteserver.c
+	version number bumped to 0.3.1
+
+* README
+	updates all around
+
+
+2003-10-24	Andre' Schulz	<andre@malchen.de>
+* masterserver.c
+	fixed: skipped one server during removal in plugin_heartbeat_thread()
+	fixed: num_msgs got set to -1 in plugin_thread()
+	minor changes
+	
+* logging.h
+	minor changes
+
+* plugins/libq3.c
+	fixed off-by-one and other bugs in process_statusResponse()
+	fixed bad abort condition in process_getservers() for-loop
+
+
+2003-10-22	Andre' Schulz	<andre@malchen.de>
+* docs/PROTOKOLLE
+	small update to q3 section
+	date format changed to ISO 8601
+
+* README
+	date format changed to ISO 8601
+
+
+2003-10-20	Andre' Schulz	<andre@malchen.de>
+* logging.c
+	log_init() now returns an int
+
+* logging.h
+	changed log_init() return type to int
+
+* masterserver.c
+	got rid of most fprintf() calls in favor of our own logging code
+	version number bumped to 0.3
+	fixed a bug when binding to more than one interface
+	minor changes
+	much faster replacement code in delete_server()
+
+* plugins/libh2.c
+	removed an unused variable
+
+* plugins/libq2.c
+	removed an unused variable
+
+* plugins/libq3.c
+	fixed not sending server list if no options given in getservers packet
+	deleted some superfluous DEBUG() messages
+	added getmotd INFO() message
+
+* plugins/libexample.c
+	minor changes
+
+* NEWS
+	added info on 0.3 release
+
+* Makefile
+	added RMDIR
+
+
+2003-10-19	Andre' Schulz	<andre@malchen.de>
+* masterserver.c
+	fixed a bug with sockets in plugin_thread()
+	fewer messages when not in debug mode
+	fixed a memory problem in socket creation routines
+
+* plugins/libh2.c
+	fixed
+	split up the process() function
+
+* plugins/libq2.c
+	fixed
+	split up the process() function
+
+* plugins/libq3.c
+	minor changes
+
+* plugins/libexample.c
+	changed code to work with the API changes
+
+
+2003-10-18	Andre' Schulz	<andre@malchen.de>
+* masterserver.c
+	rewrote cmdline parser with getopt()
+	standard behavior is now to _not_ fork into background
+	binding to 1 interface works now
+	split ERROR() macro up in ERROR() and ERRORV()
+		this fixes a compile problem with gcc 2.95.3 (and maybe others)
+	minor changes and fixes
+	multi port support is in!
+
+
+2003-10-13	Andre' Schulz	<andre@malchen.de>
+* plugins/libq3.c
+	fixed a bug in process_getservers() parser
+	added missing \n to some log messages
+
+
+2003-10-12	Andre' Schulz	<andre@malchen.de>
+* plugins/libq3.c
+	eliminated strtok() in process_getservers()
+	added missing error handling to [mc]alloc() calls
+	some minor changes/fixes
+
+
+2003-09-17	Andre' Schulz	<andre@malchen.de>
+* plugins/libq3.c
+	calculate getstatus msg_out_length dynamically
+
+
+2003-09-11	Andre' Schulz	<andre@malchen.de>
+* plugins/libq3.c
+	q3_pkt_heartbeat_len++
+
+
+2003-09-07	Andre' Schulz	<andre@malchen.de>
+* plugins/libq3.c
+	new statusResponse parser
+	preliminary motd support
+	now sending getstatus with challenge
+	added cleanup()
+
+* docs/PROTOKOLLE
+	tiny correction in "motd"
+
+* masterserver.h
+	added cleanup() to masterserver_plugin struct
+
+* masterserver.c
+	small change due to cleanup() in plugin
+
+
+2003-08-27	Andre' Schulz	<andre@malchen.de>
+* plugins/libq3.c
+	revert s/strncmp/strcmp/ changes in process()
+	added "\n" to q3_pkt_heartbeat
+	player info parser almost completed
+	create challenge in send_getstatus()
+
+* masterserver.c
+	clear the incoming packet buffer
+
+
+2003-08-26	Andre' Schulz	<andre@malchen.de>
+* plugins/libq3.c
+	s/strncmp/strcmp/
+	q3m_player_data_t struct added
+	began work on player data parser
+	rewrote and renamed process_infoResponse() to process_statusResponse()
+	rewrote and renamed send_getinfo() to send_getstatus()
+
+* plugins/libq2.c
+	s/strncmp/strcmp/
+
+* plugins/libh2.c
+	s/strncmp/strcmp/
+
+
+2003-08-25	Andre' Schulz	<andre@malchen.de>
+* plugins/libq2.c
+	fix s/nr_servers/num_servers/ breakage
+
+* plugins/libh2.c
+	fix s/nr_servers/num_servers/ breakage
+
+* plugins/libq3.c
+	error handling of [m|re|c]alloc()
+	rewrote q3m_private_data_t to reflect s/getinfo/getstatus/ change
+
+* masterserver.c
+	error handling of calloc() at start of plugin_thread()
+	small optimization of cleanup loop at end of plugin_thread()
+
+
+2003-08-24	Andre' Schulz	<andre@malchen.de>
+* plugins/libq3.c
+	getserversResponse packet assembler mostly done
+	version number bumped to 0.7
+
+* docs/PROTOKOLLE
+	tiny change
+
+* docs/PROTOKOLLE.uk_us
+	tiny change
+
+
+2003-08-23	Andre' Schulz	<andre@malchen.de>
+* plugins/libq3.c
+	s/nr_servers/num_servers/
+	parser for infoResponse packets rewritten using strtok()
+	parser for getservers packets done
+	began getserversResponse packet assembler overhaul
+	process() split up into several functions
+	new function process_heartbeat()
+	new function send_getinfo()
+	new function process_getservers()
+	new function process_infoResponse()
+	added _options bit field to q3m_private_data_t
+
+* masterserver.c
+	s/nr_servers/num_servers/
+	free() private_data of plugins during cleanup
+
+* masterserver.h
+	s/nr_servers/num_servers/ in struct masterserver_plugin for consistency
+
+
+2003-08-22	Andre' Schulz	<andre@malchen.de>
+* plugins/libq3.c
+	replaced snprintf() with memcpy()
+	killed unsigned char ip, port
+
+
+2003-06-09	Andre' Schulz	<andre@malchen.de>
+* docs/PROTOKOLLE
+	Q3: "heartbeat" and "getservers" updated
+
+* plugins/libq3.c
+	private_data added
+	send "getinfo" on every "heartbeat" (this is not right; will fix later)
+	"infoResponse" packet parser 
+
+
+2003-04-26	Andre' Schulz	<andre@malchen.de>
+* plugins/libq3.c
+	version number bumped to 0.6
+
+* plugins/libq2.c
+	works now with the new masterserver_plugin struct
+	less "chatty"
+	version number bumped to 0.3
+
+* plugins/libh2.c
+	overwritten with libq2.c; works now, too
+
+
+2003-04-20	Andre' Schulz	<andre@malchen.de>
+* masterserver.c
+	INFO() message typo fixed in plugin_heartbeat_thread()
+
+* plugins/libq3.c
+	new code works
+
+
+2003-04-17	Andre' Schulz	<andre@malchen.de>
+* masterserver.c
+	msg_out and msg_out_length changes
+	
+* masterserver.h
+	char *msg_out -> char **msg_out
+	int msg_out_length -> int *msg_out_length
+	int num_msgs added
+	math.h include
+
+* plugins/libq3.c
+	now only sending 112 servers per packet
+
+
+2003-03-14	Andre' Schulz	<andre@malchen.de>
+* masterserver.c
+	transferred some code from main() into functions:
+		int load_plugins(char *, void* [])
+		void give_up_root_privileges(void)
+	removed unused commented out code
+
+* docs/PROTOKOLLE
+	"ping" and "ack" packets added to Quake2
+
+* plugins/libq2.c
+	answer "ping" packets
+	msg_out_length+1 in calloc() for query/ping packet msg_out
+		thx to valgrind
+	version number bumped up to 0.2
+
+* plugins/libq3.c
+	fixed snprintf() off-by-one bugs
+	msg_out_length+1 in calloc() for getservers packet msg_out
+		thx to valgrind
+
+
+2003-03-08	Andre' Schulz	<andre@malchen.de>
+* plugins/libq2.c
+	preliminary q2 plugin
+
+* plugins/libq3.c
+	substituted the "4" on lines 82 and 137 with q3_pkt_header_len
+	failing to reallocate the serverlist is now fatal
+	added Q3M_PROTOCOL for future use
+
+* masterserver.c
+	commented out INFO() messages in plugin_thread()
+	removed unused commented out code in plugin_thread(),
+		plugin_heartbeat_thread(), delete_server()
+	check realloc return value in delete_server()
+	check calloc return value in register_plugin()
+
+* masterserver.h
+	added protocol variable in masterserver_plugin struct for future use
+
+
+2003-03-03	Andre' Schulz	<andre@malchen.de>
+* masterserver.c
+	when removing a dead server show its ip adress and port
+
+
+2003-03-02	Andre' Schulz	<andre@malchen.de>
+* docs/PROTOKOLLE
+	more packet dumps
+
+
+2003-03-01	Andre' Schulz	<andre@malchen.de>
+* docs/PROTOKOLLE
+	included packet dumps for better understanding
+
+
+2003-02-24	Andre' Schulz	<andre@malchen.de>
+* plugins/libexample.c
+	don't fill next and mutex variables in masterserver_plugin struct
+
+* plugins/libh2.c
+	don't fill next and mutex variables in masterserver_plugin struct
+
+* plugins/libq3.c
+	don't fill next and mutex variables in masterserver_plugin struct
+
+* masterserver.h
+	next and mutex variables moved down in masterserver_plugin struct
+	(they don't have to be declared in plugins anymore)
+
+* masterserver.c
+	init pthread mutex in masterserver_plugin struct in register_plugin()
+
+
+2003-02-21	Andre' Schulz	<andre@malchen.de>
+* plugins/libq3.c
+	reformat to 80 chars width
+	missed some fprintf()s; converted to fitting log macro
+	changed Q3_HEADER from preprocessor define to static char
+	created static char packet variables
+	some minor changes/fixes
+
+* plugins/libh2.c
+	deleted and recreated from the new libq3.c
+	small bugfixes
+
+* plugins/libexample.c
+	minor change to version info
+
+* masterserver.h
+	minor change to version info
+
+* README
+	minor changes
+
+* Makefile
+	change PREFIX to /usr
+
+* masterserver.c
+	forgot to set pointer to next entry in linked list =)
+	added cmdline option -p and --plugin-dir to configure plugin directory
+
+
+2003-02-20	Andre' Schulz	<andre@malchen.de>
+* plugins/libh2.c
+	copied code from libq3.c and customized it for heretic2
+
+* README
+	added qstat credits
+
+* docs/PROTOKOLLE
+	minor changes in quake2 and heretics sections
+
+
+2003-02-19	Andre' Schulz	<andre@malchen.de>
+* logging.h
+	s/SUBNAME/LOG_SUBNAME/
+
+* masterserver.c
+	s/SUBNAME/LOG_SUBNAME/
+
+* plugins/libq3.c
+	s/SUBNAME/LOG_SUBNAME/
+
+* plugins/libh2.c
+	s/SUBNAME/LOG_SUBNAME/
+
+* plugins/libexample.c
+	s/SUBNAME/LOG_SUBNAME/
+
+
+2003-02-08	Andre' Schulz	<andre@malchen.de>
+* masterserver.c
+	using snprintf instead of sprintf
+
+* plugins/libq3.c
+	using snprintf instead of sprintf
+
+* README
+	logging code credit to Viktor
+
+* logging.c / logging.h
+	added to cvs
+
+
+2003-02-06	Andre' Schulz	<andre@malchen.de>
+* masterserver.c
+	slight change to GPL disclaimer
+
+* logging.c
+	GPL disclaimer added
+
+* plugins/libexample.c
+	GPL disclaimer added
+
+* plugins/libh2.c
+	GPL disclaimer added
+
+* plugins/libq3.c
+	GPL disclaimer added
+
+
+2003-02-04	Andre' Schulz	<andre@malchen.de>
+* masterserver.h
+	MASTERSERVER_VERSION split up into:
+	MASTERSERVER_MAJOR_VERSION
+	MASTERSERVER_MINOR_VERSION
+
+* plugins/libq3.c
+	Q3M_PLUGIN_VERSION split up into:
+	Q3M_PLUGIN_VERSION_MAJOR
+	Q3M_PLUGIN_VERSION_MINOR
+
+* plugins/libh2.c
+	H2M_PLUGIN_VERSION split up into:
+	H2M_PLUGIN_VERSION_MAJOR
+	H2M_PLUGIN_VERSION_MINOR
+
+* plugins/libexample.c
+	EXAMPLE_PLUGIN_VERSION split up into:
+	EXAMPLE_PLUGIN_VERSION_MAJOR
+	EXAMPLE_PLUGIN_VERSION_MINOR
+
+	P.S.: string version defines are a pain in the ass
+
+
+2002-12-30	Andre' Schulz	<andre@malchen.de>
+* masterserver.h
+	added ERROR, PRINT and DEBUG defines
+
+
+2002-12-19	Andre' Schulz	<andre@malchen.de>
+* docs/PROTOKOLLE
+	some corrections
+	quake2 section updated
+	unreal tournament section created
+	added a link to q2 net specs
+
+
+2002-12-03	Andre' Schulz	<andre@malchen.de>
+* masterserver.c:
+	daemonizing implemented
+	binding to specific interfaces implemented (and root privilege dropping)
+	catch opendir() errors
+	catch dlopen() errors
+
+* masterserver.h:
+	version number bumped up to 0.2
+

+ 281 - 0
src/masterserver/COPYING

@@ -0,0 +1,281 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+                       59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+

+ 39 - 0
src/masterserver/Makefile

@@ -0,0 +1,39 @@
+include common.mk
+
+OBJ_FILES = masterserver.o logging.o
+PROGRAM = masterserver
+
+.PHONY: all clean install masterserver plugins uninstall
+
+all: masterserver plugins
+.SUFFIXES = .c .o
+
+.c.o:
+	$(CC) -c $< -o $@ $(CFLAGS_MAIN) 
+
+masterserver.o: masterserver.c masterserver.h
+
+logging.o: logging.c logging.h
+
+masterserver: $(OBJ_FILES)
+	$(CC) -o $@ $(OBJ_FILES) $(LDFLAGS) $(CFLAGS_MAIN)
+
+plugins:
+	$(MAKE) -C plugins
+
+clean:
+	$(RM) $(OBJ_FILES) $(PROGRAM)
+	$(MAKE) -C plugins clean
+
+install: all
+	$(INSTALL) $(PROGRAM) $(bindir)/$(PROGRAM)
+	$(MAKE) -C plugins install
+
+uninstall:
+	$(RM) $(bindir)/$(PROGRAM)
+	$(MAKE) -C plugins uninstall
+	$(RMDIR) $(libdir)/lasange/masterserver
+
+check:
+	$(MAKE) -C tests all
+

+ 43 - 0
src/masterserver/NEWS

@@ -0,0 +1,43 @@
+2005-05-28	masterserver-0.4.1 release
+- fixed a byte-order bug in the Doom 3 plugin
+
+2005-05-21	masterserver-0.4 release
+- updated STVEF1, UT and Doom 3 sections in docs/PROTOCOLS
+- added RtCW section to docs/PROTOCOLS
+- plugin API changed slightly
+- lots of bug fixes and cleaning up
+- compiles and runs on FreeBSD out-of-the-box
+- Doom 3 plugin
+- Star Trek Voyager: Elite Force plugin
+
+2004-04-26	masterserver-0.3.3 release
+- fix libh2/q2 gamespy query bug for real
+- docs/PROTOKOLLE* merged and translated to docs/PROTOCOLS
+- masterserver LAN usage pointers in README
+- QuakeWorld plugin
+
+2004-02-01	masterserver-0.3.2 release
+- libh2 process() function fixed
+- libh2/q2 gamespy query bug fixed (thanks to fatty <fatty@gmx.ch>)
+- made log messages smaller
+- docs/PROTOKOLLE updated
+
+2003-10-25	masterserver-0.3.1 release
+- major bugfixes in Quake3 plugin and masterserver
+
+2003-10-20	masterserver-0.3 release
+- Quake2 plugin
+- Heretic2 plugin
+- masterserver doesn't automatically daemonize anymore
+- lots of changes under the "hood"
+- binding to interfaces works now as intended
+- motd support in Quake3 plugin
+- no interface limit
+- plugins can use more than one port
+
+2002-12-03	masterserver-0.2 release
+- Binding to specific interfaces/devices implemented (e.g. -i --listen-on eth0)
+	Allows up to 15 interfaces (this is hardcoded in masterserver.c line 169)
+- masterserver now automatically daemonizes (e.g. -D --no-daemon to prevent)
+
+2002-11-03	masterserver-0.1 release

+ 204 - 0
src/masterserver/README

@@ -0,0 +1,204 @@
+masterserver v0.4.1
+-------------------
+
+1. Introduction
+2. Goals
+3. Installation
+4. Misc
+
+
+1. Introduction
+---------------
+
+masterserver is an attempt at writing a generic master server for game servers.
+masterserver is easily extendable via plugins written in C.
+Currently, there are plugins for Quake2/3 and Heretic2. I've tested
+masterserver on:
+Linux 2.4.29 with glibc 2.3.2 (with linuxthreads) and gcc 3.3.5
+FreeBSD 5.3-STABLE with gcc 3.4.2
+SunOS 5.8 with gcc 3.4.1
+
+The name might change, any suggestions are welcome.
+
+Beware!: This is alpha quality. So it might crash 'n' burn.
+Contributions and suggestions are very welcome.
+
+
+2. Goals
+--------
+
+- generic master server
+- easy to extend via plugins
+- document the network protocols of the supported games
+- admin interface via console and tcp/ip
+- ...
+
+
+3. Installation
+---------------
+
+Note:
+	masterserver currently compiles only on Linux systems. I'm trying to make
+	it run on other unices, too, but that will take some time because of my
+	lack of experience.
+
+Note on Q3 plugin:
+	The new Q3 plugin understands and processes motd packets. The default motd
+	is "Insert MOTD here.". If you want to change the motd then you have to
+	edit "plugins/libq3.c". At the beginning of the file there's a line which
+	reads: #define Q3M_MOTD "Insert MOTD here."
+	Just replace "Insert MOTD here." with your own motd, recompile the Q3
+	plugin and enjoy.
+	Beware! The Q3 Master's ports partially collide with the EF Master's ports.
+	So you can't run both a EF and Q3 Master on the same IP address. By
+	default, both plugins will be installed. So you'll have to delete one of
+	them.
+	If you want to serve lists for both games you have to start 2 masterservers
+	which use 2 different plugin directories and are bound to different IPs.
+
+Note on Q2/H2 plugins:
+	They're very similar because Heretic2 is based on the Q2 engine. Both
+	plugins haven't had much testing. So far I made sure that they write
+	servers into their server list and correctly send it out when requested.
+
+Note on D3 plugin:
+	This is a barebones implementation. Only processes "heartbeat" and
+	"getServers" packets. Other packets are ignored.
+
+Note on EF plugin:
+	Again a barebones implementation. Only "getservers", "getmotd", "heartbeat"
+	and "heartstop" packets are processed. Other packets are ignored.
+	The default motd is "Insert MOTD here.". If you want to change it then you
+	have to edit "plugins/libef.c". Near the beginning of the file there's a
+	line which reads: #define EFM_MOTD "Insert MOTD here."
+	Just replace "Insert MOTD here." with your own motd, recompile the EF
+	plugin and enjoy.
+	Beware! The EF Master's ports partially collide with the Q3 Master's ports.
+	So you can't run both a EF and Q3 Master on the same IP address. By
+	default, both plugins will be installed. So you'll have to delete one of
+	them.
+	If you want to serve lists for both games you have to start 2 masterservers
+	which use 2 different plugin directories and are bound to different IPs.
+
+The usual "make; make install".
+
+# make
+# make install (as root)
+
+Now, you're ready to go.
+
+By default the plugins are installed in "/usr/lib/lasange/masterserver" and the
+binary is installed in "/usr/bin". If that isn't to your liking you can change
+the destinations in the Makefile.
+
+masterserver can be started as any user. (preferably as a non privileged user)
+It also has the ability to bind to specific interfaces. (Attention: This is a
+Linux-only feature) If you want to do that masterserver needs to be root. After
+masterserver has created and bound the sockets to the interface(s) it drops the
+root privileges to the user and group specified on the command line, using the
+"-u" and "-g" options.
+
+In the Makefile there are 2 other targets, named "masterserver" and "plugins", to either
+compile only the masterserver or only the plugins.
+
+There is no configuration file.
+By default masterserver reads all files in the MASTERSERVER_LIB_DIR,
+which is defined in masterserver.h and opens all files as shared objects.
+Or you can specify a different directory via the "-p" command line
+switch. MASTERSERVER_LIB_DIR defaults to "/usr/lib/lasange/masterserver".
+
+Btw, to uninstall masterserver, run "make uninstall". (as root)
+
+
+Note for masterserver usage on LAN parties:
+-------------------------------------------
+You should setup the following DNS names to point to the IP on which
+masterserver will run.
+
+libq3/Quake3
+	Q3 master DNS name:	master.quake3arena.com
+	Q3 MOTD DNS name:	update.quake3arena.com
+	Q3 auth DNS name:	authorize.quake3arena.com
+
+	server command line options:
+	+set sv_master1 10.0.0.1 +set sv_master2 masterserver.exhale.de
+
+	You can specify up to 5 masters.
+
+
+libq2/Quake2
+	server command line options:
+	+set public 1 +setmaster 10.0.0.1 masterserver.exhale.de
+
+	You can specify up to 7 masters.
+
+
+libh2/Heretic2
+	H2 master DNS name:	master.ravensoft.com
+
+	server command line options:
+	+set public 1 +setmaster 10.0.0.1 masterserver.exhale.de
+
+	You can specify up to 7 masters.
+
+
+libqw/QuakeWorld
+	server command line options:
+	+setmaster 10.0.0.1 10.0.0.2
+
+	You can specify up to 8 masters.
+
+
+libd3/Doom3
+	Doom3 master DNS name: idnet.ua-corp.com
+
+	Doom3 server command line options:
+	+set net_master0 10.0.0.1 +set net_master1 masterserver.exhale.de
+
+	You can specify up to 5 masters.
+
+
+libef/Star Trek Voyager: Elite Force
+	EF master DNS name:	master.stef1.ravensoft.com
+	EF MOTD DNS name:	motd.stef1.ravensoft.com
+	EF auth DNS name:	authenticate.stef1.ravensoft.com
+
+	server command line options:
+	+set sv_master1 10.0.0.1 +set sv_master2 masterserver.exhale.de
+
+	You can specify up to 5 masters.
+
+4. Misc
+-------
+
+Thanks to:
+----------
+iptables team for their source code from which I've learned how to use 
+shared objects. <http://www.netfilter.org>
+
+Ingo Rohlfs <irohlfs@irohlfs.de> for suggestions on the thread system and the
+nightly debugging sessions.
+
+Viktor Vasilev aka shr1k3 <shr1k3@gmx.at>
+	Suggestions and code auditing
+	Logging code
+	FreeBSD support
+	letting me test the code on his machine
+
+qstat team for their source code <http://www.qstat.org>
+
+fatty <fatty@gmx.ch>
+	packet dumps which helped fix a small bug in libq2
+	suggestions
+
+QuakeForge <http://www.quakeforge.net>
+	QW protocol
+
+<XL*g0b> <g0b@arcor.de>
+	capturing Doom3 packets
+
+ID Software <http://www.idsoftware.com>
+	releasing their old engines under the GPL
+
+Last modified: 2005-05-28 by Andre' Schulz
+

+ 49 - 0
src/masterserver/TODO

@@ -0,0 +1,49 @@
+Coming up next:
+- implement libq3 parser in lib{q2,h2} for player info etc.
+- maybe rework the socket/interface binding code
+- docs/PROTOCOLS: add stef1 packets
+- H2 test suite
+	- heartbeat/query/servers
+	- ping/ack
+	- heartbeat/query/servers/shutdown
+- Q3 test suite
+	- heartbeat/getservers/getserversResponse/2xheartbeat
+- QW test suite
+	- k/l
+	- a/c/d
+	- a/c/d/C
+- D3 test suite
+	- fix byte-order in heartbeat-getServers
+- EF test suite
+- ET test suite
+- RW test suite
+- more detailed packet description in docs/PROTOCOLS
+  (e.g. when are the packets sent?, what is their purpose?)
+- maybe create 1 id software plugin which supports q3, ef, rw, et, ...
+  to resolve the port collision problem
+
+Near future:
+- implement some anti spamming/DoS or throttling system to prevent DoS attacks
+- implement stub functions for protocols other than udp
+- Damn UT is using TCP for communication with master server.
+  Allow plugins to use protocols other than UDP for communication.
+- process packets in parallel
+- add more qw/q2/q3 based games
+- docs/PROTOCOLS: check which options the stef1 master honors in getservers
+- "The Sega.Net master server address is: "master.id-q3a.games.sega.net: 27950"
+- check out the "populated=1" string gamespy3d sends to q2 master
+
+More or less far future:
+- Support for other games:
+	check tasks at http://sf.net/projects/lasange-system
+- Stats
+- admin commands/interface via network
+	- ability to load/unload plugins on-the-fly
+- libq3: implement authorization code
+- libq3: implement player statistics(?)
+- include a database interface in masterserver
+  (the one from devil)
+- Check RakNet Master source code
+
+Last modified: 2005-05-28 by Andre' Schulz
+

+ 45 - 0
src/masterserver/common.mk

@@ -0,0 +1,45 @@
+SHELL = /bin/sh
+
+prefix = /usr
+bindir = $(prefix)/bin
+sbindir = $(prefix)/sbin
+libdir = $(prefix)/lib
+
+INSTALL = /usr/bin/install
+INSTALLDATA = /usr/bin/install -m 644
+CC = gcc
+LD = ld
+RM = rm -f
+RMDIR = rmdir -p --ignore-fail-on-non-empty
+
+CFLAGS = -DDEBUG -g -Wall
+
+PLATFORM := $(shell uname)
+ifeq "$(PLATFORM)" "Linux"
+CFLAGS_MAIN		= $(CFLAGS) -rdynamic \
+		-DMASTERSERVER_LIB_DIR=\"/usr/lib/lasange/masterserver\"
+CFLAGS_PLUGIN	= $(CFLAGS) -fPIC
+CFLAGS_TESTS	= $(CFLAGS) -lm
+LDFLAGS			= -lpthread -ldl
+LDFLAGS_PLUGIN	= -shared -lm
+endif
+
+ifeq "$(PLATFORM)" "FreeBSD"
+CFLAGS_MAIN		= $(CFLAGS) -rdynamic \
+		-DMASTERSERVER_LIB_DIR=\"/usr/lib/lasange/masterserver\"
+CFLAGS_PLUGIN	= $(CFLAGS) -fPIC
+CFLAGS_TESTS	= $(CFLAGS) -lm
+LDFLAGS			= -pthread
+LDFLAGS_PLUGIN	= -shared -lm
+endif
+
+ifeq "$(PLATFORM)" "SunOS"
+CFLAGS_MAIN		= $(CFLAGS) \
+		-DMASTERSERVER_LIB_DIR=\"/usr/lib/lasange/masterserver\" \
+		-DSOLARIS -D__EXTENSIONS__
+CFLAGS_PLUGIN	= $(CFLAGS) -fPIC
+CFLAGS_TESTS	= $(CFLAGS) -lm -lnsl -lsocket
+LDFLAGS			= -lpthread -ldl -lsocket -lnsl
+LDFLAGS_PLUGIN	= -shared -lm
+endif
+

+ 2498 - 0
src/masterserver/docs/PROTOCOLS

@@ -0,0 +1,2498 @@
+ATTENTION:
+English is not my native language so if there are any spelling/grammar/...
+errors please send me a diff/email so I can correct them.
+
+This document describes the protocols used by master servers.
+
+Beware! This document is still chaotic and definitely incomplete.
+
+1. Quake 3 protocol
+	1.1. Master server
+		1.1.1. getservers
+		1.1.2. getserversResponse
+		1.1.3. getKeyAuthorize
+		1.1.4. getmotd
+	1.2. Dedicated/Listen server
+		1.2.1. getinfo
+		1.2.2. infoResponse
+		1.2.3. getstatus
+		1.2.4. statusResponse
+		1.2.5. getchallenge
+		1.2.6. challengeResponse
+		1.2.7. connect
+		1.2.8. connectResponse
+		1.2.9. heartbeat
+		1.2.10 print
+		1.2.11 getIpAuthorize
+		1.2.12 ipAuthorize
+		1.2.13 ping
+		1.2.14 disconnect
+
+2. Heretic 2 protocol
+	2.1. heartbeat
+	2.2. query
+	2.3. shutdown
+	2.4. status
+	2.5. print
+	2.6. info
+	2.7. info
+	2.8. getchallenge
+	2.9. challenge
+	2.10. connect
+	2.11. client_connect
+	2.12. servers
+	2.13. ping
+	2.14. ack
+
+3. Quake 2 protocol
+	3.1. heartbeat
+	3.2. query
+	3.3. shutdown
+	3.4. status
+	3.5. print
+	3.6. info
+	3.7. info
+	3.8. getchallenge
+	3.9. challenge
+	3.10. connect
+	3.11. client_connect
+	3.12. servers
+	3.13. ping
+	3.14. ack
+
+4. STV: Elite Force protocol
+	4.1. Master server
+		4.1.1. getservers
+		4.1.2. getserversResponse
+		4.1.3. getKeyAuthorize
+		4.1.4. getmotd
+	4.2. Dedicated/Listen server
+		4.2.1. getinfo
+		4.2.2. infoResponse
+		4.2.3. getstatus
+		4.2.4. statusResponse
+		4.2.5. getchallenge
+		4.2.6. challengeResponse
+		4.2.7. connect
+		4.2.8. connectResponse
+		4.2.9. heartbeat
+		4.2.10 print
+		4.2.11 heartstop
+
+5. Unreal Tournament protocol
+	5.1. heartbeat
+	5.2. info
+	5.3. status
+	5.4. REPORTQUERY
+	5.5. HTTP traffic
+	5.6. Master server traffic
+
+6. QuakeWorld protocol
+	6.1. status
+	6.2. n
+	6.3. c
+	6.4. d
+	6.5. ping
+	6.6. k
+	6.7. l
+	6.8. a
+	6.9. C
+
+7. HexenWorld protocol
+
+8. RtCW protocol
+	8.1. heartbeat
+	8.2. getinfo
+	8.3. getIpAuthorize
+	8.4. ipAuthorize
+	8.5. infoReponse
+	8.6. getmotd
+	8.7. motd
+
+9. Doom3 protocol
+	9.1. getServers
+	9.2. versionCheck
+	9.3. servers
+	9.4. getInfo
+	9.5. infoResponse
+	9.6. newVersion
+	9.7. heartbeat
+	9.8. srvAuth
+	9.9. auth
+	9.10. challenge
+	9.11. challengeResponse
+
+10. Half-Life protocol
+	10.1. s
+	10.2. q
+	10.3. 0
+
+11. Enemy Territory
+
+12. Links
+
+--
+
+1. Quake 3 protocol
+===================
+
+Q3 server port: UDP 27960
+
+Q3 master server DNS name:	master.quake3arena.com
+Q3 master server port:		UDP 27950
+Q3 MOTD server DNS name:	update.quake3arena.com
+Q3 MOTD server port:		UDP 27951
+Q3 auth server DNS name:	authorize.quake3arena.com
+Q3 auth server port:		UDP 27952
+
+
+1.1. Master server
+------------------
+
+1.1.1. getservers
+-----------------
+(UDP, Client -> Master:27950)
+
+0000  ff ff ff ff 67 65 74 73 65 72 76 65 72 73 20 36   ÿÿÿÿgetservers 6
+0010  36 20 65 6d 70 74 79 20 66 75 6c 6c 20 64 65 6d   6 empty full dem
+0020  6f 0a                                             o.    
+
+getservers		- command
+66				- protocol version
+					Q3 1.30 == 66
+					Q3 1.31 == 67
+					Q3 1.32	== 68
+					STEF1 1.20 == 24
+empty full demo	- indicates which servers should be displayed. Can be omitted
+				  to get servers which are neither full nor empty.
+The following keywords are known to me:
+	empty - empty servers (no player slots occupied)
+	full - full servers (all player slots occupied, except private slots)
+	demo - demo servers
+	ffa - Free For All servers
+	team - Team Deathmatch
+	tourney - Tournament (1on1) servers
+	ctf - Capture the Flag servers
+
+After some tests I've found out that the ID Q3 master server processes at
+least the protocol version. That means the master returns a server list which
+includes only servers who have the variable "protocol" set to 66.
+getservers packets are sent to master.quake3arena.com:27950.
+
+
+1.1.2. getserversResponse
+-------------------------
+(UDP, Master:27950 -> Client)
+
+0000  ff ff ff ff 67 65 74 73 65 72 76 65 72 73 52 65   ÿÿÿÿgetserversRe
+0010  73 70 6f 6e 73 65 5c d9 d3 f8 cc fb e7 5c 18 f3   sponse\ÙÓøÌûç\.ó
+0020  b2 d2 6d 38 5c 51 62 7f cd 6d 38 5c cb d9 3f fa   ²Òm8\Qb.Ím8\ËÙ?ú
+0030  e3 00 5c 18 30 ad b3 6d 38 5c 44 44 97 a2 6d 38   ã.\.0­³m8\DD.¢m8
+0040  5c 18 a4 31 01 6d 38 5c c3 e6 b3 55 6d 38 5c d9   \.¤1.m8\Ãæ³Um8\Ù
+0050  24 72 82 4e bd 5c 45 4f 54                        $r.N½\EOT
+
+getserversResponse is the response to a "getservers" packet.
+The IP and port address of the servers are encoded in 6 byte blocks
+(4 bytes IP, 2 bytes port) which are separated by "\" ASCII chars. (Hex 0x5C)
+The string "\EOT" marks the end of the packet.
+The master server sends multiple UDP packets if there are too many servers
+to fit into one packet. The limit for one packet is 112 servers which
+translates to:
+112*(6+1)	= 784 bytes (112 servers + delimiter)
++ 4			= 788 bytes (protocol marker)
++ 18		= 806 bytes (length of command)
++ 4			= 810 bytes ("\EOT")
+So every packet the ID Q3 master sends has a maximum of 810 bytes payload.
+
+
+1.1.3. getKeyAuthorize
+----------------------
+(UDP, Client -> Master:27952)
+
+0000  ff ff ff ff 67 65 74 4b 65 79 41 75 74 68 6f 72   ÿÿÿÿgetKeyAuthor
+0010  69 7a 65 20 30 20 78 78 78 78 78 78 78 78 78 78   ize 0 xxxxxxxxxx
+0020  78 78 78 78 78 78                                 xxxxxx
+
+The "x" characters are placeholders for the cd key which consists of
+16 characters out of [A-Za-z0-9].
+
+Note:
+ID's Q3 master server doesn't reply to the "getKeyAuthorize" command any more.
+I read somewhere that the authorization is back online because of PunkBuster.
+getKeyAuthorize packets are sent to authorize.quake3arena.com:27952.
+
+
+1.1.4. getmotd
+--------------
+(UDP, Client -> Master:27951)
+
+0000  ff ff ff ff 67 65 74 6d 6f 74 64 20 22 5c 76 65   ÿÿÿÿgetmotd "\ve
+0010  72 73 69 6f 6e 5c 51 33 20 31 2e 33 32 62 20 6c   rsion\Q3 1.32b l
+0020  69 6e 75 78 2d 69 33 38 36 20 4e 6f 76 20 31 34   inux-i386 Nov 14
+0030  20 32 30 30 32 5c 72 65 6e 64 65 72 65 72 5c 47    2002\renderer\G
+0040  65 46 6f 72 63 65 33 2f 41 47 50 2f 53 53 45 5c   eForce3/AGP/SSE\
+0050  63 68 61 6c 6c 65 6e 67 65 5c 32 31 31 30 35 34   challenge\211054
+0060  38 39 37 36 22 0a                                 8976".
+
+Q3 sends the variables "version", "renderer" and "challenge".
+
+Note:
+The Q3 client sends the "getmotd" packet to update.quake3arena.com:27951 UDP.
+
+
+1.1.5. motd
+-----------
+(UDP, Master:27951 -> Client)
+
+0000  ff ff ff ff 6d 6f 74 64 20 22 63 68 61 6c 6c 65   ÿÿÿÿmotd "challe
+0010  6e 67 65 5c 38 32 32 32 38 33 31 36 38 5c 6d 6f   nge\822283168\mo
+0020  74 64 5c 46 75 6c 6c 20 76 65 72 73 69 6f 6e 20   td\Full version 
+0030  6e 6f 77 20 61 76 61 69 6c 61 62 6c 65 20 66 6f   now available fo
+0040  72 20 64 6f 77 6e 6c 6f 61 64 21 21 5c 22         r download!!\"
+
+"motd" response of RtCW: Enemy Territory. Should be identical to Q3.
+The "challenge" value is parsed from the "getmotd" packet.
+
+
+1.2. Dedicated/Listen Server
+----------------------------
+
+1.2.1. getinfo
+--------------
+(UDP, Client -> Server:27960)
+
+0000  ff ff ff ff 67 65 74 69 6e 66 6f 0a               ÿÿÿÿgetinfo.
+
+No parameters known.
+
+
+1.2.2. infoResponse
+-------------------
+(UDP, Server:27960 -> Client)
+
+0000  ff ff ff ff 69 6e 66 6f 52 65 73 70 6f 6e 73 65   ÿÿÿÿinfoResponse
+0010  0a 5c 70 75 6e 6b 62 75 73 74 65 72 5c 30 5c 70   .\punkbuster\0\p
+0020  75 72 65 5c 30 5c 67 61 6d 65 74 79 70 65 5c 30   ure\0\gametype\0
+0030  5c 73 76 5f 6d 61 78 63 6c 69 65 6e 74 73 5c 31   \sv_maxclients\1
+0040  35 5c 63 6c 69 65 6e 74 73 5c 30 5c 6d 61 70 6e   5\clients\0\mapn
+0050  61 6d 65 5c 6d 6b 73 70 61 63 65 64 6d 30 33 5c   ame\mkspacedm03\
+0060  68 6f 73 74 6e 61 6d 65 5c 53 70 79 72 6f 73 20   hostname\Spyros 
+0070  4c 61 69 72 5c 70 72 6f 74 6f 63 6f 6c 5c 36 38   Lair\protocol\68
+
+In comparison, a Q3 1.30 dedicated server running baseq3:
+
+0000  ff ff ff ff 69 6e 66 6f 52 65 73 70 6f 6e 73 65   ÿÿÿÿinfoResponse
+0010  0a 5c 73 76 5f 61 6c 6c 6f 77 41 6e 6f 6e 79 6d   .\sv_allowAnonym
+0020  6f 75 73 5c 30 5c 70 75 72 65 5c 31 5c 67 61 6d   ous\0\pure\1\gam
+0030  65 74 79 70 65 5c 34 5c 73 76 5f 6d 61 78 63 6c   etype\4\sv_maxcl
+0040  69 65 6e 74 73 5c 31 32 5c 63 6c 69 65 6e 74 73   ients\12\clients
+0050  5c 31 32 5c 6d 61 70 6e 61 6d 65 5c 71 33 63 74   \12\mapname\q3ct
+0060  66 33 5c 68 6f 73 74 6e 61 6d 65 5c 44 65 6d 6f   f3\hostname\Demo
+0070  6e 20 55 4b 20 51 75 61 6b 65 33 20 43 54 46 20   n UK Quake3 CTF 
+0080  28 31 29 5c 70 72 6f 74 6f 63 6f 6c 5c 36 36      (1)\protocol\66
+
+
+"infoResponse" is the server's response to a "getinfo" packet.
+After the string "infoResponse" a couple of console variables follow with
+their respective values. The entries are separated by "\".
+
+Note:
+The server returns only a few variables:
+punkbuster
+pure
+gametype
+sv_maxclients
+clients
+mapname
+hostname
+protocol
+
+The Q3 1.30 server adds "sv_allowAnonymous", but excludes "punkbuster".
+I don't know about "sv_allowAnonymous" but "punkbuster" is excluded because
+PB support was added in version 1.32.
+
+Q3 sends "getinfo" packets to 255.255.255.255:2796[0-3] to find other Q3
+servers in a LAN.
+
+
+1.2.3. getstatus
+--------------
+(UDP, Client -> Server:27960)
+
+0000  ff ff ff ff 67 65 74 73 74 61 74 75 73 0a         ÿÿÿÿgetstatus.
+
+0000  ff ff ff ff 67 65	74 73 74 61 74 75 73 20 30 31   ÿÿÿÿgetstatus 10
+0010  36 31 38 32 39 35 34 37 37 32 33                  61829547723
+
+"getstatus" is a verbose version of "getinfo" and is sent by the Q3 master
+to the server after every "heartbeat". The only optional parameter is a
+challenge value, which is afaik only used by the Q3 master.
+
+
+1.2.4. statusResponse
+---------------------
+(UDP, Server:27960 -> Client)
+
+An empty baseq3 1.32 server:
+
+0000  ff ff ff ff 73 74 61 74 75 73 52 65 73 70 6f 6e   ÿÿÿÿstatusRespon
+0010  73 65 0a 5c 73 76 5f 70 75 6e 6b 62 75 73 74 65   se.\sv_punkbuste
+0020  72 5c 30 5c 63 61 70 74 75 72 65 6c 69 6d 69 74   r\0\capturelimit
+0030  5c 38 5c 67 5f 6d 61 78 47 61 6d 65 43 6c 69 65   \8\g_maxGameClie
+0040  6e 74 73 5c 30 5c 73 76 5f 6d 61 78 63 6c 69 65   nts\0\sv_maxclie
+0050  6e 74 73 5c 31 35 5c 74 69 6d 65 6c 69 6d 69 74   nts\15\timelimit
+0060  5c 32 30 5c 66 72 61 67 6c 69 6d 69 74 5c 32 30   \20\fraglimit\20
+0070  5c 64 6d 66 6c 61 67 73 5c 30 5c 73 76 5f 68 6f   \dmflags\0\sv_ho
+0080  73 74 6e 61 6d 65 5c 53 70 79 72 6f 73 20 4c 61   stname\Spyros La
+0090  69 72 5c 73 76 5f 6d 61 78 52 61 74 65 5c 38 30   ir\sv_maxRate\80
+00a0  30 30 5c 73 76 5f 6d 69 6e 50 69 6e 67 5c 30 5c   00\sv_minPing\0\
+00b0  73 76 5f 6d 61 78 50 69 6e 67 5c 30 5c 73 76 5f   sv_maxPing\0\sv_
+00c0  66 6c 6f 6f 64 50 72 6f 74 65 63 74 5c 31 5c 76   floodProtect\1\v
+00d0  65 72 73 69 6f 6e 5c 51 33 20 31 2e 33 32 20 6c   ersion\Q3 1.32 l
+00e0  69 6e 75 78 2d 69 33 38 36 20 4f 63 74 20 20 37   inux-i386 Oct  7
+00f0  20 32 30 30 32 5c 67 5f 67 61 6d 65 74 79 70 65    2002\g_gametype
+0100  5c 30 5c 70 72 6f 74 6f 63 6f 6c 5c 36 38 5c 6d   \0\protocol\68\m
+0110  61 70 6e 61 6d 65 5c 6d 6b 73 70 61 63 65 64 6d   apname\mkspacedm
+0120  30 33 5c 73 76 5f 70 72 69 76 61 74 65 43 6c 69   03\sv_privateCli
+0130  65 6e 74 73 5c 30 5c 73 76 5f 61 6c 6c 6f 77 44   ents\0\sv_allowD
+0140  6f 77 6e 6c 6f 61 64 5c 30 5c 62 6f 74 5f 6d 69   ownload\0\bot_mi
+0150  6e 70 6c 61 79 65 72 73 5c 30 5c 67 61 6d 65 6e   nplayers\0\gamen
+0160  61 6d 65 5c 62 61 73 65 71 33 5c 67 5f 6e 65 65   ame\baseq3\g_nee
+0170  64 70 61 73 73 5c 30 0a                           dpass\0.
+
+In comparison a baseq3 1.30 server with 8 players:
+
+0000  ff ff ff ff 73 74 61 74 75 73 52 65 73 70 6f 6e   ÿÿÿÿstatusRespon
+0010  73 65 0a 5c 76 65 72 73 69 6f 6e 5c 51 33 20 31   se.\version\Q3 1
+0020  2e 33 30 20 6c 69 6e 75 78 2d 69 33 38 36 20 53   .30 linux-i386 S
+0030  65 70 20 32 37 20 32 30 30 31 5c 64 6d 66 6c 61   ep 27 2001\dmfla
+0040  67 73 5c 30 5c 66 72 61 67 6c 69 6d 69 74 5c 30   gs\0\fraglimit\0
+0050  30 5c 74 69 6d 65 6c 69 6d 69 74 5c 31 35 5c 67   0\timelimit\15\g
+0060  5f 67 61 6d 65 74 79 70 65 5c 30 5c 70 72 6f 74   _gametype\0\prot
+0070  6f 63 6f 6c 5c 36 36 5c 6d 61 70 6e 61 6d 65 5c   ocol\66\mapname\
+0080  71 33 64 6d 36 5c 73 76 5f 70 72 69 76 61 74 65   q3dm6\sv_private
+0090  43 6c 69 65 6e 74 73 5c 33 5c 73 76 5f 68 6f 73   Clients\3\sv_hos
+00a0  74 6e 61 6d 65 5c 20 20 2d 2d 3d 3d 20 51 20 55   tname\  --== Q U 
+00b0  20 41 20 44 20 52 20 55 20 4d 20 3d 3d 2d 2d 5c    A D R U M ==--\
+00c0  73 76 5f 6d 61 78 63 6c 69 65 6e 74 73 5c 31 38   sv_maxclients\18
+00d0  5c 73 76 5f 6d 61 78 52 61 74 65 5c 32 35 30 30   \sv_maxRate\2500
+00e0  30 5c 73 76 5f 6d 69 6e 50 69 6e 67 5c 30 5c 73   0\sv_minPing\0\s
+00f0  76 5f 6d 61 78 50 69 6e 67 5c 30 5c 73 76 5f 66   v_maxPing\0\sv_f
+0100  6c 6f 6f 64 50 72 6f 74 65 63 74 5c 31 5c 73 76   loodProtect\1\sv
+0110  5f 61 6c 6c 6f 77 41 6e 6f 6e 79 6d 6f 75 73 5c   _allowAnonymous\
+0120  30 5c 73 76 5f 61 6c 6c 6f 77 44 6f 77 6e 6c 6f   0\sv_allowDownlo
+0130  61 64 5c 30 5c 67 61 6d 65 73 74 61 72 74 75 70   ad\0\gamestartup
+0140  5c 32 35 2e 30 38 2e 32 30 30 33 20 30 35 3a 30   \25.08.2003 05:0
+0150  30 3a 31 31 5c 41 64 6d 69 6e 69 73 74 72 61 74   0:11\Administrat
+0160  6f 72 5c 4b 61 68 6c 65 73 73 5c 55 52 4c 5c 77   or\Kahless\URL\w
+0170  77 77 2e 62 6f 68 77 2d 63 6c 61 6e 2e 64 65 5c   ww.bohw-clan.de\
+0180  43 6c 61 6e 5c 5b 42 6f 68 57 5d 5c 67 61 6d 65   Clan\[BohW]\game
+0190  6e 61 6d 65 5c 62 61 73 65 71 33 5c 67 5f 6d 61   name\baseq3\g_ma
+01a0  78 47 61 6d 65 43 6c 69 65 6e 74 73 5c 30 5c 63   xGameClients\0\c
+01b0  61 70 74 75 72 65 6c 69 6d 69 74 5c 38 5c 67 5f   apturelimit\8\g_
+01c0  6e 65 65 64 70 61 73 73 5c 30 0a 35 20 34 38 20   needpass\0.5 48 
+01d0  22 43 33 50 4f 22 0a 30 20 33 33 39 20 22 5e 31   "C3PO".0 339 "^1
+01e0  4e 5e 36 61 53 74 6f 5e 31 4c 5e 36 61 54 6b 5e   N^6aSto^1L^6aTk^
+01f0  31 41 22 0a 32 20 35 30 20 22 5e 31 5b 5e 33 4d   1A".2 50 "^1[^3M 
+0200  2e 5e 31 41 2e 5e 33 44 2e 5e 31 5d 5e 31 43 5e   .^1A.^3D.^1]^1C^
+0210  33 4f 4c 4f 22 0a 32 37 20 34 30 20 22 5e 33 2a   3OLO".27 40 "^3*
+0220  43 6f 6c 2e 4b 75 72 74 7a 2a 22 0a 33 34 20 35   Col.Kurtz*".34 5
+0230  30 20 22 6d 63 2d 42 61 73 74 61 72 44 22 0a 32   0 "mc-BastarD".2
+0240  39 20 34 38 20 22 5e 31 5b 5e 33 4d 2e 5e 31 41   9 48 "^1[^3M.^1A
+0250  2e 5e 33 44 2e 5e 31 5d 5e 31 4b 5e 33 31 5e 31   .^3D.^1]^1K^31^1
+0260  4c 4c 61 22 0a 33 32 20 35 30 20 22 5e 5e 30 5d   LLa".32 50 "^^0]
+0270  46 46 41 5b 46 69 73 68 5e 37 69 22 0a 36 20 35   FFA[Fish^7i".6 5
+0280  30 20 22 52 32 44 32 22 0a                        0 "R2D2".
+
+The following is a "statusResponse" answer to a "getstatus" request with
+a challenge value:
+
+0000  ff ff ff ff 73 74 61 74 75 73 52 65 73 70 6f 6e   ÿÿÿÿstatusRespon
+0010  73 65 0a 5c 63 68 61 6c 6c 65 6e 67 65 5c 31 30   se.\challenge\10
+0020  36 31 38 32 39 35 34 37 37 32 33 5c 73 76 5f 70   61829547723\sv_p
+0030  75 6e 6b 62 75 73 74 65 72 5c 30 5c 67 5f 6d 61   unkbuster\0\g_ma
+0040  78 47 61 6d 65 43 6c 69 65 6e 74 73 5c 30 5c 63   xGameClients\0\c
+0050  61 70 74 75 72 65 6c 69 6d 69 74 5c 30 5c 73 76   apturelimit\0\sv
+0060  5f 6d 61 78 63 6c 69 65 6e 74 73 5c 38 5c 74 69   _maxclients\8\ti
+0070  6d 65 6c 69 6d 69 74 5c 30 5c 66 72 61 67 6c 69   melimit\0\fragli
+0080  6d 69 74 5c 31 35 30 5c 64 6d 66 6c 61 67 73 5c   mit\150\dmflags\
+0090  30 5c 73 76 5f 6d 61 78 50 69 6e 67 5c 30 5c 73   0\sv_maxPing\0\s
+00a0  76 5f 6d 69 6e 50 69 6e 67 5c 30 5c 73 76 5f 68   v_minPing\0\sv_h
+00b0  6f 73 74 6e 61 6d 65 5c 48 75 65 68 6e 65 72 46   ostname\HuehnerF
+00c0  61 72 6d 5c 73 76 5f 6d 61 78 52 61 74 65 5c 30   arm\sv_maxRate\0
+00d0  5c 73 76 5f 66 6c 6f 6f 64 50 72 6f 74 65 63 74   \sv_floodProtect
+00e0  5c 31 5c 76 65 72 73 69 6f 6e 5c 51 33 20 31 2e   \1\version\Q3 1.
+00f0  33 32 62 20 6c 69 6e 75 78 2d 69 33 38 36 20 4e   32b linux-i386 N
+0100  6f 76 20 31 34 20 32 30 30 32 5c 67 5f 67 61 6d   ov 14 2002\g_gam
+0110  65 74 79 70 65 5c 30 5c 70 72 6f 74 6f 63 6f 6c   etype\0\protocol
+0120  5c 36 38 5c 6d 61 70 6e 61 6d 65 5c 71 33 64 6d   \68\mapname\q3dm
+0130  30 5c 73 76 5f 70 72 69 76 61 74 65 43 6c 69 65   0\sv_privateClie
+0140  6e 74 73 5c 30 5c 73 76 5f 61 6c 6c 6f 77 44 6f   nts\0\sv_allowDo
+0150  77 6e 6c 6f 61 64 5c 30 5c 62 6f 74 5f 6d 69 6e   wnload\0\bot_min
+0160  70 6c 61 79 65 72 73 5c 30 5c 67 61 6d 65 6e 61   players\0\gamena
+0170  6d 65 5c 62 61 73 65 71 33 5c 67 5f 6e 65 65 64   me\baseq3\g_need
+0180  70 61 73 73 5c 30 0a                              pass\0.
+
+"statusResponse" is the server's answer to "getstatus" packets.
+Following the "statusResponse" string are console variables and their values,
+separated by "\". A LF char (ASCII Hex 0x0A) marks the end of the list of
+console variables and also marks the beginning of the player information.
+Player information blocks are separated by LF chars (ASCII Hex 0x0A).
+The blocks themselves contain information on the number of frags, player
+ping lateny and the player's nickname. The information inside the block is
+separated by whitespace. (ASCII Hex 0x20) Additionally the nickname is
+enclosed in quotes. (ASCII Hex 0x22)
+The blocks look like the following:
+\n<number_of_frags> <ping> "<nickname>"\n
+
+
+1.2.5. getchallenge
+-----------------
+(UDP, Client -> Server:27960)
+
+0000  ff ff ff ff 67 65 74 63 68 61 6c 6c 65 6e 67 65   ÿÿÿÿgetchallenge
+
+No parameters known.
+
+
+1.2.6. challengeResponse
+----------------------
+(UDP, Server:27960 -> Client)
+
+0000  ff ff ff ff 63 68 61 6c 6c 65 6e 67 65 52 65 73   ÿÿÿÿchallengeRes
+0010  70 6f 6e 73 65 20 39 36 31 31 36 36 35 31         ponse 96116651
+
+I think the value after the "challengeResponse" string is a signed 32 bit
+integer.
+
+
+1.2.7. connect
+------------
+(UDP, Client -> Server:27960)
+
+0000  ff ff ff ff 63 6f 6e 6e 65 63 74 20 01 53 44 74   ÿÿÿÿconnect .SDt
+0010  30 8e 05 0c c7 26 c3 14 ec 8e f9 67 70 1a 36 c1   0...Ç&Ã.ì.ùgp.6Á
+0020  48 f9 09 2b 72 38 3a 0e 83 bd e2 54 b8 44 5b 61   Hù.+r8:..½âT¸D[a
+0030  49 d7 95 83 6e e8 32 1c 7d 93 d1 67 09 d7 82 a5   I×..nè2.}.Ñg.×.¥
+0040  c5 24 ad 43 d0 c0 20 d5 88 70 44 05 cc 14 61 5e   Å$­CÐÀ Õ.pD.Ì.a^
+0050  e1 cf 92 de 0c 31 e0 d4 00 a6 39 61 7f e4 1e 84   áÏ.Þ.1àÔ.¦9a.ä..
+0060  ce 69 74 af e7 19 30 9f ac 58 f3 18 cd 03 ce 02   Îit¯ç.0.¬Xó.Í.Î.
+0070  cf 44 6e 59 d6 d3 41 3d 9e 77 6c 5f aa 43 f1 08   ÏDnYÖÓA=.wl_ªCñ.
+0080  1c d4 35 e9 a5 6c bc fe fd fd 9e e5 9f e2 87 8f   .Ô5é¥l¼þýý.å.â..
+0090  9e 6d d7 34 e2 e8 39 38 e8 97 87 61 66 4d 08 6b   .m×4âè98è..afM.k
+00a0  68 2b 5b 0b 71 e8 6e df f3 ef 36 3e f7 31 cb fa   h+[.qènßóï6>÷1Ëú
+00b0  05 78 1c b5 8d 20 e6 6a 9c 9d d5 8b 8b c1 22 6a   .x.µ. æj..Õ..Á"j
+00c0  d9 47 4f 74 e0 f9 be bb d1 40 22 d3 f2 0d a8 fe   ÙGOtàù¾»Ñ@"Óò.¨þ
+00d0  6f 3f d4 60 1b 76 14 91 95 59 d6 57 f8 0e 82 a7   o?Ô`.v...YÖWø..§
+00e0  75 31 9c 6f 8b be 15 70 9d f5 4b 83 6d 58 2f ce   u1.o.¾.p.õK.mX/Î
+00f0  3a bf de a7 9e bf 34 86 75 56 30 96 c3 7e 2f e9   :¿Þ§.¿4.uV0.Ã~/é
+0100  61 e1 61 52 34 8e 6b 9e c2 cc 41 8c e1 48 96 d5   aáaR4.k.ÂÌA.áH.Õ
+0110  91 8b db 66 18 19 13 fc e8 00                     ..Ûf...üè.
+
+After a "challengeResponse" packet the client sends a "connect" packet, which
+looks like this one.
+TODO: information is huffman encoded; see luigi auriemma's research
+
+
+1.2.8. connectResponse
+--------------------
+(UDP, Server:27960 -> Client)
+
+0000  00 01 6d 38 6d 38 00 1b e1 3e ff ff ff ff 63 6f   ..m8m8..á>ÿÿÿÿco
+0010  6e 6e 65 63 74 52 65 73 70 6f 6e 73 65            nnectResponse   
+
+Response to "connect" packet.
+No parameters known.
+
+
+1.2.9. heartbeat
+--------------
+(UDP, Server:27960 -> Master:27950)
+
+0000  ff ff ff ff 68 65 61 72 74 62 65 61 74 20 51 75   ÿÿÿÿheartbeat Qu
+0010  61 6b 65 41 72 65 6e 61 2d 31 0a                  akeArena-1.
+
+Heartbeats are sent by Q3 servers to the masters specified in the console
+variables sv_master[1-8]. Heartbeats are sent after every map change, when
+a player enters or exits the game or every 300 seconds.
+The string "QuakeArena-1" seems to be some sort of ID.
+The console variable "sv_master1" defaults to master.quake3arena.com.
+
+If a server is shutdown then it sends 2 heartbeats in quick succession to
+signal the master that it has shutdown.
+
+The master sends a "getstatus" packet on every "heartbeat" packet to gather
+information about the server. (e.g. "protocol", etc.)
+
+
+1.2.10 print
+------------
+(UDP, Server:27960 -> Client)
+
+0000  ff ff ff ff 70 72 69 6e 74 0a 53 65 72 76 65 72   ÿÿÿÿprint.Server
+0010  20 75 73 65 73 20 70 72 6f 74 6f 63 6f 6c 20 76    uses protocol v
+0020  65 72 73 69 6f 6e 20 36 36 2e 0a                  ersion 66..
+
+The server or master can tell the Q3 client to show a text message to the
+user with the "print" packet. This packet is used for messages like
+"Invalid Password", etc.
+
+
+1.2.11 getIpAuthorize
+---------------------
+(UDP, Server:27960 -> Master:27950)
+
+0000  ff ff ff ff 67 65 74 49 70 41 75 74 68 6f 72 69   ÿÿÿÿgetIpAuthori
+0010  7a 65 20 2d 31 36 33 32 38 38 38 30 36 31 20 78   ze -1632888061 x
+0020  78 78 2e 78 78 78 2e 78 78 78 2e 78 78 20 62 61   xx.xxx.xxx.xx ba
+0030  73 65 71 33 20 30 20 31                           seq3 0 1
+
+When a client connects to the server, the server asks the master if the
+client's IP is authorized by the master. (A client's IP is authorized by
+it sending a getKeyAuthorize with a valid cd key to the master)
+The "x" characters are placeholders for the client IP.
+The first 3 parameters are pretty obvious; challenge number, client IP and
+modification ID string. The 2 trailing numbers are unknown to me.
+
+
+1.2.12 ipAuthorize
+------------------
+(UDP, Master:27950 -> Server:27960)
+
+0000  ff ff ff ff 69 70 41 75 74 68 6f 72 69 7a 65 20   ÿÿÿÿipAuthorize 
+0010  2d 31 36 33 32 38 38 38 30 36 31 20 61 63 63 65   -1632888061 acce
+0020  70 74 20 4b 45 59 5f 49 53 5f 47 4f 4f 44         pt KEY_IS_GOOD
+
+TODO: packet which denies authorization
+
+This is a reply to a "getIpAuthorize" packet. As you can see the challenge
+number is present again. The 2 strings after the challenge number are unknown
+to me.
+
+
+1.2.13 ping
+-----------
+(UDP, Client -> Server:27960)
+
+0000  70 69 6e 67                                       ping
+
+Sending a packet which contains only the string "ping" makes the server reply
+with a "disconnect" packet.
+
+
+1.2.14 disconnect
+-----------------
+(UDP, Server:27960 -> Client)
+
+0000  ff ff ff ff 64 69 73 63 6f 6e 6e 65 63 74         ÿÿÿÿdisconnect
+
+Reply to a "ping" packet.
+
+--
+
+2. Heretic 2 protocol
+=====================
+Heretic 2 server port: UDP 28910
+
+Heretic 2 master server IP:			209.98.56.7
+Heretic 2 master server DNS name:	master.ravensoft.com
+Heretic 2 master server port:		UDP 28900
+
+Heretic 2 Gamespy master IP:	207.38.8.34
+Heretic 2 Gamespy master port:	UDP 27900
+
++set public 1
+public 1
+setmaster 10.0.0.1 10.0.0.2 10.0.0.3 (max. 7 master servers)
+
+
+2.1.1. shutdown
+---------------
+(UDP, Server:28910 -> Master:28900)
+
+0000  ff ff ff ff 73 68 75 74 64 6f 77 6e                ÿÿÿÿshutdown
+
+Tells the master that the server is shutting down.
+
+
+2.1.2. heartbeat
+----------------
+(UDP, Server:28910 -> Master:28900)
+
+Gamespy heartbeat packet:
+
+0000  5c 68 65 61 72 74 62 65 61 74 5c 32 38 39 31 31    \heartbeat\28911
+0010  5c 67 61 6d 65 6e 61 6d 65 5c 68 65 72 65 74 69    \gamename\hereti
+0020  63 32                                              c2
+
+
+H2 master heartbeat packet, no players:
+
+0000  ff ff ff ff 68 65 61 72 74 62 65 61 74 0a 5c 6d    ÿÿÿÿheartbeat.\m
+0010  61 70 6e 61 6d 65 5c 64 6d 63 69 74 61 64 65 6c    apname\dmcitadel
+0020  5c 6e 6f 6d 6f 6e 73 74 65 72 73 5c 30 5c 67 61    \nomonsters\0\ga
+0030  6d 65 64 61 74 65 5c 41 75 67 20 20 38 20 32 30    medate\Aug  8 20
+0040  30 31 5c 67 61 6d 65 6e 61 6d 65 5c 48 65 72 65    01\gamename\Here
+0050  74 69 63 32 76 31 36 5c 6d 61 78 63 6c 69 65 6e    tic2v16\maxclien
+0060  74 73 5c 38 5c 70 72 6f 74 6f 63 6f 6c 5c 35 31    ts\8\protocol\51
+0070  5c 63 68 65 61 74 73 5c 30 5c 74 69 6d 65 6c 69    \cheats\0\timeli
+0080  6d 69 74 5c 30 5c 66 72 61 67 6c 69 6d 69 74 5c    mit\0\fraglimit\
+0090  30 5c 64 6d 66 6c 61 67 73 5c 33 32 37 36 38 5c    0\dmflags\32768\
+00a0  64 65 61 74 68 6d 61 74 63 68 5c 31 5c 76 65 72    deathmatch\1\ver
+00b0  73 69 6f 6e 5c 31 2e 30 36 61 2e 30 31 2e 30 35    sion\1.06a.01.05
+00c0  30 34 2e 30 31 3a 20 20 78 38 36 20 41 75 67 20    04.01:  x86 Aug 
+00d0  20 38 20 32 30 30 31 20 52 45 4c 45 41 53 45 5c     8 2001 RELEASE\
+00e0  61 64 76 61 6e 63 65 64 73 74 61 66 66 5c 31 5c    advancedstaff\1\
+00f0  68 6f 73 74 6e 61 6d 65 5c 48 75 65 68 6e 65 72    hostname\Huehner
+0100  66 61 72 6d 5c 75 73 65 72 64 69 72 5c 2f 68 6f    farm\userdir\/ho
+0110  6d 65 2f 61 6e 64 72 65 2f 2e 6c 6f 6b 69 2f 68    me/andre/.loki/h
+0120  65 72 65 74 69 63 32 0a                            eretic2.
+
+H2 master heartbeat packet, 1 player:
+
+0000  ff ff ff ff 68 65 61 72 74 62 65 61 74 0a 5c 6d    ÿÿÿÿheartbeat.\m
+0010  61 70 6e 61 6d 65 5c 64 6d 74 6f 77 65 72 5c 6e    apname\dmtower\n
+0020  6f 6d 6f 6e 73 74 65 72 73 5c 30 5c 67 61 6d 65    omonsters\0\game
+0030  64 61 74 65 5c 41 75 67 20 20 38 20 32 30 30 31    date\Aug  8 2001
+0040  5c 67 61 6d 65 6e 61 6d 65 5c 48 65 72 65 74 69    \gamename\Hereti
+0050  63 32 76 31 36 5c 6d 61 78 63 6c 69 65 6e 74 73    c2v16\maxclients
+0060  5c 38 5c 70 72 6f 74 6f 63 6f 6c 5c 35 31 5c 63    \8\protocol\51\c
+0070  68 65 61 74 73 5c 30 5c 74 69 6d 65 6c 69 6d 69    heats\0\timelimi
+0080  74 5c 30 5c 66 72 61 67 6c 69 6d 69 74 5c 30 5c    t\0\fraglimit\0\
+0090  64 6d 66 6c 61 67 73 5c 33 32 37 36 38 5c 64 65    dmflags\32768\de
+00a0  61 74 68 6d 61 74 63 68 5c 31 5c 76 65 72 73 69    athmatch\1\versi
+00b0  6f 6e 5c 31 2e 30 36 61 2e 30 31 2e 30 35 30 34    on\1.06a.01.0504
+00c0  2e 30 31 3a 20 20 78 38 36 20 41 75 67 20 20 38    .01:  x86 Aug  8
+00d0  20 32 30 30 31 20 52 45 4c 45 41 53 45 5c 61 64     2001 RELEASE\ad
+00e0  76 61 6e 63 65 64 73 74 61 66 66 5c 31 5c 68 6f    vancedstaff\1\ho
+00f0  73 74 6e 61 6d 65 5c 48 75 65 68 6e 65 72 66 61    stname\Huehnerfa
+0100  72 6d 5c 75 73 65 72 64 69 72 5c 2f 68 6f 6d 65    rm\userdir\/home
+0110  2f 61 6e 64 72 65 2f 2e 6c 6f 6b 69 2f 68 65 72    /andre/.loki/her
+0120  65 74 69 63 32 0a 2d 32 20 31 37 20 22 43 68 69    etic2.-2 17 "Chi
+0130  63 6b 65 6e 4d 61 6e 22 0a                         ckenMan".
+
+
+The "heartbeat" string is followed by a "\n" (Hex 0x0A) and some variables,
+as well as their values. The variable block ends with a "\n" (Hex 0x0A).
+The blocks are separated by "\" (Hex 0x5C) characters.
+A list of the values in the packet follows:
+mapname
+nomonsters
+gamedate
+gamename
+maxclients
+protocol
+cheats
+timelimit
+fraglimit
+dmflags
+deathmatch
+version
+advancedstaff
+hostname
+userdir
+
+After the variable block player information is appended, if there are any
+players on the server. The player information is separated in blocks for each
+player. The blocks are separated by "\n" (Hex 0x0A) characters and contain
+the player's score, ping and nickname. Score, ping and nickname are separated
+by " " (Hex 0x20) characters. Additionally the nickname is enclosed in quotes.
+Heartbeats are sent after a map change or every 300 seconds.
+
+
+2.1.3. info
+-----------
+(UDP, Client -> Server:28910)
+
+Broadcast packet:
+-----------------
+0000  ff ff ff ff 69 6e 66 6f 20 35 31                   ÿÿÿÿinfo 51
+
+Reply packet:
+-------------
+0000  ff ff ff ff 69 6e 66 6f 0a 44 69 76 69 6e 65 20    ÿÿÿÿinfo.Divine 
+0010  52 65 66 6c 65 63 74 69 6f 6e 20 5b 42 6c 61 64    Reflection [Blad
+0020  65 6d 61 74 63 68 5d 0a 20 20 20 67 74 6f 6d 62    ematch].   gtomb
+0030  20 20 30 2f 31 36 0a                                 0/16.
+
+The "info" packet is used to search for H2 servers in a LAN.
+When a client sends a "info" packet to 255.255.255.255:28910, H2 servers
+reply with a "info" packet. The number "51" is the network protocol version.
+Only servers with the same protocol version will reply to a "info" packet.
+A "info" reply contains the variables "hostname", "mapname", number of players
+and maximum number of players on the server.
+The blocks are separated by "\n" (Hex 0x0A) and " " (Hex 0x20) characters.
+
+
+2.1.4. status
+-------------
+(UDP, Client -> Server:28910)
+
+0000  ff ff ff ff 73 74 61 74 75 73 0a                   ÿÿÿÿstatus.
+
+"status" is a request for server information. Servers reply with a "print"
+packet.
+
+
+2.1.5. print
+------------
+(UDP, Server:28910 -> Client)
+
+0000  ff ff ff ff 70 72 69 6e 74 0a 5c 6d 61 70 6e 61    ÿÿÿÿprint.\mapna
+0010  6d 65 5c 67 64 6f 6d 65 5c 6e 6f 6d 6f 6e 73 74    me\gdome\nomonst
+0020  65 72 73 5c 30 5c 67 61 6d 65 64 61 74 65 5c 4f    ers\0\gamedate\O
+0030  63 74 20 32 31 20 32 30 30 33 5c 67 61 6d 65 6e    ct 21 2003\gamen
+0040  61 6d 65 5c 48 65 72 65 74 69 63 32 76 31 36 5c    ame\Heretic2v16\
+0050  67 61 6d 65 64 69 72 5c 74 6d 6f 64 5c 6d 61 78    gamedir\tmod\max
+0060  63 6c 69 65 6e 74 73 5c 31 36 5c 70 72 6f 74 6f    clients\16\proto
+0070  63 6f 6c 5c 35 31 5c 63 68 65 61 74 73 5c 30 5c    col\51\cheats\0\
+0080  74 69 6d 65 6c 69 6d 69 74 5c 34 30 5c 66 72 61    timelimit\40\fra
+0090  67 6c 69 6d 69 74 5c 32 30 5c 61 64 76 61 6e 63    glimit\20\advanc
+00a0  65 64 73 74 61 66 66 5c 31 5c 64 6d 66 6c 61 67    edstaff\1\dmflag
+00b0  73 5c 33 32 37 38 34 5c 64 65 61 74 68 6d 61 74    s\32784\deathmat
+00c0  63 68 5c 31 5c 76 65 72 73 69 6f 6e 5c 31 2e 30    ch\1\version\1.0
+00d0  36 61 2e 30 31 2e 30 35 30 34 2e 30 31 3a 20 20    6a.01.0504.01:  
+00e0  78 38 36 20 41 75 67 20 20 38 20 32 30 30 31 20    x86 Aug  8 2001 
+00f0  52 45 4c 45 41 53 45 5c 68 6f 73 74 6e 61 6d 65    RELEASE\hostname
+0100  5c 44 69 76 69 6e 65 20 52 65 66 6c 65 63 74 69    \Divine Reflecti
+0110  6f 6e 20 5b 42 6c 61 64 65 6d 61 74 63 68 5d 5c    on [Bladematch]\
+0120  75 73 65 72 64 69 72 5c 2f 68 6f 6d 65 2f 68 65    userdir\/home/he
+0130  72 65 74 69 63 32 2f 2e 6c 6f 6b 69 2f 68 65 72    retic2/.loki/her
+0140  65 74 69 63 32 5c 67 61 6d 65 5c 74 6d 6f 64 0a    etic2\game\tmod.
+
+A "print" packet is a reply to a "status" packet and contains information
+about the server. Separator is a "\" (Hex 0x5C) character.
+A list of variables which are sent in the packet:
+mapname
+nomonsters
+gamedate
+gamename
+maxclients
+protocol
+cheats
+timelimit
+fraglimit
+advancedstaff
+dmflags
+deathmatch
+version
+hostname
+userdir
+game
+
+The "game" variable only appears if it is set. (i.e. only when playing a mod)
+
+
+2.1.6. getchallenge
+-------------------
+(UDP, Client -> Server:28910)
+
+0000  ff ff ff ff 67 65 74 63 68 61 6c 6c 65 6e 67 65    ÿÿÿÿgetchallenge
+0010  0a                                                 .     
+
+A "getchallenge" packet is the first step to establish a connection to a
+Heretic2 server. As the name denotes the packet is a request for a challenge
+number.
+
+
+2.1.7. challenge
+----------------
+(UDP, Server:28910 -> Client)
+
+0000  ff ff ff ff 63 68 61 6c 6c 65 6e 67 65 20 32 30    ÿÿÿÿchallenge 20
+0010  38 30 30 36 37 32 33                               8006723
+
+The "challenge" packet is the reply to a "getchallenge" packet. The number
+returned seems to be a signed 32 bit integer.
+
+
+2.1.8. connect
+--------------
+(UDP, Client -> Server:28910)
+
+0000  ff ff ff ff 63 6f 6e 6e 65 63 74 20 35 31 20 35    ÿÿÿÿconnect 51 5
+0010  36 34 20 32 30 38 30 30 36 37 32 33 20 22 5c 61    64 208006723 "\a
+0020  75 74 6f 77 65 61 70 6f 6e 5c 30 5c 6e 61 6d 65    utoweapon\0\name
+0030  5c 43 68 69 63 6b 65 6e 4d 61 6e 5c 73 6b 69 6e    \ChickenMan\skin
+0040  5c 6d 61 6c 65 2f 52 6f 67 75 65 5c 72 61 74 65    \male/Rogue\rate
+0050  5c 32 35 30 30 30 5c 6d 73 67 5c 31 5c 66 6f 76    \25000\msg\1\fov
+0060  5c 37 35 2e 30 22 0a                               \75.0".
+
+"connect" is the third step in establishing a connection to a server. I
+assume that the first number after "connect" is the protocol version and the
+third number is the "challenge" number. I'm not sure what the second number
+means. Client supplied information follows in quotes.
+
+
+2.1.9. client_connect
+---------------------
+(UDP, Server:28910 -> Client)
+
+0000  ff ff ff ff 63 6c 69 65 6e 74 5f 63 6f 6e 6e 65    ÿÿÿÿclient_conne
+0010  63 74                                              ct
+
+"client_connect" is the server's reply to a "connect" packet.
+TODO: find out if this an acknowledgement.
+
+
+2.1.10. ping
+------------
+(UDP, Server:28910 -> Master:28900)
+
+0000  ff ff ff ff 70 69 6e 67                            ÿÿÿÿping
+
+
+--
+
+3. Quake 2 protocol
+===================
+
+Quake 2 server port:		UDP 27910
+Quake 2 master server IP:	192.246.40.37
+Quake 2 master server port:	UDP 27900
+
+
+3.1. heartbeat
+--------------
+(UDP, Server:27910 -> Master:27900)
+
+0000  ff ff ff ff 68 65 61 72 74 62 65 61 74 0a 5c 6d   ÿÿÿÿheartbeat.\m
+0010  61 70 6e 61 6d 65 5c 71 32 64 6d 31 5c 6e 65 65   apname\q2dm1\nee
+0020  64 70 61 73 73 5c 30 5c 6d 61 78 73 70 65 63 74   dpass\0\maxspect
+0030  61 74 6f 72 73 5c 34 5c 67 61 6d 65 64 61 74 65   ators\4\gamedate
+0040  5c 44 65 63 20 31 36 20 32 30 30 32 5c 67 61 6d   \Dec 16 2002\gam
+0050  65 6e 61 6d 65 5c 62 61 73 65 71 32 5c 6d 61 78   ename\baseq2\max
+0060  63 6c 69 65 6e 74 73 5c 38 5c 70 72 6f 74 6f 63   clients\8\protoc
+0070  6f 6c 5c 33 34 5c 63 68 65 61 74 73 5c 30 5c 74   ol\34\cheats\0\t
+0080  69 6d 65 6c 69 6d 69 74 5c 30 5c 66 72 61 67 6c   imelimit\0\fragl
+0090  69 6d 69 74 5c 30 5c 64 6d 66 6c 61 67 73 5c 31   imit\0\dmflags\1
+00a0  36 5c 64 65 61 74 68 6d 61 74 63 68 5c 31 5c 76   6\deathmatch\1\v
+00b0  65 72 73 69 6f 6e 5c 33 2e 32 31 20 69 33 38 36   ersion\3.21 i386
+00c0  20 44 65 63 20 31 36 20 32 30 30 32 20 4c 69 6e    Dec 16 2002 Lin
+00d0  75 78 5c 68 6f 73 74 6e 61 6d 65 5c 6e 6f 6e 61   ux\hostname\nona
+00e0  6d 65 0a                                          me.   
+
+Heartbeats are sent every 300 seconds and after every map change to tell the
+master that the server is still alive. Some console variables are sent with
+the heartbeat.
+
+
+3.2. query
+----------
+(UDP, Client -> Master:27900)
+
+0000  71 75 65 72 79 0a 00                              query..
+
+Requests the server list from the master.
+
+
+3.3. shutdown
+-------------
+(UDP, Server:27910 -> Master:27900)
+
+0000  ff ff ff ff 73 68 75 74 64 6f 77 6e               ÿÿÿÿshutdown
+
+Tells the master that the server is shutting down.
+
+
+3.4. status
+-----------
+(UDP, Client -> Server:27910)
+
+0000  ff ff ff ff 73 74 61 74 75 73 0a                  ÿÿÿÿstatus.
+
+Request server information.
+
+
+3.5. print
+----------
+(UDP, Server:27910 -> Client)
+
+1 player, baseq2:
+
+0000  ff ff ff ff 70 72 69 6e 74 0a 5c 6d 61 70 6e 61   ÿÿÿÿprint.\mapna
+0010  6d 65 5c 71 32 64 6d 31 5c 6e 65 65 64 70 61 73   me\q2dm1\needpas
+0020  73 5c 30 5c 6d 61 78 73 70 65 63 74 61 74 6f 72   s\0\maxspectator
+0030  73 5c 34 5c 67 61 6d 65 64 61 74 65 5c 44 65 63   s\4\gamedate\Dec
+0040  20 31 36 20 32 30 30 32 5c 67 61 6d 65 6e 61 6d    16 2002\gamenam
+0050  65 5c 62 61 73 65 71 32 5c 6d 61 78 63 6c 69 65   e\baseq2\maxclie
+0060  6e 74 73 5c 38 5c 70 72 6f 74 6f 63 6f 6c 5c 33   nts\8\protocol\3
+0070  34 5c 63 68 65 61 74 73 5c 30 5c 74 69 6d 65 6c   4\cheats\0\timel
+0080  69 6d 69 74 5c 30 5c 66 72 61 67 6c 69 6d 69 74   imit\0\fraglimit
+0090  5c 30 5c 64 6d 66 6c 61 67 73 5c 31 36 5c 64 65   \0\dmflags\16\de
+00a0  61 74 68 6d 61 74 63 68 5c 31 5c 76 65 72 73 69   athmatch\1\versi
+00b0  6f 6e 5c 33 2e 32 31 20 69 33 38 36 20 44 65 63   on\3.21 i386 Dec
+00c0  20 31 36 20 32 30 30 32 20 4c 69 6e 75 78 5c 68    16 2002 Linux\h
+00d0  6f 73 74 6e 61 6d 65 5c 6e 6f 6e 61 6d 65 0a 30   ostname\noname.0
+00e0  20 36 20 22 43 68 69 63 6b 65 6e 4d 61 6e 22 0a    6 "ChickenMan".
+
+In comparison 13 players, gloom:
+
+0000  ff ff ff ff 70 72 69 6e 74 0a 5c 6d 61 70 6e 61   ÿÿÿÿprint.\mapna
+0010  6d 65 5c 68 61 67 67 65 73 5f 66 69 78 32 5c 67   me\hagges_fix2\g
+0020  61 6d 65 64 61 74 65 5c 4e 6f 76 20 31 33 20 32   amedate\Nov 13 2
+0030  30 30 32 5c 67 61 6d 65 6e 61 6d 65 5c 67 6c 6f   002\gamename\glo
+0040  6f 6d 5c 67 6c 6f 6f 6d 76 65 72 73 69 6f 6e 5c   om\gloomversion\
+0050  31 2e 78 5c 75 70 74 69 6d 65 5c 32 35 64 61 79   1.x\uptime\25day
+0060  73 2c 20 39 68 72 73 2c 20 34 39 6d 69 6e 73 5c   s, 9hrs, 49mins\
+0070  63 75 72 70 6c 61 79 65 72 73 5c 31 33 5c 6d 61   curplayers\13\ma
+0080  78 70 6c 61 79 65 72 73 5c 32 37 5c 6e 65 65 64   xplayers\27\need
+0090  70 61 73 73 5c 30 5c 76 6f 74 69 6e 67 5c 31 32   pass\0\voting\12
+00a0  37 5c 6d 61 78 5f 63 6c 5f 6d 61 78 66 70 73 5c   7\max_cl_maxfps\
+00b0  39 30 5c 6d 61 78 5f 72 61 74 65 5c 32 35 30 30   90\max_rate\2500
+00c0  30 5c 70 72 6f 74 6f 63 6f 6c 5c 33 35 5c 63 68   0\protocol\35\ch
+00d0  65 61 74 73 5c 30 5c 76 65 72 73 69 6f 6e 5c 72   eats\0\version\r
+00e0  31 2e 30 31 20 69 33 38 36 20 4e 6f 76 20 31 33   1.01 i386 Nov 13
+00f0  20 32 30 30 32 20 4c 69 6e 75 78 5c 64 6d 66 6c    2002 Linux\dmfl
+0100  61 67 73 5c 32 36 38 34 33 35 34 35 36 5c 78 6d   ags\268435456\xm
+0110  69 6e 73 5c 30 5c 74 69 6d 65 6c 69 6d 69 74 5c   ins\0\timelimit\
+0120  34 35 5c 66 72 61 67 6c 69 6d 69 74 5c 30 5c 6d   45\fraglimit\0\m
+0130  61 78 63 6c 69 65 6e 74 73 5c 32 37 5c 64 65 61   axclients\27\dea
+0140  74 68 6d 61 74 63 68 5c 31 5c 68 6f 73 74 6e 61   thmatch\1\hostna
+0150  6d 65 5c 65 44 6f 6d 65 20 51 75 61 6b 65 32 20   me\eDome Quake2 
+0160  47 6c 6f 6f 6d 5c 67 61 6d 65 64 69 72 5c 67 6c   Gloom\gamedir\gl
+0170  6f 6f 6d 5c 67 61 6d 65 5c 67 6c 6f 6f 6d 0a 32   oom\game\gloom.2
+0180  32 20 36 35 20 22 73 31 71 22 0a 36 36 20 35 30   2 65 "s1q".66 50
+0190  20 22 53 6c 65 64 67 65 22 0a 32 31 35 20 37 37    "Sledge".215 77
+01a0  20 22 53 77 61 74 22 0a 33 34 20 32 30 32 20 22    "Swat".34 202 "
+01b0  4d 61 74 74 68 69 65 77 22 0a 33 38 20 37 30 20   Matthiew".38 70 
+01c0  22 53 65 76 65 6e 22 0a 35 31 20 37 37 20 22 61   "Seven".51 77 "a
+01d0  64 64 69 63 74 65 64 32 77 61 72 22 0a 31 35 32   ddicted2war".152
+01e0  20 35 30 20 22 4e 6f 42 65 65 22 0a 31 32 20 33    50 "NoBee".12 3
+01f0  32 34 20 22 61 66 72 6f 6e 69 67 67 61 22 0a 37   24 "afronigga".7
+0200  33 20 39 31 20 22 43 69 6b 79 22 0a 36 31 20 34   3 91 "Ciky".61 4
+0210  31 20 22 53 79 43 6f 20 6f 66 20 6f 6f 6f 48 22   1 "SyCo of oooH"
+0220  0a 34 36 20 37 32 20 22 53 2d 49 2d 42 6c 6f 64   .46 72 "S-I-Blod
+0230  2d 43 2d 4b 22 0a 35 20 36 36 20 22 72 6f 61 64   -C-K".5 66 "road
+0240  6b 69 6c 6c 22 0a 31 36 20 33 33 20 22 5b 56 61   kill".16 33 "[Va
+0250  6d 70 69 72 65 5d 42 6c 6f 6f 64 22 0a            mpire]Blood".
+
+This is the reply to the "status" packet.
+TODO: more detailed description, see heretic2 section for now
+
+
+3.6. info
+---------
+(UDP, Server:27910 -> Client)
+
+0000  ff ff ff ff 69 6e 66 6f 0a 20 20 20 20 20 20 20   ÿÿÿÿinfo.       
+0010  20 20 20 6e 6f 6e 61 6d 65 20 20 20 20 71 32 64      noname    q2d
+0020  6d 31 20 20 30 2f 20 38 0a                        m1  0/8.
+
+Server information. Contains hostname, mapname, current and maximum number of
+players.
+
+
+3.7. info
+---------
+(UDP, Client -> Server:27910)
+
+0000  ff ff ff ff 69 6e 66 6f 20 33 34                  ÿÿÿÿinfo 34
+
+The q2 client broadcasts this packet on port UDP 27910 to find other servers
+in a LAN. "34" is the desired protocol version.
+TODO: more detailed description, see heretic2 section for now
+
+
+3.8. getchallenge
+-----------------
+(UDP, Client -> Server:27910)
+
+0000  ff ff ff ff 67 65 74 63 68 61 6c 6c 65 6e 67 65   ÿÿÿÿgetchallenge
+0010  0a                                                .     
+
+Request a "challenge" number from the server. First step in establishing a
+connection.
+
+
+3.9. challenge
+--------------
+(UDP, Server:27910 -> Client)
+
+0000  ff ff ff ff 63 68 61 6c 6c 65 6e 67 65 20 32 32   ÿÿÿÿchallenge 22
+0010  36 39 35                                          695
+
+Reply to a "getchallenge" packet. Contains a presumably signed 32bit integer.
+Second step in establishing a connection.
+
+
+3.10. connect
+-------------
+(UDP, Client -> Server:27910)
+
+0000  ff ff ff ff 63 6f 6e 6e 65 63 74 20 33 34 20 38   ÿÿÿÿconnect 34 8
+0010  31 30 20 32 32 36 39 35 20 22 5c 73 70 65 63 74   10 22695 "\spect
+0020  61 74 6f 72 5c 30 5c 72 61 74 65 5c 31 30 30 30   ator\0\rate\1000
+0030  30 5c 6d 73 67 5c 31 5c 66 6f 76 5c 39 30 5c 67   0\msg\1\fov\90\g
+0040  65 6e 64 65 72 5c 6d 61 6c 65 5c 73 6b 69 6e 5c   ender\male\skin\
+0050  6d 61 6c 65 2f 72 61 7a 6f 72 5c 6e 61 6d 65 5c   male/razor\name\
+0060  43 68 69 63 6b 65 6e 4d 61 6e 5c 68 61 6e 64 5c   ChickenMan\hand\
+0070  30 22 0a                                          0".   
+
+Third step in establishing a connection. Contains protocol version ("34"),
+an unknown number ("810"), the challenge number ("22695") and client supplied
+information.
+
+
+3.11. client_connect
+--------------------
+(UDP, Server:27910 -> Client)
+
+0000  ff ff ff ff 63 6c 69 65 6e 74 5f 63 6f 6e 6e 65   ÿÿÿÿclient_conne
+0010  63 74                                             ct    
+
+Fourth step in establishing a connection.
+TODO: is this an acknowledgement?
+
+
+3.12. servers
+-------------
+(UDP, Master:27900 -> Client)
+
+0000  ff ff ff ff 73 65 72 76 65 72 73 20 c2 fb f9 3a   ÿÿÿÿservers Âûù:
+0010  6d 06 42 a2 3a 29 6d 07 42 8b 49 11 6d 06 c3 94   m.B¢:)m.B.I.m.Ã.
+0020  30 cc 6d 06 88 a5 50 f3 6d 06 92 57 dc 05 6d 06   0Ìm..¥Póm..WÜ.m.
+0030  42 a2 3a 29 6d 06 88 a5 50 f3 6d 07               B¢:)m..¥Póm.
+
+
+Reply to a "query" packet. Contains the server list. After the "servers"
+string and a whitespace the IP and port address of the servers known to the
+master follow in blocks of 6 bytes:
+	4 bytes IP address
+	2 bytes port address
+
+Note:
+This packet originates from the Gloom master at master.planetgloom.com because
+ID Software doesn't have a Q2 master running anymore.
+
+
+3.13. ping
+----------
+(UDP, Server:27910 -> Master:27900)
+
+0000  ff ff ff ff 70 69 6e 67                           ÿÿÿÿping
+
+A "ping" packet is sent to the master on startup.
+
+
+3.14. ack
+---------
+(UDP, Master:27900 -> Server:27910)
+
+0000  ff ff ff ff 70 61 63 6b                           ÿÿÿÿack
+
+Reply to a "ping" packet.
+
+--
+
+4. STV: Elite Force protocol
+============================
+
+EF server port: UDP 27960
+
+EF Master server DNS name:	master.stef1.ravensoft.com
+EF Master server port:		UDP 27953
+EF MOTD server DNS name:	motd.stef1.ravensoft.com
+EF MOTD server port:		UDP 27951
+EF auth server DNS name:	authenticate.stef1.ravensoft.com
+EF auth server port:		UDP 27952
+
+
+4.1. Master Server
+------------------
+
+4.1.1. getservers
+-----------------
+(UDP, Client -> Master:27953)
+
+0000  ff ff ff ff 67 65 74 73 65 72 76 65 72 73 20 32   ÿÿÿÿgetservers 2
+0010  34 20 65 6d 70 74 79 20 66 75 6c 6c               4 empty full
+
+Following the "getservers" string are a number of options:
+24			- protocol version
+				STEF1 1.20 == 24
+empty full	- Specifies which servers to show. This is optional.
+
+I've seen the following keywords being used:
+	empty - empty servers
+	full - full servers (all player slots used except for the private slots)
+	ffa - Free For All server
+	team - Team Deathmatch server
+	tourney - Tournament (1v1) server
+	ctf - Capture the Flag server
+
+"getservers" packets are sent to master.stef1.ravensoft.com:27953.
+
+
+4.1.2. getserversResponse
+-------------------------
+(UDP, Master:27953 -> Client)
+
+0000  ff ff ff ff 67 65 74 73 65 72 76 65 72 73 52 65   ÿÿÿÿgetserversRe
+0010  73 70 6f 6e 73 65 20 5c 38 32 64 37 38 32 31 63   sponse \82d7821c
+0020  36 64 33 62 5c 34 35 33 38 61 32 38 36 36 64 33   6d3b\4538a2866d3
+0030  61 5c 64 34 30 36 36 63 66 39 36 64 33 39 5c 34   a\d4066cf96d39\4
+0040  35 31 63 66 30 64 65 36 64 33 38 5c 34 35 33 39   51cf0de6d38\4539
+0050  38 34 34 64 36 64 5c 45 4f 54                     844d6d\EOT
+
+Looks almost like the "getserversResponse" from the Q3 master. The only
+difference is that the STEF1 master encodes IP and port addresses in
+hex. The hex values are written in network byte order.
+I'd really like to know why Raven did this ...
+The Master sends a maximum of 97 servers per packet. So we get the following
+maximum packet size:
+97*(12+1)	= 1261 bytes (97 servers + delimiter)
++ 4			= 1265 bytes (protocol marker)
++ 19		= 1284 bytes (length of command)
++ 4			= 1288 bytes (footer "\EOT")
+
+
+4.1.3. getKeyAuthorize
+----------------------
+(UDP, Client -> Master:27952)
+
+0000  ff ff ff ff 67 65 74 4b 65 79 41 75 74 68 6f 72   ÿÿÿÿgetKeyAuthor
+0010  69 7a 65 20 39 39 39 20 78 78 78 78 78 78 78 78   ize 999 xxxxxxxx
+0020  78 78 78 78 78 78 78 78 78 78                     xxxxxxxxxx
+
+"getKeyAuthorize" packets are usually sent to
+authenticate.stef1.ravensoft.com:27952 UDP. I don't know what the "999" number
+means. The "x" characters are a placeholder for the CD key.
+
+
+4.1.4. getmotd
+--------------
+(UDP, Client -> Master:27951)
+
+0000  ff ff ff ff 67 65 74 6d 6f 74 64 20 22 5c 63 68   ÿÿÿÿgetmotd "\ch
+0010  61 6c 6c 65 6e 67 65 5c 31 34 39 38 35 5c 76 65   allenge\14985\ve
+0020  72 73 69 6f 6e 5c 53 54 3a 56 20 48 4d 20 76 31   rsion\ST:V HM v1
+0030  2e 32 30 20 77 69 6e 2d 78 38 36 20 41 70 72 20   .20 win-x86 Apr 
+0040  31 37 20 32 30 30 31 5c 72 65 6e 64 65 72 65 72   17 2001\renderer
+0050  5c 47 65 46 6f 72 63 65 33 2f 41 47 50 2f 53 53   \GeForce3/AGP/SS
+0060  45 5c 63 70 75 74 79 70 65 5c 49 6e 74 65 6c 20   E\cputype\Intel 
+0070  50 65 6e 74 69 75 6d 20 49 49 49 5c 6d 68 7a 5c   Pentium III\mhz\
+0080  37 35 30 5c 6d 65 6d 6f 72 79 5c 35 31 31 5c 6a   750\memory\511\j
+0090  6f 79 73 74 69 63 6b 5c 30 5c 63 6f 6c 6f 72 62   oystick\0\colorb
+00a0  69 74 73 5c 33 32 22                              its\32"
+
+The STEF1 client sends the following variables to the motd server:
+challenge
+version
+renderer
+cputype
+memory
+joystick
+colorbits
+
+"getmotd" packets are usually sent to motd.stef1.ravensoft.com:27951 UDP.
+
+
+4.1.5. motd
+-----------
+(UDP, Master:27951 -> Client)
+
+0000  ff ff ff ff 6d 6f 74 64 20 22 5c 63 68 61 6c 6c   ÿÿÿÿmotd "\chall
+0010  65 6e 67 65 5c 31 34 39 38 35 5c 6d 6f 74 64 5c   enge\14985\motd\
+0020  48 6f 6c 6f 6d 61 74 63 68 65 72 20 23 33 34 30   Holomatcher #340
+0030  36 37 20 6f 6e 6c 69 6e 65 2e 22                  67 online."
+
+The "motd" packet is the reply to a "getmotd" packet.
+The challenge value is parsed from the "getmotd" packet.
+The value of the "motd" variable is shown on the bottom of the loading screen.
+
+
+4.2. Dedicated/Listen Server
+----------------------------
+
+4.2.1. getinfo
+--------------
+(UDP, Client -> Server:27960)
+
+0000  ff ff ff ff 67 65 74 69 6e 66 6f 0a               ÿÿÿÿgetinfo.
+
+No parameters known.
+
+
+4.2.2. infoResponse
+-------------------
+(UDP, Server:27960 -> Client)
+
+0000  ff ff ff ff 69 6e 66 6f 52 65 73 70 6f 6e 73 65   ÿÿÿÿinfoResponse
+0010  20 22 5c 63 68 61 6c 6c 65 6e 67 65 5c 78 78 78    "\challenge\xxx
+0020  5c 70 72 6f 74 6f 63 6f 6c 5c 32 34 5c 68 6f 73   \protocol\24\hos
+0030  74 6e 61 6d 65 5c 47 41 4d 45 2e 4e 45 54 20 42   tname\GAME.NET B
+0040  6f 6f 6b 61 62 6c 65 20 2d 20 53 54 56 20 45 6c   ookable - STV El
+0050  69 74 65 20 46 6f 72 63 65 20 28 33 29 20 2d 20   ite Force (3) - 
+0060  41 76 61 69 6c 61 62 6c 65 5c 6d 61 70 6e 61 6d   Available\mapnam
+0070  65 5c 68 6d 5f 62 6f 72 67 31 5c 63 6c 69 65 6e   e\hm_borg1\clien
+0080  74 73 5c 30 5c 73 76 5f 6d 61 78 63 6c 69 65 6e   ts\0\sv_maxclien
+0090  74 73 5c 31 36 5c 67 61 6d 65 74 79 70 65 5c 30   ts\16\gametype\0
+00a0  5c 70 75 72 65 5c 31 5c 67 61 6d 65 5c 42 61 73   \pure\1\game\Bas
+00b0  65 45 46 22                                       eEF"  
+
+TODO: description
+
+
+4.2.3. getstatus
+--------------
+(UDP, Client -> Server:27960)
+
+0000  ff ff ff ff 67 65 74 73 74 61 74 75 73 0a         ÿÿÿÿgetstatus.
+
+0000  ff ff ff ff 67 65	74 73 74 61 74 75 73 20 30 31   ÿÿÿÿgetstatus 10
+0010  36 31 38 32 39 35 34 37 37 32 33                  61829547723
+
+"getstatus" is a verbose version of "getinfo" and is sent by the master
+on every heartbeat.
+Optionally, a challenge value can be sent with the request. AFAIK the
+"challenge" value is only set in packets originating from the master.
+
+
+4.2.4. statusResponse
+---------------------
+(UDP, Server:27960 -> Client)
+
+statusReponse from an empty EF server:
+--------------------------------------
+0000  ff ff ff ff 73 74 61 74 75 73 52 65 73 70 6f 6e   ÿÿÿÿstatusRespon
+0010  73 65 0a 5c 63 68 61 6c 6c 65 6e 67 65 5c 31 30   se.\challenge\10
+0020  36 39 30 31 31 31 34 39 39 32 31 5c 67 5f 6e 65   69011149921\g_ne
+0030  65 64 70 61 73 73 5c 30 5c 63 61 70 74 75 72 65   edpass\0\capture
+0040  6c 69 6d 69 74 5c 38 5c 67 5f 6d 61 78 47 61 6d   limit\8\g_maxGam
+0050  65 43 6c 69 65 6e 74 73 5c 30 5c 67 61 6d 65 6e   eClients\0\gamen
+0060  61 6d 65 5c 62 61 73 65 45 46 5c 62 6f 74 5f 6d   ame\baseEF\bot_m
+0070  69 6e 70 6c 61 79 65 72 73 5c 30 5c 73 76 5f 61   inplayers\0\sv_a
+0080  6c 6c 6f 77 44 6f 77 6e 6c 6f 61 64 5c 31 5c 73   llowDownload\1\s
+0090  76 5f 70 75 72 65 5c 31 5c 73 76 5f 66 6c 6f 6f   v_pure\1\sv_floo
+00a0  64 50 72 6f 74 65 63 74 5c 31 5c 73 76 5f 6d 61   dProtect\1\sv_ma
+00b0  78 50 69 6e 67 5c 30 5c 73 76 5f 6d 69 6e 50 69   xPing\0\sv_minPi
+00c0  6e 67 5c 30 5c 73 76 5f 6d 61 78 52 61 74 65 5c   ng\0\sv_maxRate\
+00d0  30 5c 73 76 5f 6d 61 78 63 6c 69 65 6e 74 73 5c   0\sv_maxclients\
+00e0  38 5c 73 76 5f 68 6f 73 74 6e 61 6d 65 5c 6e 6f   8\sv_hostname\no
+00f0  6e 61 6d 65 5c 73 76 5f 70 72 69 76 61 74 65 43   name\sv_privateC
+0100  6c 69 65 6e 74 73 5c 30 5c 6d 61 70 6e 61 6d 65   lients\0\mapname
+0110  5c 68 6d 5f 62 6f 72 67 31 5c 70 72 6f 74 6f 63   \hm_borg1\protoc
+0120  6f 6c 5c 32 34 5c 67 5f 70 4d 6f 64 45 6c 69 6d   ol\24\g_pModElim
+0130  69 6e 61 74 69 6f 6e 5c 30 5c 67 5f 70 4d 6f 64   ination\0\g_pMod
+0140  41 63 74 69 6f 6e 48 65 72 6f 5c 30 5c 67 5f 70   ActionHero\0\g_p
+0150  4d 6f 64 44 69 73 69 6e 74 65 67 72 61 74 69 6f   ModDisintegratio
+0160  6e 5c 30 5c 67 5f 70 4d 6f 64 41 73 73 69 6d 69   n\0\g_pModAssimi
+0170  6c 61 74 69 6f 6e 5c 30 5c 67 5f 70 4d 6f 64 53   lation\0\g_pModS
+0180  70 65 63 69 61 6c 74 69 65 73 5c 30 5c 67 5f 67   pecialties\0\g_g
+0190  61 6d 65 74 79 70 65 5c 30 5c 74 69 6d 65 6c 69   ametype\0\timeli
+01a0  6d 69 74 5c 30 5c 66 72 61 67 6c 69 6d 69 74 5c   mit\0\fraglimit\
+01b0  32 30 5c 64 6d 66 6c 61 67 73 5c 30 5c 76 65 72   20\dmflags\0\ver
+01c0  73 69 6f 6e 5c 53 54 3a 56 20 48 4d 20 76 31 2e   sion\ST:V HM v1.
+01d0  32 30 20 6c 69 6e 75 78 2d 69 33 38 36 20 41 70   20 linux-i386 Ap
+01e0  72 20 31 37 20 32 30 30 31 0a                     r 17 2001.
+
+statusReponse from a populated EF server:
+-----------------------------------------
+0000  ff ff ff ff 73 74 61 74 75 73 52 65 73 70 6f 6e   ....statusRespon
+0010  73 65 0a 5c 67 5f 6e 65 65 64 70 61 73 73 5c 30   se.\g_needpass\0
+0020  5c 63 61 70 74 75 72 65 6c 69 6d 69 74 5c 38 5c   \capturelimit\8\
+0030  67 5f 6d 61 78 47 61 6d 65 43 6c 69 65 6e 74 73   g_maxGameClients
+0040  5c 30 5c 67 61 6d 65 6e 61 6d 65 5c 62 61 73 65   \0\gamename\base
+0050  45 46 5c 67 5f 61 6c 6c 6f 77 76 6f 74 65 5c 30   EF\g_allowvote\0
+0060  5c 67 5f 73 70 65 65 64 5c 32 35 30 5c 67 5f 67   \g_speed\250\g_g
+0070  72 61 76 69 74 79 5c 38 30 30 5c 67 5f 66 72 69   ravity\800\g_fri
+0080  65 6e 64 6c 79 66 69 72 65 5c 30 5c 62 6f 74 5f   endlyfire\0\bot_
+0090  6d 69 6e 70 6c 61 79 65 72 73 5c 32 5c 73 76 5f   minplayers\2\sv_
+00a0  61 6c 6c 6f 77 44 6f 77 6e 6c 6f 61 64 5c 31 5c   allowDownload\1\
+00b0  73 76 5f 70 75 72 65 5c 31 5c 73 76 5f 66 6c 6f   sv_pure\1\sv_flo
+00c0  6f 64 50 72 6f 74 65 63 74 5c 35 5c 73 76 5f 6d   odProtect\5\sv_m
+00d0  61 78 50 69 6e 67 5c 35 30 30 5c 73 76 5f 6d 69   axPing\500\sv_mi
+00e0  6e 50 69 6e 67 5c 30 5c 73 76 5f 6d 61 78 52 61   nPing\0\sv_maxRa
+00f0  74 65 5c 30 5c 73 76 5f 6d 61 78 63 6c 69 65 6e   te\0\sv_maxclien
+0100  74 73 5c 31 32 5c 73 76 5f 68 6f 73 74 6e 61 6d   ts\12\sv_hostnam
+0110  65 5c 43 4c 41 53 53 49 43 5f 43 54 46 5f 32 5c   e\CLASSIC_CTF_2\
+0120  73 76 5f 70 72 69 76 61 74 65 43 6c 69 65 6e 74   sv_privateClient
+0130  73 5c 30 5c 6d 61 70 6e 61 6d 65 5c 63 74 66 5f   s\0\mapname\ctf_
+0140  73 70 79 67 6c 61 73 73 5c 70 72 6f 74 6f 63 6f   spyglass\protoco
+0150  6c 5c 32 34 5c 67 5f 70 4d 6f 64 45 6c 69 6d 69   l\24\g_pModElimi
+0160  6e 61 74 69 6f 6e 5c 30 5c 67 5f 70 4d 6f 64 41   nation\0\g_pModA
+0170  63 74 69 6f 6e 48 65 72 6f 5c 30 5c 67 5f 70 4d   ctionHero\0\g_pM
+0180  6f 64 44 69 73 69 6e 74 65 67 72 61 74 69 6f 6e   odDisintegration
+0190  5c 30 5c 67 5f 70 4d 6f 64 41 73 73 69 6d 69 6c   \0\g_pModAssimil
+01a0  61 74 69 6f 6e 5c 30 5c 67 5f 70 4d 6f 64 53 70   ation\0\g_pModSp
+01b0  65 63 69 61 6c 74 69 65 73 5c 30 5c 67 5f 67 61   ecialties\0\g_ga
+01c0  6d 65 74 79 70 65 5c 34 5c 74 69 6d 65 6c 69 6d   metype\4\timelim
+01d0  69 74 5c 32 30 5c 66 72 61 67 6c 69 6d 69 74 5c   it\20\fraglimit\
+01e0  32 30 5c 64 6d 66 6c 61 67 73 5c 30 5c 76 65 72   20\dmflags\0\ver
+01f0  73 69 6f 6e 5c 53 54 3a 56 20 48 4d 20 76 31 2e   sion\ST:V HM v1.
+0200  32 30 20 6c 69 6e 75 78 2d 69 33 38 36 20 41 70   20 linux-i386 Ap
+0210  72 20 31 37 20 32 30 30 31 0a 31 31 20 30 20 22   r 17 2001.11 0 "
+0220  50 65 6c 6c 65 74 69 65 72 22 0a 31 37 20 30 20   Pelletier".17 0 
+0230  22 43 68 61 6f 74 69 63 61 22 0a 30 20 30 20 22   "Chaotica".0 0 "
+0240  4b 65 6e 6e 22 0a 33 37 20 30 20 22 42 75 73 74   Kenn".37 0 "Bust
+0250  65 72 20 4b 69 6e 63 61 69 64 22 0a 31 34 20 30   er Kincaid".14 0
+0260  20 22 54 27 4c 61 72 22 0a                         "T'Lar".
+
+TODO: description
+
+
+4.2.5. getchallenge
+-------------------
+(UDP, Client -> Server:27960)
+
+0000  ff ff ff ff 67 65 74 63 68 61 6c 6c 65 6e 67 65   ÿÿÿÿgetchallenge
+
+No paramters known.
+
+
+4.2.6. challengeResponse
+------------------------
+(UDP, Server:27960 -> Client)
+
+0000  ff ff ff ff 63 68 61 6c 6c 65 6e 67 65 52 65 73   ÿÿÿÿchallengeRes
+0010  70 6f 6e 73 65 20 39 36 31 31 36 36 35 31         ponse 96116651
+
+The number following "challengeResponse" is presumably a signed 32bit integer.
+
+
+4.2.7. connect
+--------------
+(UDP, Client -> Server:27960)
+
+0000  ff ff ff ff 63 6f 6e 6e 65 63 74 20 01 53 44 74   ÿÿÿÿconnect .SDt
+0010  30 8e 05 0c c7 26 c3 14 ec 8e f9 67 70 1a 36 c1   0...Ç&Ã.ì.ùgp.6Á
+0020  48 f9 09 2b 72 38 3a 0e 83 bd e2 54 b8 44 5b 61   Hù.+r8:..½âT¸D[a
+0030  49 d7 95 83 6e e8 32 1c 7d 93 d1 67 09 d7 82 a5   I×..nè2.}.Ñg.×.¥
+0040  c5 24 ad 43 d0 c0 20 d5 88 70 44 05 cc 14 61 5e   Å$­CÐÀ Õ.pD.Ì.a^
+0050  e1 cf 92 de 0c 31 e0 d4 00 a6 39 61 7f e4 1e 84   áÏ.Þ.1àÔ.¦9a.ä..
+0060  ce 69 74 af e7 19 30 9f ac 58 f3 18 cd 03 ce 02   Îit¯ç.0.¬Xó.Í.Î.
+0070  cf 44 6e 59 d6 d3 41 3d 9e 77 6c 5f aa 43 f1 08   ÏDnYÖÓA=.wl_ªCñ.
+0080  1c d4 35 e9 a5 6c bc fe fd fd 9e e5 9f e2 87 8f   .Ô5é¥l¼þýý.å.â..
+0090  9e 6d d7 34 e2 e8 39 38 e8 97 87 61 66 4d 08 6b   .m×4âè98è..afM.k
+00a0  68 2b 5b 0b 71 e8 6e df f3 ef 36 3e f7 31 cb fa   h+[.qènßóï6>÷1Ëú
+00b0  05 78 1c b5 8d 20 e6 6a 9c 9d d5 8b 8b c1 22 6a   .x.µ. æj..Õ..Á"j
+00c0  d9 47 4f 74 e0 f9 be bb d1 40 22 d3 f2 0d a8 fe   ÙGOtàù¾»Ñ@"Óò.¨þ
+00d0  6f 3f d4 60 1b 76 14 91 95 59 d6 57 f8 0e 82 a7   o?Ô`.v...YÖWø..§
+00e0  75 31 9c 6f 8b be 15 70 9d f5 4b 83 6d 58 2f ce   u1.o.¾.p.õK.mX/Î
+00f0  3a bf de a7 9e bf 34 86 75 56 30 96 c3 7e 2f e9   :¿Þ§.¿4.uV0.Ã~/é
+0100  61 e1 61 52 34 8e 6b 9e c2 cc 41 8c e1 48 96 d5   aáaR4.k.ÂÌA.áH.Õ
+0110  91 8b db 66 18 19 13 fc e8 00                     ..Ûf...üè.
+
+The "connect" packet is sent after a "challengeResponse".
+Note: This packet is encoded with the Huffman algorithm. For further
+	  information take a look at Luigi Auriemma's site at
+	  http://aluigi.altervista.org/ .
+
+
+4.2.8. connectResponse
+----------------------
+(UDP, Server:27960 -> Client)
+
+0000  ff ff ff ff 63 6f 6e 6e 65 63 74 52 65 73 70 6f   ÿÿÿÿconnectRespo
+0010  6e 73 65                                          nse
+
+No parameters known.
+
+
+4.2.9. heartbeat
+----------------
+(UDP, Server:27960 -> Master:27953)
+
+0000  ff ff ff ff 5c 68 65 61 72 74 62 65 61 74 5c 32   ÿÿÿÿ\heartbeat\2
+0010  37 39 36 30 5c 67 61 6d 65 6e 61 6d 65 5c 53 54   7960\gamename\ST
+0020  45 46 31 5c                                       EF1\
+
+Looks almost like a Q3 heartbeat packet except for the separators and the
+"gamename" variable. The "heartbeat" value is the port on which the server
+is running. The STEF1 master sends a "getchallenge" and a "getstatus" packet
+on every heartbeat. I assume this is to gather information of the server
+to be able to reply to the "getservers" packets.
+
+
+4.2.10 print
+------------
+(UDP, Server:27960 -> Client)
+
+0000  ff ff ff ff 70 72 69 6e 74 0a 53 65 72 76 65 72   ÿÿÿÿprint.Server
+0010  20 75 73 65 73 20 70 72 6f 74 6f 63 6f 6c 20 76    uses protocol v
+0020  65 72 73 69 6f 6e 20 36 36 2e 0a                  ersion 66..
+
+The "print" packet displays a message on the client's screen.
+This is used for messages like "Invalid Password", wrong protocol version, ...
+
+
+4.2.11 heartstop
+----------------
+(UDP, Server:27960 -> Master:27953)
+
+0000  ff ff ff ff 5c 68 65 61 72 74 73 74 6f 70 5c 32   ÿÿÿÿ\heartstop\2
+0010  37 39 36 30 5c 67 61 6d 65 6e 61 6d 65 5c 53 54   7960\gamename\ST
+0020  45 46 31 5c                                       EF1\
+
+A "heartstop" packet is sent on server shutdown.
+
+
+--
+
+
+5. Unreal Tournament protocol
+=============================
+
+UT server ports:			UDP 7777, UDP 7778, UDP 7779, UDP 7780, UDP 8777
+UT master ports:			TCP 80, UDP 27900, TCP 28900
+UT Gamespy master DNS name:	master0.gamespy.com
+UT Epic master DNS name:	unreal.epicgames.com
+UT Mplayer master DNS name:	master.mplayer.com
+
+UT uses the Gamespy protocol.
+TODO: more info on gspy protocol
+
+
+5.1. heartbeat
+--------------
+(UDP, Server:7779 -> Master:27900)
+
+There are 2 different heartbeat packets:
+1. Epic master
+2. Gamespy master
+
+Packets for the Epic master look like the following:
+
+0000  5c 68 65 61 72 74 62 65 61 74 5c 37 37 37 38 5c   \heartbeat\7778\
+0010  67 61 6d 65 6e 61 6d 65 5c 75 74 5c 67 61 6d 65   gamename\ut\game
+0020  76 65 72 5c 34 35 31                              ver\451
+
+The "heartbeat" value is the port on which the server runs.
+"gamename" is a string to identify the game.
+"gamever" is the game version. (duh!)
+
+Packets for the Epic master are sent to unreal.epicgames.com. The time between
+2 heartbeats is 60 seconds.
+
+
+Packets for the Gamespy master look like this:
+
+0000  5c 68 65 61 72 74 62 65 61 74 5c 37 37 37 38 5c   \heartbeat\7778\
+0010  67 61 6d 65 6e 61 6d 65 5c 75 74                  gamename\ut
+
+Variables should have the same meaning as in the Epic master heartbeat. Packets
+for the Gamespy master are sent to master0.gamespy.com. The time between 2
+heartbeats is 60 seconds.
+
+
+5.2. info
+---------
+(UDP, Client -> Server:7778)
+
+0000  5c 69 6e 66 6f 5c                                 \info\
+
+TODO: description
+
+Reply looks like the following:
+
+0000  5c 68 6f 73 74 6e 61 6d 65 5c 20 6b 2d 70 6c 61   \hostname\ k-pla
+0010  79 2e 64 65 20 2f 2f 20 55 54 20 23 30 31 20 5b   y.de // UT #01 [
+0020  50 55 52 45 5d 5c 68 6f 73 74 70 6f 72 74 5c 38   PURE]\hostport\8
+0030  33 30 30 5c 6d 61 70 74 69 74 6c 65 5c 4d 61 6c   300\maptitle\Mal
+0040  65 76 6f 6c 65 6e 63 65 5c 6d 61 70 6e 61 6d 65   evolence\mapname
+0050  5c 44 4d 2d 4d 61 6c 65 76 6f 6c 65 6e 63 65 5c   \DM-Malevolence\
+0060  67 61 6d 65 74 79 70 65 5c 44 65 61 74 68 4d 61   gametype\DeathMa
+0060  74 63 68 50 6c 75 73 5c 6e 75 6d 70 6c 61 79 65   tchPlus\numplaye
+0070  72 73 5c 39 5c 6d 61 78 70 6c 61 79 65 72 73 5c   rs\9\maxplayers\
+0080  31 30 5c 67 61 6d 65 6d 6f 64 65 5c 6f 70 65 6e   10\gamemode\open
+0090  70 6c 61 79 69 6e 67 5c 67 61 6d 65 76 65 72 5c   playing\gamever\
+00a0  34 35 31 5c 6d 69 6e 6e 65 74 76 65 72 5c 34 33   451\minnetver\43
+00b0  32 5c 77 6f 72 6c 64 6c 6f 67 5c 74 72 75 65 5c   2\worldlog\true\
+00c0  77 61 6e 74 77 6f 72 6c 64 6c 6f 67 5c 74 72 75   wantworldlog\tru
+00d0  65 5c 71 75 65 72 79 69 64 5c 34 34 2e 31 5c 66   e\queryid\44.1\f
+00e0  69 6e 61 6c 5c                                    inal\ 
+
+
+5.3. status
+-----------
+(UDP, Client -> Server)
+
+0000  5c 73 74 61 74 75 73 5c                           \status\
+
+Reply looks like the following:
+
+0000  00 01 20 6d 07 d0 03 89 e4 3c 5c 67 61 6d 65 6e   .. m.Ð..ä<\gamen
+0010  61 6d 65 5c 75 74 5c 67 61 6d 65 76 65 72 5c 34   ame\ut\gamever\4
+0020  35 31 5c 6d 69 6e 6e 65 74 76 65 72 5c 34 33 32   51\minnetver\432
+0030  5c 6c 6f 63 61 74 69 6f 6e 5c 30 5c 68 6f 73 74   \location\0\host
+0040  6e 61 6d 65 5c 20 6b 2d 70 6c 61 79 2e 64 65 20   name\ k-play.de 
+0050  2f 2f 20 55 54 20 23 30 31 20 5b 50 55 52 45 5d   // UT #01 [PURE]
+0060  5c 68 6f 73 74 70 6f 72 74 5c 38 33 30 30 5c 6d   \hostport\8300\m
+0070  61 70 74 69 74 6c 65 5c 4d 61 6c 65 76 6f 6c 65   aptitle\Malevole
+0080  6e 63 65 5c 6d 61 70 6e 61 6d 65 5c 44 4d 2d 4d   nce\mapname\DM-M
+0090  61 6c 65 76 6f 6c 65 6e 63 65 5c 67 61 6d 65 74   alevolence\gamet
+00a0  79 70 65 5c 44 65 61 74 68 4d 61 74 63 68 50 6c   ype\DeathMatchPl
+00b0  75 73 5c 6e 75 6d 70 6c 61 79 65 72 73 5c 38 5c   us\numplayers\8\
+00c0  6d 61 78 70 6c 61 79 65 72 73 5c 31 30 5c 67 61   maxplayers\10\ga
+00d0  6d 65 6d 6f 64 65 5c 6f 70 65 6e 70 6c 61 79 69   memode\openplayi
+00e0  6e 67 5c 67 61 6d 65 76 65 72 5c 34 35 31 5c 6d   ng\gamever\451\m
+00f0  69 6e 6e 65 74 76 65 72 5c 34 33 32 5c 77 6f 72   innetver\432\wor
+0100  6c 64 6c 6f 67 5c 74 72 75 65 5c 77 61 6e 74 77   ldlog\true\wantw
+0110  6f 72 6c 64 6c 6f 67 5c 74 72 75 65 5c 6d 75 74   orldlog\true\mut
+0120  61 74 6f 72 73 5c 61 6e 74 69 63 76 5c 6c 69 73   ators\anticv\lis
+0130  74 65 6e 73 65 72 76 65 72 5c 46 61 6c 73 65 5c   tenserver\False\
+0140  70 61 73 73 77 6f 72 64 5c 46 61 6c 73 65 5c 74   password\False\t
+0150  69 6d 65 6c 69 6d 69 74 5c 32 30 5c 66 72 61 67   imelimit\20\frag
+0160  6c 69 6d 69 74 5c 30 5c 6d 69 6e 70 6c 61 79 65   limit\0\minplaye
+0170  72 73 5c 30 5c 63 68 61 6e 67 65 6c 65 76 65 6c   rs\0\changelevel
+0180  73 5c 54 72 75 65 5c 74 6f 75 72 6e 61 6d 65 6e   s\True\tournamen
+0190  74 5c 46 61 6c 73 65 5c 67 61 6d 65 73 74 79 6c   t\False\gamestyl
+01a0  65 5c 48 61 72 64 63 6f 72 65 5c 41 64 6d 69 6e   e\Hardcore\Admin
+01b0  4e 61 6d 65 5c 77 77 77 2e 6b 2d 70 6c 61 79 2e   Name\www.k-play.
+01c0  64 65 5c 41 64 6d 69 6e 45 4d 61 69 6c 5c 73 75   de\AdminEMail\su
+01d0  70 70 6f 72 74 40 6b 2d 70 6c 61 79 2e 64 65 5c   pport@k-play.de\
+01e0  70 6c 61 79 65 72 5f 30 5c 68 61 6c 6f 5b 4e 69   player_0\halo[Ni
+01f0  6c 5d 5c 66 72 61 67 73 5f 30 5c 37 5c 70 69 6e   l]\frags_0\7\pin
+0200  67 5f 30 5c 20 36 33 5c 74 65 61 6d 5f 30 5c 30   g_0\ 63\team_0\0
+0210  5c 6d 65 73 68 5f 30 5c 4d 61 6c 65 20 53 6f 6c   \mesh_0\Male Sol
+0220  64 69 65 72 5c 73 6b 69 6e 5f 30 5c 53 6f 6c 64   dier\skin_0\Sold
+0230  69 65 72 53 6b 69 6e 73 2e 73 6c 64 72 5c 66 61   ierSkins.sldr\fa
+0240  63 65 5f 30 5c 53 6f 6c 64 69 65 72 53 6b 69 6e   ce_0\SoldierSkin
+0250  73 2e 42 72 6f 63 6b 5c 6e 67 73 65 63 72 65 74   s.Brock\ngsecret
+0260  5f 30 5c 66 61 6c 73 65 5c 70 6c 61 79 65 72 5f   _0\false\player_
+0270  31 5c 61 63 74 5f 73 75 70 65 72 6e 6f 75 6d 5c   1\act_supernoum\
+0280  66 72 61 67 73 5f 31 5c 32 30 5c 70 69 6e 67 5f   frags_1\20\ping_
+0290  31 5c 20 39 31 5c 74 65 61 6d 5f 31 5c 31 5c 6d   1\ 91\team_1\1\m
+02a0  65 73 68 5f 31 5c 4d 61 6c 65 20 53 6f 6c 64 69   esh_1\Male Soldi
+02b0  65 72 5c 73 6b 69 6e 5f 31 5c 53 6f 6c 64 69 65   er\skin_1\Soldie
+02c0  72 53 6b 69 6e 73 2e 52 61 77 53 5c 66 61 63 65   rSkins.RawS\face
+02d0  5f 31 5c 53 6f 6c 64 69 65 72 53 6b 69 6e 73 2e   _1\SoldierSkins.
+02e0  41 72 6b 6f 6e 5c 6e 67 73 65 63 72 65 74 5f 31   Arkon\ngsecret_1
+02f0  5c 66 61 6c 73 65 5c 70 6c 61 79 65 72 5f 32 5c   \false\player_2\
+0300  55 72 75 6b 2d 48 61 69 5c 66 72 61 67 73 5f 32   Uruk-Hai\frags_2
+0310  5c 35 33 5c 70 69 6e 67 5f 32 5c 20 36 36 5c 74   \53\ping_2\ 66\t
+0320  65 61 6d 5f 32 5c 30 5c 6d 65 73 68 5f 32 5c 46   eam_2\0\mesh_2\F
+0330  65 6d 61 6c 65 20 53 6f 6c 64 69 65 72 5c 73 6b   emale Soldier\sk
+0340  69 6e 5f 32 5c 53 47 69 72 6c 53 6b 69 6e 73 2e   in_2\SGirlSkins.
+0350  56 65 6e 6d 5c 66 61 63 65 5f 32 5c 53 47 69 72   Venm\face_2\SGir
+0360  6c 53 6b 69 6e 73 2e 41 74 68 65 6e 61 5c 6e 67   lSkins.Athena\ng
+0370  73 65 63 72 65 74 5f 32 5c 66 61 6c 73 65 5c 71   secret_2\false\q
+0380  75 65 72 79 69 64 5c 36 32 2e 31                  ueryid\62.1     
+
+2nd packet, observe the queryid variable:
+
+0000  00 01 20 6d 07 d0 02 ca  45 61 5c 70 6c 61 79 65   .. m.Ð.Ê Ea\playe
+0010  72 5f 33 5c 43 72 61 69  2d 5a 5c 66 72 61 67 73   r_3\Crai -Z\frags
+0020  5f 33 5c 37 35 5c 70 69  6e 67 5f 33 5c 20 34 39   _3\75\pi ng_3\ 49
+0030  5c 74 65 61 6d 5f 33 5c  31 5c 6d 65 73 68 5f 33   \team_3\ 1\mesh_3
+0040  5c 46 65 6d 61 6c 65 20  53 6f 6c 64 69 65 72 5c   \Female  Soldier\
+0050  73 6b 69 6e 5f 33 5c 53  47 69 72 6c 53 6b 69 6e   skin_3\S GirlSkin
+0060  73 2e 66 62 74 68 5c 66  61 63 65 5f 33 5c 53 47   s.fbth\f ace_3\SG
+0070  69 72 6c 53 6b 69 6e 73  2e 41 6e 6e 61 6b 61 5c   irlSkins .Annaka\
+0080  6e 67 73 65 63 72 65 74  5f 33 5c 74 72 75 65 5c   ngsecret _3\true\
+0090  70 6c 61 79 65 72 5f 34  5c 69 4e 4b 41 53 5c 66   player_4 \iNKAS\f
+00a0  72 61 67 73 5f 34 5c 34  33 5c 70 69 6e 67 5f 34   rags_4\4 3\ping_4
+00b0  5c 20 39 30 5c 74 65 61  6d 5f 34 5c 31 5c 6d 65   \ 90\tea m_4\1\me
+00c0  73 68 5f 34 5c 4d 61 6c  65 20 43 6f 6d 6d 61 6e   sh_4\Mal e Comman
+00d0  64 6f 5c 73 6b 69 6e 5f  34 5c 43 6f 6d 6d 61 6e   do\skin_ 4\Comman
+00e0  64 6f 53 6b 69 6e 73 2e  63 6d 64 6f 5c 66 61 63   doSkins. cmdo\fac
+00f0  65 5f 34 5c 43 6f 6d 6d  61 6e 64 6f 53 6b 69 6e   e_4\Comm andoSkin
+0100  73 2e 42 6c 61 6b 65 5c  6e 67 73 65 63 72 65 74   s.Blake\ ngsecret
+0110  5f 34 5c 74 72 75 65 5c  70 6c 61 79 65 72 5f 35   _4\true\ player_5
+0120  5c 53 6b 69 54 7a 5c 66  72 61 67 73 5f 35 5c 31   \SkiTz\f rags_5\1
+0130  37 5c 70 69 6e 67 5f 35  5c 20 35 31 5c 74 65 61   7\ping_5 \ 51\tea
+0140  6d 5f 35 5c 32 35 35 5c  6d 65 73 68 5f 35 5c 4d   m_5\255\ mesh_5\M
+0150  61 6c 65 20 53 6f 6c 64  69 65 72 5c 73 6b 69 6e   ale Sold ier\skin
+0160  5f 35 5c 53 6f 6c 64 69  65 72 53 6b 69 6e 73 2e   _5\Soldi erSkins.
+0170  73 6c 64 72 5c 66 61 63  65 5f 35 5c 53 6f 6c 64   sldr\fac e_5\Sold
+0180  69 65 72 53 6b 69 6e 73  2e 42 72 6f 63 6b 5c 6e   ierSkins .Brock\n
+0190  67 73 65 63 72 65 74 5f  35 5c 66 61 6c 73 65 5c   gsecret_ 5\false\
+01a0  70 6c 61 79 65 72 5f 36  5c 4d 72 2e 46 61 68 72   player_6 \Mr.Fahr
+01b0  65 6e 68 65 69 74 5c 66  72 61 67 73 5f 36 5c 39   enheit\f rags_6\9
+01cc  30 5c 70 69 6e 67 5f 36  5c 20 31 35 38 5c 74 65   0\ping_6 \ 158\te
+01d0  61 6d 5f 36 5c 31 5c 6d  65 73 68 5f 36 5c 4d 61   am_6\1\m esh_6\Ma
+01e0  6c 65 20 53 6f 6c 64 69  65 72 5c 73 6b 69 6e 5f   le Soldi er\skin_
+01f0  36 5c 53 6f 6c 64 69 65  72 53 6b 69 6e 73 2e 68   6\Soldie rSkins.h
+0200  6b 69 6c 5c 66 61 63 65  5f 36 5c 53 6f 6c 64 69   kil\face _6\Soldi
+0210  65 72 53 6b 69 6e 73 2e  6d 61 74 72 69 78 5c 6e   erSkins. matrix\n
+0220  67 73 65 63 72 65 74 5f  36 5c 74 72 75 65 5c 70   gsecret_ 6\true\p
+0230  6c 61 79 65 72 5f 37 5c  64 65 6e 61 74 75 72 61   layer_7\ denatura
+0240  74 5c 66 72 61 67 73 5f  37 5c 37 37 5c 70 69 6e   t\frags_ 7\77\pin
+0250  67 5f 37 5c 20 34 38 5c  74 65 61 6d 5f 37 5c 31   g_7\ 48\ team_7\1
+0260  5c 6d 65 73 68 5f 37 5c  4d 61 6c 65 20 53 6f 6c   \mesh_7\ Male Sol
+0270  64 69 65 72 5c 73 6b 69  6e 5f 37 5c 53 6f 6c 64   dier\ski n_7\Sold
+0280  69 65 72 53 6b 69 6e 73  2e 68 6b 69 6c 5c 66 61   ierSkins .hkil\fa
+0290  63 65 5f 37 5c 53 6f 6c  64 69 65 72 53 6b 69 6e   ce_7\Sol dierSkin
+02a0  73 2e 6d 61 74 72 69 78  5c 6e 67 73 65 63 72 65   s.matrix \ngsecre
+02b0  74 5f 37 5c 74 72 75 65  5c 71 75 65 72 79 69 64   t_7\true \queryid
+02c0  5c 36 32 2e 32 5c 66 69  6e 61 6c 5c               \62.2\fi nal\    
+
+TODO: clean up
+TODO: more packets?
+TODO: description
+
+
+5.4. REPORTQUERY
+----------------
+(UDP, Client -> Server)
+
+0000  52 45 50 4f 52 54 51 55 45 52 59                  REPORTQUERY
+
+TODO: dump of reply
+
+This is broadcasted by the UT client to find LAN servers. Packet is sent to
+UDP ports 8777-8786.
+
+
+5.5. HTTP Traffic
+-----------------
+(TCP, Client -> Master:80)
+
+MOTD request:
+
+0000  47 45 54 20 2f 55 70 64 61 74 65 53 65 72 76 65   GET /UpdateServe
+0010  72 2f 75 74 6d 6f 74 64 34 35 31 2e 68 74 6d 6c   r/utmotd451.html
+0020  20 48 54 54 50 2f 31 2e 31 0d 0a 55 73 65 72 2d    HTTP/1.1..User-
+0030  41 67 65 6e 74 3a 20 55 6e 72 65 61 6c 0d 0a 43   Agent: Unreal..C
+0040  6f 6e 6e 65 63 74 69 6f 6e 3a 20 63 6c 6f 73 65   onnection: close
+0050  0d 0a 48 6f 73 74 3a 20 75 6e 72 65 61 6c 2e 65   ..Host: unreal.e
+0060  70 69 63 67 61 6d 65 73 2e 63 6f 6d 3a 38 30 0d   picgames.com:80.
+0070  0a 0d 0a                                          ...            
+
+MOTD fallback request, if above request fails:
+
+0000  47 45 54 20 2f 55 70 64 61 74 65 53 65 72 76 65   GET /UpdateServe
+0010  72 2f 75 74 6d 6f 74 64 66 61 6c 6c 62 61 63 6b   r/utmotdfallback
+0020  2e 68 74 6d 6c 20 48 54 54 50 2f 31 2e 31 0d 0a   .html HTTP/1.1..
+0030  55 73 65 72 2d 41 67 65 6e 74 3a 20 55 6e 72 65   User-Agent: Unre
+0040  61 6c 0d 0a 43 6f 6e 6e 65 63 74 69 6f 6e 3a 20   al..Connection: 
+0050  63 6c 6f 73 65 0d 0a 48 6f 73 74 3a 20 75 6e 72   close..Host: unr
+0060  65 61 6c 2e 65 70 69 63 67 61 6d 65 73 2e 63 6f   eal.epicgames.co
+0070  6d 3a 38 30 0d 0a 0d 0a                           m:80....
+
+masterserver.txt request:
+
+0000  47 45 54 20 2f 55 70 64 61 74 65 53 65 72 76 65   GET /UpdateServe
+0010  72 2f 75 74 6d 61 73 74 65 72 73 65 72 76 65 72   r/utmasterserver
+0020  2e 74 78 74 20 48 54 54 50 2f 31 2e 31 0d 0a 55   .txt HTTP/1.1..U
+0030  73 65 72 2d 41 67 65 6e 74 3a 20 55 6e 72 65 61   ser-Agent: Unrea
+0040  6c 0d 0a 43 6f 6e 6e 65 63 74 69 6f 6e 3a 20 63   l..Connection: c
+0050  6c 6f 73 65 0d 0a 48 6f 73 74 3a 20 75 6e 72 65   lose..Host: unre
+0060  61 6c 2e 65 70 69 63 67 61 6d 65 73 2e 63 6f 6d   al.epicgames.com
+0070  3a 38 30 0d 0a 0d 0a                              :80....
+
+utircserver.txt request:
+
+0000  47 45 54 20 2f 55 70 64 61 74 65 53 65 72 76 65   GET /UpdateServe
+0010  72 2f 75 74 69 72 63 73 65 72 76 65 72 2e 74 78   r/utircserver.tx
+0020  74 20 48 54 54 50 2f 31 2e 31 0d 0a 55 73 65 72   t HTTP/1.1..User
+0030  2d 41 67 65 6e 74 3a 20 55 6e 72 65 61 6c 0d 0a   -Agent: Unreal..
+0040  43 6f 6e 6e 65 63 74 69 6f 6e 3a 20 63 6c 6f 73   Connection: clos
+0050  65 0d 0a 48 6f 73 74 3a 20 75 6e 72 65 61 6c 2e   e..Host: unreal.
+0060  65 70 69 63 67 61 6d 65 73 2e 63 6f 6d 3a 38 30   epicgames.com:80
+0070  0d 0a 0d 0a                                       ....
+
+TODO: description of the files
+TODO: description
+
+
+5.6. Master Server Traffic
+--------------------------
+The following packets are chronologically ordered: 
+1st packet transmitted:
+(TCP, Master:28900 -> Client)
+
+0000  5c 62 61 73 69 63 5c 5c 73 65 63 75 72 65 5c 77   \basic\\secure\w
+0010  6f 6f 6b 69 65                                    ookie
+
+2nd packet:
+(TCP, Client -> Master:28900)
+
+0000  5c 67 61 6d 65 6e 61 6d 65 5c 75 74 5c 6c 6f 63   \gamename\ut\loc
+0010  61 74 69 6f 6e 5c 30 5c 76 61 6c 69 64 61 74 65   ation\0\validate
+0020  5c 32 2f 54 59 46 4d 52 63 5c 66 69 6e 61 6c 5c   \2/TYFMRc\final\
+
+3rd packet:
+(TCP, Client -> Master:28900)
+
+0000  5c 6c 69 73 74 5c 5c 67 61 6d 65 6e 61 6d 65 5c   \list\\gamename\
+0010  75 74 5c 66 69 6e 61 6c 5c                        ut\final\
+
+Then the client just receives the server list packets from the master:
+(TCP, Master:28900 -> Client)
+
+0000  5c 69 70 5c 36 39 2e 32 38 2e 32 32 30 2e 31 32   \ip\69.28.220.12
+0010  33 3a 37 37 37 38 5c 69 70 5c 32 31 36 2e 31 32   3:7778\ip\216.12
+0020  39 2e 32 34 32 2e 32 37 3a 38 37 38 39 5c 69 70   9.242.27:8789\ip
+0030  5c 32 31 32 2e 34 32 2e 31 36 2e 38 39 3a 32 37   \212.42.16.89:27
+0040  30 30 31 5c ...                                   001\
+ .
+ . (same or more packets)
+ .
+xxxx  66 69 6e 61 6c 5c                                 final\
+
+TODO: packet dump
+TODO: description
+
+--
+
+6. QuakeWorld protocol
+======================
+
+Master port: UDP 27000
+Server port: UDP 27500
+
+
+6.1. status
+-----------
+(UDP, Client -> Server:27500)
+
+0000  ff ff ff ff 73 74 61 74 75 73 0a                  ....status.
+
+This packet requests information from the server.
+
+
+6.2. n
+------
+(UDP, Server:27500 -> Client)
+
+QW server with no players:
+--------------------------
+0000  ff ff ff ff 6e 5c 73 70 61 77 6e 5c 30 5c 77 61   ....n\spawn\0\wa
+0010  74 65 72 76 69 73 5c 30 5c 2a 76 65 72 73 69 6f   tervis\0\*versio
+0020  6e 5c 32 2e 34 30 5c 68 6f 73 74 6e 61 6d 65 5c   n\2.40\hostname\
+0030  45 55 6e 65 74 20 5a 20 53 6d 61 6c 6c 20 44 65   EUnet Z Small De
+0040  61 74 68 6d 61 74 63 68 5c 2a 67 61 6d 65 64 69   athmatch\*gamedi
+0050  72 5c 71 77 5c 6d 61 78 66 70 73 5c 31 35 30 5c   r\qw\maxfps\150\
+0060  66 72 61 67 6c 69 6d 69 74 5c 30 5c 74 65 61 6d   fraglimit\0\team
+0070  70 6c 61 79 5c 30 5c 6d 61 78 63 6c 69 65 6e 74   play\0\maxclient
+0080  73 5c 38 5c 73 61 6d 65 6c 65 76 65 6c 5c 32 5c   s\8\samelevel\2\
+0090  5b 77 77 77 5d 5c 68 74 74 70 3a 2f 2f 77 77 77   [www]\http://www
+00a0  2e 63 6c 61 6e 2d 7a 2e 6f 72 67 2f 5c 5b 6d 61   .clan-z.org/\[ma
+00b0  70 73 5d 5c 68 74 74 70 3a 2f 2f 77 77 77 2e 63   ps]\http://www.c
+00c0  6c 61 6e 2d 7a 2e 6f 72 67 2f 66 69 6c 65 73 2f   lan-z.org/files/
+00d0  5c 5b 6d 61 78 72 61 74 65 5d 5c 32 30 30 30 30   \[maxrate]\20000
+00e0  5c 6d 61 78 73 70 65 63 74 61 74 6f 72 73 5c 38   \maxspectators\8
+00f0  2e 30 30 30 30 30 30 5c 2a 70 72 6f 67 73 5c 31   .000000\*progs\1
+0100  32 38 34 34 5c 70 6f 77 65 72 75 70 73 5c 30 5c   2844\powerups\0\
+0110  64 65 61 74 68 6d 61 74 63 68 5c 33 5c 74 69 6d   deathmatch\3\tim
+0120  65 6c 69 6d 69 74 5c 32 30 5c 6d 61 70 5c 64 6d   elimit\20\map\dm
+0130  36 0a 00                                          6..
+
+
+QW server with 7 players:
+-------------------------
+0000  ff ff ff ff 6e 5c 66 72 61 67 6c 69 6d 69 74 5c   ....n\fraglimit\
+0010  30 5c 74 65 61 6d 70 6c 61 79 5c 30 5c 6d 61 78   0\teamplay\0\max
+0020  76 69 70 5f 73 70 65 63 74 61 74 6f 72 73 5c 30   vip_spectators\0
+0030  5c 73 70 61 77 6e 5c 30 5c 77 61 74 65 72 76 69   \spawn\0\watervi
+0040  73 5c 30 5c 2a 71 77 65 5f 76 65 72 73 69 6f 6e   s\0\*qwe_version
+0050  5c 30 2e 31 37 33 5c 2a 76 65 72 73 69 6f 6e 5c   \0.173\*version\
+0060  32 2e 34 30 5c 68 6f 73 74 6e 61 6d 65 5c 58 53   2.40\hostname\XS
+0070  34 41 4c 4c 20 46 72 65 65 20 46 6f 72 20 41 6c   4ALL Free For Al
+0080  6c 5c 74 69 6d 65 6c 69 6d 69 74 5c 31 32 5c 64   l\timelimit\12\d
+0090  65 61 74 68 6d 61 74 63 68 5c 33 5c 73 61 6d 65   eathmatch\3\same
+00a0  6c 65 76 65 6c 5c 32 5c 6d 61 78 63 6c 69 65 6e   level\2\maxclien
+00b0  74 73 5c 31 36 5c 6d 61 78 73 70 65 63 74 61 74   ts\16\maxspectat
+00c0  6f 72 73 5c 36 5c 2a 67 61 6d 65 64 69 72 5c 71   ors\6\*gamedir\q
+00d0  77 5c 6e 65 65 64 70 61 73 73 5c 30 5c 61 64 6d   w\needpass\0\adm
+00e0  69 6e 5c 59 6f 4d 61 6d 61 28 79 6f 6d 61 6d 61   in\YoMama(yomama
+00f0  40 78 73 34 61 6c 6c 2e 6e 6c 29 20 2f 20 46 6c   @xs4all.nl) / Fl
+0100  65 50 73 65 72 20 28 6a 65 73 70 65 72 64 65 67   ePser (jesperdeg
+0110  72 61 61 66 40 79 61 68 6f 6f 2e 63 6f 6d 5c 2a   raaf@yahoo.com\*
+0120  70 72 6f 67 73 5c 31 31 35 33 34 5c 6d 61 70 5c   progs\11534\map\
+0130  75 6b 63 6c 64 6d 32 0a 31 37 39 30 35 20 30 20   ukcldm2.17905 0 
+0140  30 20 34 31 20 22 70 61 73 74 22 20 22 74 68 75   0 41 "past" "thu
+0150  67 34 6c 69 66 65 22 20 31 30 20 33 0a 31 37 39   g4life" 10 3.179
+0160  31 31 20 32 20 30 20 33 31 20 22 72 69 6f 74 22   11 2 0 31 "riot"
+0170  20 22 6f 30 6f 30 30 6f e8 20 e8 20 e8 2e 70 63    "o0o00o. . ..pc
+0180  78 22 20 31 31 20 31 31 0a 31 37 38 39 36 20 32   x" 11 11.17896 2 
+0190  20 30 20 36 30 20 22 44 44 22 20 22 6a 65 64 69    0 60 "DD" "jedi
+01a0  22 20 34 20 34 0a 31 37 38 38 39 20 31 20 30 20   " 4 4.17889 1 0 
+01b0  34 30 20 22 c4 75 6b 65 20 ce 75 6b 65 6d 22 20   40 ".uke .ukem" 
+01c0  22 62 61 73 65 22 20 30 20 30 0a 31 37 38 35 39   "base" 0 0.17859
+01d0  20 32 20 30 20 34 32 20 22 9d cb e5 f7 ec e9 ef    2 0 42 ".......
+01e0  9f 22 20 22 24 22 20 30 20 34 0a 31 37 38 39 30   ." "$" 0 4.17890
+01f0  20 30 20 30 20 39 39 39 20 22 8d 63 68 69 70 73    0 0 999 ".chips
+0200  22 20 22 6e 6f 6e 65 22 20 31 20 31 0a 31 37 39   " "none" 1 1.179
+0210  30 38 20 31 20 30 20 32 38 20 22 72 65 71 22 20   08 1 0 28 "req" 
+0220  22 63 68 65 66 73 22 20 31 32 20 38 0a 00         "chefs" 12 8..
+
+Example for client message display:
+-----------------------------------
+0000  ff ff ff ff 6e 42 61 64 20 72 63 6f 6e 5f 70 61   ....nBad rcon_pa
+0010  73 73 77 6f 72 64 2e 0a                           ssword..
+
+This is the response to a "status" packet. The "n" packet is also used to
+display messages on the client as seen in the last packet dump.
+The player information is organised as follows:
+struct {
+	int user_id;
+	int frags;
+	int time_spend_on_server;
+	int ping;
+	char *player_name;
+	char *player_skin;
+	int top_color;
+	int bottom_color;
+};
+
+
+6.3. c
+------
+(UDP, Client -> Master:27000)
+
+0000  63 0a 00                                          c..
+
+This packet requests the server list from the master.
+
+
+6.4. d
+------
+(UDP, Master:27000 -> Client)
+
+0000  ff ff ff ff 64 0a 42 45 65 94 6b 6c cd f5 49 6f   ....d.BEe.kl..Io
+0010  6b 6d c8 2a 5c ad 6b 6c c8 2c 38 a3 6b 6c d1 b8   km.*\.kl.,8.kl..
+0020  ea 54 6b 6c c2 6d 06 dc 6b 6c d1 bf cd b7 6b 6c   .Tkl.m..kl....kl
+0030  92 c9 07 4e 6b 76 d0 91 60 6f 6b 6d d4 98 20 8b   ...Nkv..`okm.. .
+0040  6b 6c 3e 50 85 b7 6b 6c d8 0c 56 0d 6b 6c 42 33   kl>P..kl..V.klB3
+0050  cd 38 6b 6c d1 92                                 .8kl..
+
+This is the reply to a "c" (server list request) packet. After the
+"\xff\xff\xff\xffd\n" string the servers are listed in 6 byte blocks. 4 bytes
+for the IP and 2 bytes for the port. There are no delimiters.
+
+
+6.5. ping
+---------
+(UDP, Master:27000 -> Server:27500)
+
+0000  ff ff ff ff 70 69 6e 67 0a 00                     ....ping..
+
+
+6.6. k
+------
+(UDP, Server:27500 -> Master:27000/Client)
+
+0000  6b 00                                             k.
+
+Ping from Server to Master or client.
+
+
+6.7. l
+------
+(UDP, All -> All)
+
+0000  ff ff ff ff 6c 0a 00                              ....l..
+
+This is the reply/ack to "ping" and "k" packets.
+
+
+6.8. a
+------
+(UDP, Server:27500 -> Master:27000)
+
+0000  61 0a 31 0a 30 0a                                 a.1.0.
+
+This is the heartbeat packet.
+TODO: What's the meaning of the numbers?
+
+
+6.9. C
+------
+(UDP, Server:27500 -> Master:27000)
+
+0000  43 0a                                             C.
+
+This packet tells the master that the server is shutting down.
+
+--
+
+7. HexenWorld protocol
+======================
+
+Master port: UDP 26900
+Server port: UDP 26950
+
+--
+
+8. RtCW protocol
+================
+
+Server port: UDP 27960
+
+Master server DNS name:	wolf.idsoftware.com
+Master server port:		UDP 27950
+MOTD server DNS name:	wolf.idsoftware.com
+MOTD server port:		UDP 27951
+Auth server DNS name:	wolf.idsoftware.com
+Auth server port:		UDP 27952
+
+The RtCW protocol is similar to the Q3 protocol.
+
+
+8.1. heartbeat
+--------------
+(UDP, Server:27960 -> Master:27950)
+
+0000  ff ff ff ff 68 65 61 72 74 62 65 61 74 20 57 6f   ....heartbeat Wo
+0010  6c 66 65 6e 73 74 65 69 6e 2d 31 0a               lfenstein-1.
+
+0000  ff ff ff ff 68 65 61 72 74 62 65 61 74 20 57 6f   ....heartbeat Wo
+0010  6c 66 46 6c 61 74 6c 69 6e 65 2d 31 0a            lfFlatline-1.
+
+Exactly like the Q3 heartbeat except for the different option. The 2nd
+"heartbeat" packet is sent by the server on shutdown.
+
+
+8.2. getinfo
+------------
+(UDP, Master:27950 -> Server:27960)
+
+getinfo from Wolf master:
+-------------------------
+0000  ff ff ff ff 67 65 74 69 6e 66 6f 20 31 30 35 31   ....getinfo 1051
+0010  33 39 36 30 35 30 34 34 35                        396050445
+
+TODO: getinfo without challenge
+
+Just like the Q3 protocol. The challenge number is a 32 bit signed integer and
+is optional.
+
+
+8.3. getIpAuthorize
+-------------------
+(UDP, Server:27960 -> Master:27952)
+
+0000  ff ff ff ff 67 65 74 49 70 41 75 74 68 6f 72 69   ....getIpAuthori
+0010  7a 65 20 2d 33 34 32 33 39 30 34 39 39 20 31 39   ze -342390499 19
+0020  32 2e 32 34 36 2e 34 30 2e 36 35 20 20 30         2.246.40.65  0
+
+This packet is sent by the server to the master every time a client is
+connecting. It's used to check with the master if the client has a valid CD key
+and has authorized itself at the master. The number is a challenge number which
+is a 32 bit signed integer.
+TODO: What is the meaning of the "0" at the end of the packet?
+
+
+8.4. ipAuthorize
+----------------
+(UDP, Master:27952 -> Server:27960)
+
+0000  ff ff ff ff 69 70 41 75 74 68 6f 72 69 7a 65 20   ....ipAuthorize 
+0010  2d 33 34 32 33 39 30 34 39 39 20 64 65 6e 79 20   -342390499 deny 
+0020  49 4e 56 41 4c 49 44 5f 43 44 4b 45 59            INVALID_CDKEY
+
+This is the reply to a "getIpAuthorize" packet. It is used to authorize a
+client connecting to a server. The server sends the "getIpAuthorize" packet to
+the master, which looks up if the cdkey of the IP is valid or not. If the IP
+is not authorized by the master it sends the above packet with the string
+"deny INVALID_CDKEY". The number is a challenge number which is a 32bit signed
+integer.
+
+
+8.5. infoResponse
+-----------------
+(UDP, Server:27960 -> Client)
+
+infoResponse with challenge:
+----------------------------
+0000  ff ff ff ff 69 6e 66 6f 52 65 73 70 6f 6e 73 65   ....infoResponse
+0010  0a 5c 63 68 61 6c 6c 65 6e 67 65 5c 31 30 35 31   .\challenge\1051
+0020  33 39 36 30 35 30 34 34 35 5c 70 72 6f 74 6f 63   396050445\protoc
+0030  6f 6c 5c 35 30 5c 68 6f 73 74 6e 61 6d 65 5c 57   ol\50\hostname\W
+0040  6f 6c 66 48 6f 73 74 5c 6d 61 70 6e 61 6d 65 5c   olfHost\mapname\
+0050  6d 70 5f 62 65 61 63 68 5c 63 6c 69 65 6e 74 73   mp_beach\clients
+0060  5c 30 5c 73 76 5f 6d 61 78 63 6c 69 65 6e 74 73   \0\sv_maxclients
+0070  5c 32 30 5c 67 61 6d 65 74 79 70 65 5c 35 5c 70   \20\gametype\5\p
+0080  75 72 65 5c 31 5c 73 76 5f 61 6c 6c 6f 77 41 6e   ure\1\sv_allowAn
+0090  6f 6e 79 6d 6f 75 73 5c 30 5c 67 61 6d 65 73 6b   onymous\0\gamesk
+00a0  69 6c 6c 5c 33 5c 66 72 69 65 6e 64 6c 79 46 69   ill\3\friendlyFi
+00b0  72 65 5c 31 5c 6d 61 78 6c 69 76 65 73 5c 30 5c   re\1\maxlives\0\
+00c0  74 6f 75 72 6e 65 79 5c 30                        tourney\0
+
+TODO: infoResponse from a populated server
+
+
+8.6. getmotd
+------------
+(UDP, Client -> Master:27951)
+
+0000  ff ff ff ff 67 65 74 6d 6f 74 64 20 22 5c 63 68   ....getmotd "\ch
+0010  61 6c 6c 65 6e 67 65 5c 36 38 33 32 32 33 31 36   allenge\68322316
+0020  37 5c 72 65 6e 64 65 72 65 72 5c 47 65 46 6f 72   7\renderer\GeFor
+0030  63 65 33 2f 41 47 50 2f 53 53 45 5c 76 65 72 73   ce3/AGP/SSE\vers
+0040  69 6f 6e 5c 57 6f 6c 66 20 31 2e 31 2d 4d 50 20   ion\Wolf 1.1-MP 
+0050  6c 69 6e 75 78 2d 69 33 38 36 20 44 65 63 20 32   linux-i386 Dec 2
+0060  35 20 32 30 30 31 22 0a                           5 2001".
+
+This packet requests the Motto of the Day from the master. The packet contains
+some information about the client such as the value of the "renderer" and
+"version" variables. It also contains a challenge number, which is a 32 bit
+signed integer.
+
+
+8.7. motd
+---------
+(UDP, Master:27951 -> Client)
+
+0000  ff ff ff ff 6d 6f 74 64 20 22 63 68 61 6c 6c 65   ....motd "challe
+0010  6e 67 65 5c 36 38 33 32 32 33 31 36 37 5c 6d 6f   nge\683223167\mo
+0020  74 64 5c 57 69 6c 6c 6b 6f 6d 6d 65 6e 21 20 20   td\Willkommen!  
+0030  57 65 6c 63 6f 6d 65 21 5c 22                     Welcome!\"
+
+This is the reply to a "getmotd" packet. It contains the challenge number and
+the Motto of the Day. The MOTD is shown on the client while loading the game
+data.
+
+
+8.8. getservers
+---------------
+(UDP, Client -> Master:27950)
+
+0000  ff ff ff ff 67 65 74 73 65 72 76 65 72 73 20 36   ....getservers 6
+0010  30 20 65 6d 70 74 79 20 66 75 6c 6c 20 64 65 6d   0 empty full dem
+0020  6f 0a                                             o.
+
+This packet is used to request the server list from the master. After
+"getservers" follows the protocol version and filter options.
+	empty	include servers with no players on them
+	full	include full servers
+	demo	include demo version servers
+
+--
+
+9. Doom3 protocol
+=================
+
+Server port:			UDP 27666
+Master server port: 	UDP 27650
+Master server DNS name:	idnet.ua-corp.com
+
+
+9.1. getServers
+---------------
+(UDP, Client -> Master:27650)
+
+0000  ff ff 67 65 74 53 65 72 76 65 72 73 00 21 00 01   ..getServers.!..
+0010  00 00 00                                          ...
+
+This packet requests the server list from the master. A \0 delimiter follows and
+the next 4 bytes are the requested protocol version (In this example 1.33).
+Then after the next \0 delimiter we have the filter byte. The LSBs of the 18th
+byte are interpreted as a server filter and the MSBs as a game type filter.
+0x01	passworded servers
+0x02	non-passworded servers
+0x04	no full servers
+0x08	no full/empty servers
+0x10	Deathmatch
+0x20	Tournament/1v1
+0x30	TDM		XXX: 0x30? not 0x40?
+
+
+9.2. versionCheck
+-----------------
+(UDP, Server:27666/Client -> Master:27650)
+
+0000  ff ff 76 65 72 73 69 6f 6e 43 68 65 63 6b 00 21   ..versionCheck.!
+0010  00 01 00 00 00 44 4f 4f 4d 20 31 2e 30 2e 31 32   .....DOOM 1.0.12
+0020  36 32 20 77 69 6e 2d 78 38 36 20 4a 75 6c 20 20   62 win-x86 Jul
+0030  38 20 32 30 30 34 20 31 36 3a 34 36 3a 33 37 00   8 2004 16:46:37.
+0040  00                                                .
+
+FIXME: No clue what this packet is all about.
+The 4 bytes after "versionCheck" plus \0 delimiter are the protocol version.
+(see getServers)
+
+
+9.3. servers
+------------
+(UDP, Master:27650 -> Client)
+
+0000  ff ff 73 65 72 76 65 72 73 00 42 1b 42 dd 12 6c   ..servers.B.B..l
+0010  18 76 2d fa 12 6c 18 00 be ad 12 6c 43 a7 fe 3b   .v-..l.....lC..;
+0020  12 6c d9 e2 e0 0e 12 6c 80 c2 44 4b 12 6c cf 24   .l.....l..DK.l.$
+0030  d1 8c 12 6c 46 19 7d a3 12 6c 45 09 a1 6b 12 6c   ...lF.}..lE..k.l
+0040  45 6e 21 52 12 6c 40 ed 6b 14 cf 6c 44 0a dd 7e   En!R.l@.k..lD..~
+0050  12 6c 18 a7 e7 53 0a eb 40 ed 6b 14 3f 6b 18 f7   .l...S..@.k.?k..
+0060  95 7c 12 6c 18 04 3a 56 12 6c 18 21 f0 33 43 fa   .|.l..:V.l.!.3C.
+0070  53 e2 14 80 12 6c 50 da 2c 21 12 6c 0c d8 d7 1b   S....lP.,!.l....
+0080  12 6c 43 12 b2 34 12 6c de b7 1c 1f 36 0b d8 0c   .lC..4.l....6...
+0090  46 02 12 6c 45 16 a6 42 12 6c 52 d1 f0 a6 12 6c   F..lE..B.lR....l
+00a0  18 2c 40 75 39 eb 43 65 ba 98 12 6c 40 51 59 da   .,@u9.Ce...l@QY.
+00b0  12 6c 42 a6 bb d5 12 6c 42 d8 dc 6a 12 6c 3e b2   .lB....lB..j.l>.
+00c0  43 f7 12 6c 45 84 8f 82 12 6c 41 31 ad 0d 12 6c   C..lE....lA1...l
+00d0  41 1b 00 ca 12 6c 44 40 c8 9a 12 6c 40 88 c3 43   A....lD@...l@..C
+00e0  12 6c 18 72 f9 ed 12 6c 18 a7 b2 c2 89 f0 46 f0   .l.r...l......F.
+00f0  0b 87 12 6c 44 35 30 32 12 6c 42 c9 e6 01 50 8a   ...lD502.lB...P.
+
+Looks like the "servers" packet from Q2. Thera are no delimiters and we have
+IP/port pairs with 4 bytes for IP and 2 bytes for the port address. And there
+is no limit on the packet length. Beware the port address; it's in network
+byte order.
+
+
+9.4. getInfo
+------------
+(UDP, Master:27650/Client -> Server:27666)
+
+getInfo without challenge:
+--------------------------
+0000  ff ff 67 65 74 49 6e 66 6f 00 00 00 00 00         ..getInfo.....
+
+getInfo with challenge:
+-----------------------
+0000  ff ff 67 65 74 49 6e 66 6f 00 01 01 01 01         ..getInfo.....
+
+Request server information. After "getInfo" and a \0 delimiter follows the
+challenge number as a 4 byte integer.
+
+
+9.5. infoResponse
+-----------------
+(UDP, Server:27666 -> Master:27650/Client)
+
+infoResponse from an empty server:
+----------------------------------
+0000  ff ff 69 6e 66 6f 52 65 73 70 6f 6e 73 65 00 00   ..infoResponse..
+0010  00 00 00 21 00 01 00 66 73 5f 67 61 6d 65 00 00   ...!...fs_game..
+0020  73 69 5f 76 65 72 73 69 6f 6e 00 44 4f 4f 4d 20   si_version.DOOM 
+0030  31 2e 30 2e 31 32 36 32 20 77 69 6e 2d 78 38 36   1.0.1262 win-x86
+0040  20 4a 75 6c 20 20 38 20 32 30 30 34 20 31 36 3a    Jul  8 2004 16:
+0050  34 36 3a 33 37 00 73 69 5f 6d 61 78 50 6c 61 79   46:37.si_maxPlay
+0060  65 72 73 00 31 36 00 73 69 5f 73 70 65 63 74 61   ers.16.si_specta
+0070  74 6f 72 73 00 31 00 73 69 5f 75 73 65 70 61 73   tors.1.si_usepas
+0080  73 00 31 00 73 69 5f 77 61 72 6d 75 70 00 30 00   s.1.si_warmup.0.
+0090  73 69 5f 74 65 61 6d 44 61 6d 61 67 65 00 30 00   si_teamDamage.0.
+00a0  73 69 5f 74 69 6d 65 4c 69 6d 69 74 00 31 30 00   si_timeLimit.10.
+00b0  73 69 5f 66 72 61 67 4c 69 6d 69 74 00 31 30 00   si_fragLimit.10.
+00c0  73 69 5f 6d 61 70 00 67 61 6d 65 2f 6d 70 2f 64   si_map.game/mp/d
+00d0  33 64 6d 31 00 73 69 5f 67 61 6d 65 54 79 70 65   3dm1.si_gameType
+00e0  00 64 65 61 74 68 6d 61 74 63 68 00 73 69 5f 6e   .deathmatch.si_n
+00f0  61 6d 65 00 2a 42 57 2a 20 4f 7a 27 73 20 43 6c   ame.*BW* Oz's Cl
+0100  6f 73 65 74 00 73 69 5f 70 75 72 65 00 31 00 67   oset.si_pure.1.g
+0110  61 6d 65 6e 61 6d 65 00 62 61 73 65 44 4f 4f 4d   amename.baseDOOM
+0120  2d 31 00 00 00 20 01 00 00 00                     -1... ....
+
+infoResponse from a populated server:
+-------------------------------------
+0000  ff ff 69 6e 66 6f 52 65 73 70 6f 6e 73 65 00 00   ..infoResponse..
+0010  00 00 00 21 00 01 00 66 73 5f 67 61 6d 65 00 00   ...!...fs_game..
+0020  73 69 5f 76 65 72 73 69 6f 6e 00 44 4f 4f 4d 20   si_version.DOOM 
+0030  31 2e 30 2e 31 32 36 32 20 77 69 6e 2d 78 38 36   1.0.1262 win-x86
+0040  20 4a 75 6c 20 20 38 20 32 30 30 34 20 31 36 3a    Jul  8 2004 16:
+0050  34 36 3a 33 37 00 73 69 5f 6d 61 78 70 6c 61 79   46:37.si_maxplay
+0060  65 72 73 00 38 00 73 69 5f 73 70 65 63 74 61 74   ers.8.si_spectat
+0070  6f 72 73 00 31 00 73 69 5f 75 73 65 70 61 73 73   ors.1.si_usepass
+0080  00 30 00 73 69 5f 77 61 72 6d 75 70 00 30 00 73   .0.si_warmup.0.s
+0090  69 5f 74 65 61 6d 44 61 6d 61 67 65 00 30 00 73   i_teamDamage.0.s
+00a0  69 5f 74 69 6d 65 4c 69 6d 69 74 00 31 30 00 73   i_timeLimit.10.s
+00b0  69 5f 66 72 61 67 4c 69 6d 69 74 00 31 30 00 73   i_fragLimit.10.s
+00c0  69 5f 6d 61 70 00 67 61 6d 65 2f 6d 70 2f 64 33   i_map.game/mp/d3
+00d0  64 6d 33 00 73 69 5f 67 61 6d 65 54 79 70 65 00   dm3.si_gameType.
+00e0  64 65 61 74 68 6d 61 74 63 68 00 73 69 5f 6e 61   deathmatch.si_na
+00f0  6d 65 00 57 69 63 6b 65 64 20 4d 6f 6a 6f 20 50   me.Wicked Mojo P
+0100  75 62 00 73 69 5f 70 75 72 65 00 31 00 67 61 6d   ub.si_pure.1.gam
+0110  65 6e 61 6d 65 00 62 61 73 65 44 4f 4f 4d 2d 31   ename.baseDOOM-1
+0120  00 00 00 00 5e 00 80 3e 00 00 6b 65 76 69 6e 00   ....^..>..kevin.
+0130  01 4a 00 80 3e 00 00 46 65 6c 69 63 65 00 02 4c   .J..>..Felice..L
+0140  00 80 3e 00 00 50 6c 61 79 65 72 5f 5f 00 03 39   ..>..Player__..9
+0150  00 80 3e 00 00 61 61 77 30 6e 00 04 75 00 80 3e   ..>..aaw0n..u..>
+0160  00 00 6d 65 65 6b 6c 6f 00 05 8c 00 80 3e 00 00   ..meeklo.....>..
+0170  55 6c 74 5e 35 69 6d 5e 34 75 73 00 07 91 00 80   Ult^5im^4us.....
+0180  3e 00 00 6d 65 67 61 20 6d 61 6e 00 08 38 00 80   >..mega man..8..
+0190  3e 00 00 42 72 75 63 65 20 4c 65 65 20 43 72 6f   >..Bruce Lee Cro
+01a0  73 73 6b 69 63 6b 00 20 01 00 00 00               sskick. ....
+
+The structure is comparable to the other ID engines' protocols. First, the
+command. Second, some console variables and their values. Third, the player
+info. Player info is organized as follows:
+FIXME:
+struct doom3_player_info
+{
+	short id;
+	short ping;
+	short rate;
+	char unknown[2] = "\0\0";
+	char *nickname;
+};
+According to qstat the challenge number follows directly after the
+"infoResponse" string. After the challenge we have the version.
+
+
+9.6. newVersion
+---------------
+(UDP, Master:27650 -> Server:27666/Client)
+
+0000  ff ff 6e 65 77 56 65 72 73 69 6f 6e 00 44 6f 77   ..newVersion.Dow
+0010  6e 6c 6f 61 64 20 76 65 72 73 69 6f 6e 20 31 2e   nload version 1.
+0020  31 3f 00 00 68 74 74 70 3a 2f 2f 77 77 77 2e 64   1?..http://www.d
+0030  6f 6f 6d 33 2e 63 6f 6d 2f 75 70 64 61 74 65 2e   oom3.com/update.
+0040  61 73 70 00 01 68 74 74 70 3a 2f 2f 77 77 77 2e   asp..http://www.
+0050  64 6f 6f 6d 33 2e 63 6f 6d 2f 75 70 64 61 74 65   doom3.com/update
+0060  2e 61 73 70 00                                    .asp.
+
+The "newVersion" packet is the reply to a "versionCheck" packet.
+
+
+9.7. heartbeat
+--------------
+(UDP, Server:27666 -> Master:27650)
+
+0000  ff ff 68 65 61 72 74 62 65 61 74 00               ..heartbeat.
+
+The good old "heartbeat" packet. Each time the Doom3 Master Server receives a
+"heartbeat" packet it sends a "getInfo" packet to that server.
+
+
+9.8. srvAuth
+------------
+(UDP, Server:27666 -> Master:27650)
+
+0000  ff ff 73 72 76 41 75 74 68 00 21 00 01 00 d9 ff   ..srvAuth.!.....
+0010  81 37 07 04 ff ff ff ff                           .7......
+
+A different srvAuth packet:
+---------------------------
+0000  ff ff 73 72 76 41 75 74 68 00 21 00 01 00 d9 ff   ..srvAuth.!.....
+0010  81 37 07 04 af 10 00 00 00                        .7.......
+
+XXX: What's that? Seems to include the server's version number.
+
+
+9.9. auth
+---------
+(UDP, Master:27650 -> Server:27666)
+
+0000  ff ff 61 75 74 68 00 02 ff ff d9 ff 81 37 00 00   ..auth.......7..
+0010  00 04                                             ..
+
+A different auth packet:
+------------------------
+0000  ff ff 61 75 74 68 00 01 af 10 d9 ff 81 37 00 00   ..auth.......7..
+0010  45 43 6c 4b 72 4a 42 38 6c 6b 63 00               EClKrJB8lkc.
+
+XXX: What's that?
+This is the reply to a "srvAuth" packet.
+
+
+9.10. challenge
+---------------
+(UDP, Client -> Server:27666)
+
+0000  ff ff 63 68 61 6c 6c 65 6e 67 65 00 af 10 00 00   ..challenge.....
+
+
+9.11. challengeResponse
+-----------------------
+(UDP, Server:27666 -> Client)
+
+0000  ff ff 63 68 61 6c 6c 65 6e 67 65 52 65 73 70 6f   ..challengeRespo
+0010  6e 73 65 00 0d a8 29 00 61 4a                     nse...).aJ    
+
+This is the reply to a "challenge" packet.
+XXX: What do the bytes after the command string mean?
+
+--
+
+10. Half-Life protocol
+======================
+
+Server port: UDP 27015
+Master port: UDP 27010
+
+
+10.1. s
+-------
+(Master:27010/Client -> Server)
+
+With challenge:
+---------------
+0000  ff ff ff ff 73 0a 25 63 25 75                     ....s.%c%u
+
+This packet requests information from a server. The last 4 bytes in the packet
+are a 32 bit integer. The Master sents this packet with a challenge number on
+every "q" packet it receives.
+
+
+10.2. q
+-------
+(UDP, Server -> Master:27010)
+
+0000  71                                                q
+
+This is the heartbeat packet which is sent by the server to the Master every
+300 seconds.
+
+
+10.3. 0
+-------
+(UDP, Server:27015 -> Master:27010/Client)
+
+Without players:
+----------------
+0000  30 0a 5c 70 72 6f 74 6f 63 6f 6c 5c 34 36 5c 63   0.\protocol\46\c
+0010  68 61 6c 6c 65 6e 67 65 5c 32 31 30 30 34 35 33   hallenge\2100453
+0020  34 34 39 5c 70 6c 61 79 65 72 73 5c 30 5c 6d 61   449\players\0\ma
+0030  78 5c 36 5c 67 61 6d 65 64 69 72 5c 76 61 6c 76   x\6\gamedir\valv
+0040  65 5c 6d 61 70 5c 75 6e 64 65 72 74 6f 77 5c 74   e\map\undertow\t
+0050  79 70 65 5c 64 5c 70 61 73 73 77 6f 72 64 5c 30   ype\d\password\0
+0060  5c 6f 73 5c 6c 5c 73 65 63 75 72 65 5c 30 5c 6c   \os\l\secure\0\l
+0070  61 6e 5c 30 5c 76 65 72 73 69 6f 6e 5c 33 2e 31   an\0\version\3.1
+0080  2e 31 2e 30 0a                                    .1.0. 
+
+This packet is the reply to a "s" packet.
+
+--
+
+11. Enemy Territory protocol
+============================
+
+Auth server DNS name:	au2rtcw2.activision.com
+Auth server port:		UDP 27960
+MOTD server DNS name:	etmaster.idsoftware.com
+MOTD server port:		UDP 27951
+Master server DNS name:	etmaster.idsoftware.com
+Master server port:		UDP 27950
+
+
+11.1. getUpdateInfo
+-------------------
+(UDP, Client -> Master:27960)
+
+0000  ff ff ff ff 67 65 74 55 70 64 61 74 65 49 6e 66   ....getUpdateInf
+0010  6f 20 22 45 54 20 32 2e 35 35 22 20 22 6c 69 6e   o "ET 2.55" "lin
+0020  75 78 2d 69 33 38 36 22 0a                        ux-i386".
+
+TODO: description
+
+
+11.2. updateResponse
+--------------------
+(UDP, Master:27960 -> Client)
+
+0000  ff ff ff ff 75 70 64 61 74 65 52 65 73 70 6f 6e   ....updateRespon
+0010  73 65 20 30                                       se 0
+
+TODO: description
+
+
+11.3. getmotd
+-------------
+(UDP, Client -> Master:27951)
+
+0000  ff ff ff ff 67 65 74 6d 6f 74 64 20 22 5c 63 68   ....getmotd "\ch
+0010  61 6c 6c 65 6e 67 65 5c 38 32 32 32 38 33 31 36   allenge\82228316
+0020  38 5c 72 65 6e 64 65 72 65 72 5c 47 65 46 6f 72   8\renderer\GeFor
+0030  63 65 33 2f 41 47 50 2f 53 53 45 5c 76 65 72 73   ce3/AGP/SSE\vers
+0040  69 6f 6e 5c 45 54 20 32 2e 35 35 20 6c 69 6e 75   ion\ET 2.55 linu
+0050  78 2d 69 33 38 36 20 4d 61 79 20 32 37 20 32 30   x-i386 May 27 20
+0060  30 33 22 0a                                       03".  
+
+TODO: description
+
+
+11.4. motd
+----------
+(UDP, Master:27951 -> Client)
+
+0000  ff ff ff ff 6d 6f 74 64 20 22 63 68 61 6c 6c 65   ....motd "challe
+0010  6e 67 65 5c 38 32 32 32 38 33 31 36 38 5c 6d 6f   nge\822283168\mo
+0020  74 64 5c 46 75 6c 6c 20 76 65 72 73 69 6f 6e 20   td\Full version 
+0030  6e 6f 77 20 61 76 61 69 6c 61 62 6c 65 20 66 6f   now available fo
+0040  72 20 64 6f 77 6e 6c 6f 61 64 21 21 5c 22         r download!!\"
+
+TODO: description
+
+
+11.5. getservers
+----------------
+(UDP, Client -> Master:27950)
+
+0000  ff ff ff ff 67 65 74 73 65 72 76 65 72 73 20 38   ....getservers 8
+0010  32 20 66 75 6c 6c 20 65 6d 70 74 79               2 full empty
+
+TODO: description
+
+
+11.6. getserversResponse
+------------------------
+(UDP, Master:27950 -> Client)
+
+0000  ff ff ff ff 67 65 74 73 65 72 76 65 72 73 52 65   ....getserversRe
+0010  73 70 6f 6e 73 65 5c cf 6f aa 7a 6d 60 5c a1 35   sponse\.o.zm`\.5
+0020  21 05 6d 38 5c 45 4f 54                           !.m8\EOT
+
+TODO: description
+
+
+11.7. getinfo
+-------------
+(UDP, Client -> Server)
+
+0000  ff ff ff ff 67 65 74 69 6e 66 6f 20 78 78 78      ....getinfo xxx
+
+TODO: description
+
+
+11.8. infoResponse
+------------------
+(UDP, Server -> Client)
+
+0000  ff ff ff ff 69 6e 66 6f 52 65 73 70 6f 6e 73 65   ....infoResponse
+0010  0a 5c 63 68 61 6c 6c 65 6e 67 65 5c 78 78 78 5c   .\challenge\xxx\
+0020  70 72 6f 74 6f 63 6f 6c 5c 38 32 5c 68 6f 73 74   protocol\82\host
+0030  6e 61 6d 65 5c 5e 35 46 72 61 67 67 65 72 73 48   name\^5FraggersH
+0040  65 61 76 65 6e 20 5e 38 6e 30 6c 61 6d 65 5c 73   eaven ^8n0lame\s
+0050  65 72 76 65 72 6c 6f 61 64 5c 38 5c 6d 61 70 6e   erverload\8\mapn
+0060  61 6d 65 5c 62 61 74 74 65 72 79 5c 63 6c 69 65   ame\battery\clie
+0070  6e 74 73 5c 31 34 5c 73 76 5f 6d 61 78 63 6c 69   nts\14\sv_maxcli
+0080  65 6e 74 73 5c 31 34 5c 67 61 6d 65 74 79 70 65   ents\14\gametype
+0090  5c 34 5c 70 75 72 65 5c 31 5c 73 76 5f 61 6c 6c   \4\pure\1\sv_all
+00a0  6f 77 41 6e 6f 6e 79 6d 6f 75 73 5c 30 5c 66 72   owAnonymous\0\fr
+00b0  69 65 6e 64 6c 79 46 69 72 65 5c 31 5c 6d 61 78   iendlyFire\1\max
+00c0  6c 69 76 65 73 5c 30 5c 6e 65 65 64 70 61 73 73   lives\0\needpass
+00d0  5c 30 5c 70 75 6e 6b 62 75 73 74 65 72 5c 31 5c   \0\punkbuster\1\
+00e0  67 61 6d 65 6e 61 6d 65 5c 65 74 5c 67 5f 61 6e   gamename\et\g_an
+00f0  74 69 6c 61 67 5c 31 5c 77 65 61 70 72 65 73 74   tilag\1\weaprest
+0100  72 69 63 74 5c 32 30 5c 62 61 6c 61 6e 63 65 64   rict\20\balanced
+0110  74 65 61 6d 73 5c 31                              teams\1
+
+TODO: description
+
+
+11.9. getchallenge
+------------------
+(UDP, Client -> Server)
+
+0000  ff ff ff ff 67 65 74 63 68 61 6c 6c 65 6e 67 65   ....getchallenge
+
+TODO: description
+
+11.10. challengeResponse
+------------------------
+(UDP, Server -> Client)
+
+0000  ff ff ff ff 63 68 61 6c 6c 65 6e 67 65 52 65 73   ....challengeRes
+0010  70 6f 6e 73 65 20 33 37 37 36 39 30 30 37 32      ponse 377690072
+
+TODO: description
+
+11.11. print
+------------
+(UDP, Server -> Client)
+
+0000  ff ff ff ff 70 72 69 6e 74 0a 53 65 72 76 65 72   ....print.Server
+0010  20 69 73 20 66 75 6c 6c 2e 0a                     is full..
+
+TODO: description
+
+--
+
+12. Links
+=========
+
+QStat - http://www.qstat.org
+XQF - http://www.linuxgames.com/xqf/
+Ethereal - http://www.ethereal.com
+Q2 Network Protocol Specs - http://www.csse.monash.edu.au/~timf/bottim/q2net/q2network-0.03.html
+Luigi Auriemma's homepage - http://aluigi.altervista.org
+QuakeForge - http://www.quakeforge.net
+
+
+Last modified: 2005-05-25 Andre' Schulz
+
+(C) Copyright 2003,2004,2005 Ingo Rohlfs & Andre' Schulz
+Released under GPL
+

+ 111 - 0
src/masterserver/logging.c

@@ -0,0 +1,111 @@
+/* logging.c: Logging code for masterserver. */
+/* This file is part of masterserver.
+ *
+ * masterserver is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * masterserver is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with masterserver; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+/*
+ * vim:sw=4:ts=4
+ */
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <time.h>
+
+#include "logging.h"
+
+#define DEFAULT_LOGFILE "/var/log/masterserver.log"
+#define DEFAULT_FILEMODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
+#define DEFAULT_FILEFLAGS (O_CREAT | O_RDWR)
+#define DEFAULT_PROGNAME "masterserver"
+
+char *default_logfile = NULL;	// can be set from main(); fallback to DEFAULT_LOGFILE
+char *default_progname = NULL;
+int _lfd = 0;	// global file descriptor where debug messages go
+FILE *_lfp = NULL;	// global file pointer where debug messages go
+
+int _log_level = 0;
+
+int log_init(char *filename, char *progname) 
+{
+	default_progname = progname != NULL ? strdup(progname) : strdup(DEFAULT_PROGNAME);
+	if (filename != NULL) {
+		default_logfile = strdup(filename);
+		_lfd = open(default_logfile, DEFAULT_FILEFLAGS, DEFAULT_FILEMODE);
+		if (_lfd == -1) {
+			fprintf(stderr, "%s, %d: %s %s", __FILE__, __LINE__, default_logfile, strerror(errno));
+			return -1;
+		}
+
+		_lfp = fdopen(_lfd, "a");
+		if (_lfp == NULL) {
+			fprintf(stderr, "%s, %d: %s", __FILE__, __LINE__, strerror(errno)); // redundant, maybe stderr is closed
+			return -1;
+		}
+	} else {
+		_lfp = stdout;
+	}
+	return 0;
+}
+
+void log_write(int log_level, char *subname, char *fmt, ...) {
+	time_t t;
+	struct tm *tm_now;
+	va_list tmp;
+
+	t = time(NULL);
+	tm_now = localtime(&t);
+
+	fprintf(_lfp, "[%.2d.%.2d.%d %.2d:%.2d:%.2d] %s:%s ", 
+		tm_now->tm_mday, tm_now->tm_mon + 1, tm_now->tm_year + 1900,
+		tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec,
+		default_progname, subname);
+
+	switch (log_level) {
+		case LOG_LEVEL_INFO:
+			fprintf(_lfp, "info: ");
+			break;
+		case LOG_LEVEL_WARNING:
+			fprintf(_lfp, "warning: ");
+			break;
+		case LOG_LEVEL_ERROR:
+			fprintf(_lfp, "Error: ");
+			break;
+		case LOG_LEVEL_DEBUG:
+			fprintf(_lfp, "Debug: ");
+			break;
+	}
+
+	va_start(tmp, fmt);
+	vfprintf(_lfp, fmt, tmp);
+	va_end(tmp);
+
+	fflush(_lfp); // write it to disk
+}
+
+
+void log_close(void) 
+{
+	if (close(_lfd) == -1) {
+		fprintf(stderr, "%s, %d: %s", __FILE__, __LINE__, strerror(errno)); // redundant, maybe stderr is closed
+		return;     
+	}
+}
+

+ 39 - 0
src/masterserver/logging.h

@@ -0,0 +1,39 @@
+#ifndef _LOGGING_H
+#define _LOGGING_H
+
+#define LOG_LEVEL_INFO		0
+#define LOG_LEVEL_WARNING	1
+#define LOG_LEVEL_ERROR		2
+#define LOG_LEVEL_DEBUG		3
+
+#define INFO(fmt, args...) \
+		log_write(LOG_LEVEL_INFO, LOG_SUBNAME, fmt, ##args);
+	/*if (_log_level <= LOG_LEVEL_INFO) \*/
+#define WARNING(fmt, args...) \
+		log_write(LOG_LEVEL_WARNING, LOG_SUBNAME, fmt, ##args);
+	/*if (_log_level <= LOG_LEVEL_WARNING) \*/
+#define ERROR(fmt) \
+		log_write(LOG_LEVEL_ERROR, LOG_SUBNAME, "in %s near line %d: "fmt, __FILE__, __LINE__);
+	/*if (_log_level <= LOG_LEVEL_ERROR) \*/
+#define ERRORV(fmt, args...) \
+		log_write(LOG_LEVEL_ERROR, LOG_SUBNAME, "in %s near line %d: "fmt, __FILE__, __LINE__, ##args);
+	/*if (_log_level <= LOG_LEVEL_ERROR) \*/
+
+#ifdef DEBUG
+#undef DEBUG
+#define DEBUG(fmt, args...) if (debug == 1) log_write(LOG_LEVEL_DEBUG, LOG_SUBNAME, fmt, ##args);
+#else
+#undef DEBUG
+#define DEBUG(fmt, args...)
+#endif
+
+#define LOG_SUBNAME "default"
+
+//extern int _log_level;
+
+extern int log_init(char *, char*);
+extern void log_write(int, char *, char *, ...);
+extern void log_close(void);
+
+#endif // _LOGGING_H
+

+ 797 - 0
src/masterserver/masterserver.c

@@ -0,0 +1,797 @@
+/* masterserver.c: A generic masterserver for various games. */
+/* Copyright (C) 2003  Andre' Schulz
+ * This file is part of masterserver.
+ *
+ * masterserver is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * masterserver is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with masterserver; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * The author can be contacted at andre@malchen.de
+ */
+/*
+ * vim:sw=4:ts=4
+ */
+
+#include <pthread.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <signal.h>
+#include <errno.h>
+#include <dirent.h> 	// opendir()
+#include <dlfcn.h> 		// for dlopen() etc.
+#include <fcntl.h>
+#include <grp.h> 		// for changing user
+#include <pwd.h> 		// for changing user
+
+#if defined(SOLARIS)
+#include <sys/time.h>
+#include <limits.h>		// PATH_MAX
+#elif defined(__FreeBSD__)
+#include <limits.h>
+#endif
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/select.h> // for select()
+#include <sys/socket.h> // for socket() etc.
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <net/if.h> 	// IFNAMSIZ
+
+#include "masterserver.h" // masterserver stuff
+
+#define MAX_PKT_LEN	1024 // max packet length
+
+#undef LOG_SUBNAME
+#define LOG_SUBNAME "main" // logging subcategory description
+
+// linked list for keeping track of plugins
+// beware! it's a pointer array! :)
+struct masterserver_plugin *plugins = NULL;
+
+
+// function prototypes
+void change_user_and_group_to(char *, char *); // self explanatory
+extern void delete_server(struct masterserver_plugin *, int); // generic function for removing servers from server list
+void exit_printhelp(void); // print help and exit
+void exit_printversion(void); // print version and exit
+int load_plugins(char *, void ***); // load plugins from the given directory
+void plugin_thread(void *); // main thread calling plugin routines
+void plugin_heartbeat_thread(void *); // remove dead servers from server list
+extern void register_plugin(struct masterserver_plugin *); // plugins call this functions to register themselves
+void sigint_handler(int); // SIGINT handler
+
+void
+exit_printhelp(void)
+{
+	fprintf(stdout,
+"Usage: masterserver [options]\n"
+"Options:\n"
+"  -D\t\tgo into daemon mode\n"
+"  -d\t\tdebug mode\n"
+"  -g groupname\tgroup under which masterserver shall run\n"
+"    \t\t(only together with -u)\n"
+"  -h\t\toutput this help text\n"
+"  -i interface\tbind the masterserver to specific interfaces\n"
+"    \t\t(use more than once for multiple interfaces)\n"
+"  -l filename\tlog stdout to a file\n"
+/*"  -L\tset log level\n"
+"    \t0 = INFO\n"
+"    \t1 = WARNING\n"
+"    \t2 = ERROR\n"*/
+"  -p path\tset location of plugins\n"
+"  -u username\tusername under which masterserver shall run\n"
+"  -V\t\tdisplay version information and exit\n"
+"Report bugs to <chickenman@exhale.de>.\n");
+}
+
+void
+exit_printversion(void)
+{
+	fprintf(stdout,
+"Copyright (C) 2003,2004,2005 André Schulz and Ingo Rohlfs\n"
+"masterserver comes with NO WARRANTY,\n"
+"to the extent permitted by law.\n"
+"You may redistribute copies of masterserver\n"
+"under the terms of the GNU General Public License.\n"
+"For more information about these matters,\n"
+"see the files named COPYING.\n\n");
+
+	exit(EXIT_SUCCESS);
+}
+
+void
+change_user_and_group_to(char *user, char *group)
+{
+	int retval = 0;
+	struct passwd *passwd_temp;
+	struct group *group_temp;
+
+	DEBUG("getting uid of user \"%s\"\n", user);
+	// check if user exists and get user infos
+	passwd_temp = getpwnam(user);
+	if (passwd_temp == NULL) {
+		ERRORV("getpwnam() (errno: %d - %s)\n", errno, strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+
+	if (group == NULL)
+		group_temp = getgrgid(passwd_temp->pw_gid);
+	else
+		group_temp = getgrnam(group);
+	if (group_temp == NULL) {
+		ERRORV("getgrgid() (errno: %d - %s)\n", errno, strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+	DEBUG("setting gid to %d (\"%s\")\n",
+		group_temp->gr_gid, group_temp->gr_name);
+	// change uid/gid to drop privileges
+	retval = setgid(group_temp->gr_gid);
+	if (retval == -1) {
+		ERRORV("setgid() (errno: %d - %s)\n", errno, strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+
+	DEBUG("setting uid to %d (\"%s\")\n",
+		passwd_temp->pw_uid, passwd_temp->pw_name);
+	retval = setuid(passwd_temp->pw_uid);
+	if (retval == -1) {
+		ERRORV("setuid() (errno: %d - %s)\n", errno, strerror(errno));
+		exit(EXIT_FAILURE);
+	}
+	INFO("uid/gid change successful\n");
+}
+
+int
+load_plugins(char *masterserver_plugin_dir, void ***handle)
+{
+	int retval = 0;
+	int num_plugins = 0;
+	DIR *plugin_dir; // for opening the plugin dir
+	struct dirent *plugin_dir_entry; 
+	char path[PATH_MAX]; // path to plugin dir
+
+	// open plugin directory
+	DEBUG("opening %s\n", masterserver_plugin_dir);
+	plugin_dir = opendir(masterserver_plugin_dir);
+	if (plugin_dir == NULL) {
+		ERRORV("opendir(%s) (errno: %d - %s)\n", masterserver_plugin_dir, errno, strerror(errno));
+		return -1;
+	}
+
+	// load all plugins in masterserver_plugin_dir
+	while ((plugin_dir_entry = readdir(plugin_dir))) {
+		// omit ., .. and files non-.so suffix
+		if ((strcmp(plugin_dir_entry->d_name, ".") == 0)
+				|| (strcmp(plugin_dir_entry->d_name, "..") == 0)
+				|| (strcmp(plugin_dir_entry->d_name+strlen(plugin_dir_entry->d_name)-3, ".so") != 0))
+			continue;
+
+		snprintf(path,
+			strlen(masterserver_plugin_dir)+plugin_dir_entry->d_reclen+2,
+			"%s/%s", masterserver_plugin_dir, plugin_dir_entry->d_name);
+		DEBUG("path: \"%s\"\n", path);
+
+		// allocate memory for the new handle
+		*handle = realloc(*handle, (num_plugins+1)*sizeof(void*));
+		if (*handle == NULL) {
+			ERRORV("realloc() failed trying to get %d bytes!\n",
+					(num_plugins+1)*sizeof(void *));
+			return -1;
+		}
+		(*handle)[num_plugins] = dlopen(path, RTLD_NOW);
+		if ((*handle)[num_plugins] == NULL)
+		{
+			ERRORV("dlopen (%s)\n", dlerror());
+			return -1;
+		}
+		DEBUG("dlopen() successful (0x%x)\n", (*handle)[num_plugins]);
+		INFO("%s loaded\n", plugin_dir_entry->d_name);
+		num_plugins++;
+	}
+
+	retval = closedir(plugin_dir);
+	if (retval == -1)
+	{
+		ERRORV("closedir(%s) (errno: %d - %s)\n", plugin_dir, errno, strerror(errno));
+		return -1;
+	}
+	DEBUG("closedir succeeded\n");
+
+	return num_plugins;
+}
+
+extern void
+register_plugin(struct masterserver_plugin *me)
+{
+	struct masterserver_plugin **i;
+
+	if (strcmp(me->cversion, masterserver_version) != 0) {
+		WARNING("plugin %s was compiled for masterserver version %s (this is %s)\n", me->name, me->cversion, masterserver_version);
+		WARNING("plugin %s disabled\n", me->name);
+		me->enabled = 0;
+	} else {
+		me->enabled = 1; // plugin is enabled
+	}
+
+	// append to linked list
+	for (i = &plugins; *i; i = &(*i)->next);
+	me->next = NULL;
+	*i = me;
+
+	// initialize plugin structure
+	// me->mutex = PTHREAD_MUTEX_INITIALIZER;
+	pthread_mutex_init(&me->mutex, NULL);
+	me->num_servers = 0;
+	me->list = calloc(1, sizeof(serverlist_t)); // initialize server list
+	if (me->list == NULL) {
+		ERRORV("calloc() failed to get %d bytes!\n", sizeof(serverlist_t));
+		exit(EXIT_FAILURE);
+	}
+	me->num_sockets = 0;
+	me->socket_d = NULL;
+	me->server = calloc(me->num_ports, sizeof(struct sockaddr_in));
+	if (me->server == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n", me->num_ports*sizeof(struct sockaddr_in));
+		exit(EXIT_FAILURE);
+	}
+	me->msg_out = NULL;
+	me->msg_out_length = NULL;
+	me->info(); // display plugin info
+}
+
+int
+main(int argc, char *argv[])
+{
+	// cmdline options
+	int option_logfile = 0;
+	int option_bind_to_interface = 0;
+	int option_daemon = 0;
+	int option_plugin_dir = 0;
+	int option_change_user_and_group = 0;
+	char *user = NULL;
+	char *group = NULL;
+	int i, k, l, num_plugins;
+
+	void **handle = NULL; // for dlopen() calls
+	int retval;	// return value of syscalls
+	unsigned int num_plugins_enabled, num_listen_interfaces = 0;
+	char *logfile; // pointer to argv argument
+	char *masterserver_plugin_dir; // pointer to argv argument
+	char **listen_interface = NULL; // ptr array for storing interface/device names
+	struct masterserver_plugin **j; // temporary variable
+
+	// temporary variables
+	int setsockopt_temp = 1;
+	pid_t temp_pid;
+
+	// seed the rng; needed for challenge creation in q3 plugin
+	srand(time(NULL));
+
+	log_init(NULL, "masterserver");
+	INFO("masterserver v%s\n", masterserver_version);
+
+	// TODO: read config
+
+	// cmdline parser
+	while (1) {
+		//retval = getopt(argc, argv, "?dDhi:l:L:p:V");
+		retval = getopt(argc, argv, "?dDg:hi:l:p:u:V");
+		if (retval == -1) break;
+
+		switch (retval) {
+			// debug
+			case 'd':
+				debug = 1;
+				break;
+			// daemon mode
+			case 'D':
+				option_daemon = 1;
+				break;
+			// run masterserver under a certain group
+			case 'g':
+				group = argv[optind-1];
+				break;
+			// bind to interface
+			case 'i':
+				if (getuid() != 0) {
+					ERRORV("you have to be root to bind to specific interfaces\n");
+					exit(EXIT_FAILURE);
+				}
+				if (strlen(argv[optind-1]) > IFNAMSIZ) {
+					ERRORV("interface/device name is longer than IFNAMSIZ = %d"
+							" chars\n", IFNAMSIZ);
+					exit(EXIT_FAILURE);
+				}
+
+				num_listen_interfaces++;
+				listen_interface = realloc(listen_interface, num_listen_interfaces*sizeof(char *));
+				listen_interface[num_listen_interfaces-1] = argv[optind-1];
+				option_bind_to_interface = 1;
+				break;
+			// log messages to a file
+			case 'l':
+				option_logfile = 1;
+				logfile = argv[optind-1];
+				break;
+			/*case 'L':
+				_log_level = atoi(argv[optind-1]);
+				if ((_log_level < 0) || (_log_level > 2)) {
+					ERROR("log level must be 0 <= x <= 2\n");
+					return -1;
+				}
+				break;*/
+			// plugin path
+			case 'p':
+				masterserver_plugin_dir = argv[optind-1];
+				option_plugin_dir = 1;
+				break;
+			// run masterserver as a certain user
+			case 'u':
+				if (getuid() != 0) {
+					ERRORV("you have to be root to change user/group\n");
+					exit(EXIT_FAILURE);
+				}
+				user = argv[optind-1];
+				option_change_user_and_group = 1;
+				break;
+			// version information
+			case 'V':
+				exit_printversion();
+			// help
+			case 'h':
+			case '?':
+			default:
+				exit_printhelp();
+				return EXIT_FAILURE;
+		} // switch(retval)
+	} // while(1)
+
+	if ((group != NULL) && !option_change_user_and_group) {
+		ERROR("-g can only be used together with -u\n");
+		return EXIT_FAILURE;
+	}
+	// XXX: this is a hack to get multi port working
+	if ((num_listen_interfaces == 0) && !option_bind_to_interface) num_listen_interfaces = 1;
+
+	// check -l cmdline argument
+	if (option_logfile) {
+		INFO("masterserver: logging stdout to %s\n", logfile);
+
+		// initialize log file
+		retval = log_init(logfile, "masterserver");
+		if (retval == -1) {
+			ERROR("log_init()\n");
+			return EXIT_FAILURE;
+		}
+
+		// log stdout to log file
+		/*if (freopen(logfile, "a", stdout) != stdout) {
+			ERRORV("freopen() (errno: %d - %s)\n", errno, strerror(errno));
+			return -1;
+		}*/
+
+		// change buffering to per line so we actually see something in the logfile
+		setvbuf(stdout, NULL, _IOLBF, BUFSIZ);
+	}
+
+	// check -D cmdline argument
+	if (option_daemon) {
+		INFO("masterserver: becoming a daemon ... bye, bye\n");
+		if ( (temp_pid = fork()) < 0) {
+			ERRORV("fork() (errno: %d - %s)\n", errno, strerror(errno));
+			return -1;
+		} else if (temp_pid != 0) {
+			exit(EXIT_SUCCESS);
+		}
+
+		retval = setsid();
+		if (retval == -1) {
+			ERRORV("setsid() (errno: %d - %s)\n", errno, strerror(errno));
+			exit(EXIT_FAILURE);
+		}
+
+		retval = chdir("/");
+		if (retval == -1) {
+			ERRORV("chdir() (errno: %d - %s)\n", errno, strerror(errno));
+			exit(EXIT_FAILURE);
+		}
+
+		umask(0);
+
+		if (option_logfile == 0) {
+			if (freopen("/dev/null", "a", stdout) != stdout) {
+				ERRORV("freopen() (errno: %d - %s)\n", errno, strerror(errno));
+				return EXIT_FAILURE;
+			}
+		}
+	}
+
+	// check if user specified an alternative plugin dir
+	// if he did well we already set it above
+	// else we set the default here
+	if (!option_plugin_dir)
+		masterserver_plugin_dir = MASTERSERVER_LIB_DIR;
+
+	// register signal handler
+	signal(SIGINT, &sigint_handler);
+
+	// load all libs in plugin_dir
+	num_plugins = load_plugins(masterserver_plugin_dir, &handle);
+	if (num_plugins <= 0) {
+		ERRORV("no plugins found in \"%s\"\n", masterserver_plugin_dir);
+		return EXIT_FAILURE;
+	}
+
+	// print out a summary
+	INFO("%d plugins loaded\n", num_plugins);
+
+	// create sockets and bind them
+	// had to be done because threads inherit the original user
+	// and we don't want the threads to be root
+	// TODO: sanity checks (e.g. duplicate ports)
+	// TODO: check for plugin protocol
+	j = &plugins;
+	DEBUG("going to listen on %d interfaces ...\n", num_listen_interfaces);
+	for (i = 0; i < num_plugins; i++) {
+		if (*j == NULL) break;
+		if ((*j)->enabled == 0) {
+			WARNING("plugin nr %d %s disabled\n", i, (*j)->name);
+			continue;
+		}
+	
+		// create socket(s) for plugin
+		for (k = 0; k < (*j)->num_ports; k++) {
+			// fill sockaddr_in structure
+			(*j)->server[k].sin_family = AF_INET;
+			(*j)->server[k].sin_port = htons((*j)->port[k].num); // port number from plugin
+			(*j)->server[k].sin_addr.s_addr = htonl(INADDR_ANY);
+
+			for (l = 0; l < num_listen_interfaces; l++, (*j)->num_sockets++) {
+				(*j)->socket_d = realloc((*j)->socket_d, ((*j)->num_sockets+1)*sizeof(int));
+				if ((*j)->socket_d == NULL) {
+					ERRORV("realloc() failed trying to get %d bytes\n",
+							(*j)->num_sockets+1*sizeof(int));
+					return EXIT_FAILURE;
+				}
+
+				(*j)->socket_d[(*j)->num_sockets] = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
+				DEBUG("plugin #%d %s | socket_d[%d] is %d\n", i, (*j)->name,
+						(*j)->num_sockets, (*j)->socket_d[(*j)->num_sockets]);
+
+				// receive broadcast packets
+				retval = setsockopt((*j)->socket_d[(*j)->num_sockets], SOL_SOCKET,
+						SO_BROADCAST, &setsockopt_temp, sizeof(setsockopt_temp));
+				if (retval == -1) {
+					ERRORV("setsockopt() (errno: %d - %s)\n", errno,
+							strerror(errno));
+					return EXIT_FAILURE;
+				}
+
+				// bind socket to the interfaces specified in -i
+				if (option_bind_to_interface) {
+#ifdef __linux__
+					DEBUG("setsockopt(..., \"%s\", %d+1);\n",
+							listen_interface[l], strlen(listen_interface[l]));
+					retval = setsockopt((*j)->socket_d[(*j)->num_sockets],
+							SOL_SOCKET, SO_BINDTODEVICE, listen_interface[l],
+							strlen(listen_interface[l])+1);
+					if (retval == -1) {
+						ERRORV("setsockopt() (errno: %d - %s)\n", errno,
+								strerror(errno));
+						return EXIT_FAILURE;
+					}
+					DEBUG("%s socket #%d successfully bound to %s\n",
+							(*j)->name, (*j)->num_sockets, listen_interface[l]);
+					INFO("listening on %s UDP port %d\n", listen_interface[l],
+							(*j)->port[k].num);
+#endif					
+				} else INFO("listening on UDP port %d\n", (*j)->port[k].num);
+
+				// bind socket to structure
+				retval = bind((*j)->socket_d[(*j)->num_sockets],
+						(struct sockaddr *) &(*j)->server[k],
+						sizeof(struct sockaddr_in));
+				if (retval == -1) {
+					ERRORV("bind() (errno: %d - %s)\n", errno, strerror(errno));
+					return EXIT_FAILURE;
+				}
+			}
+		}
+		j = &(*j)->next;
+	}
+	DEBUG("sockets successfully created and bound\n");
+
+	if (option_bind_to_interface
+		|| option_change_user_and_group)
+	{
+		change_user_and_group_to(user, group);
+	}
+
+	// main part
+	DEBUG("creating plugin threads...\n");
+	j = &plugins;
+	for (i = 0; i < num_plugins; i++) {
+		if (*j == NULL) break;
+		if ((*j)->enabled == 0) continue;
+
+		// create plugin thread
+		retval = pthread_create(&(*j)->thread_nr, NULL, (void *) plugin_thread, (void *) *j);
+		if (retval != 0) {
+			switch(retval) {
+				case EAGAIN:
+					ERROR("pthread_create returned an error; not enough system"
+						" resources to create a process for the new thread\n");
+					ERRORV("or more than %d threads are already active\n", PTHREAD_THREADS_MAX);
+					return EXIT_FAILURE;
+			}
+		}
+		INFO("created %s plugin thread\n", (*j)->name);
+		j = &(*j)->next; // point j to next plugin in linked list
+	}
+
+	// create heartbeat threads
+	DEBUG("creating heartbeat threads...\n");
+	j = &plugins;
+	for (i = 0; i < num_plugins; i++) {
+		if (*j == NULL) break;
+		if ((*j)->enabled == 0) continue;
+
+		retval = pthread_create(&(*j)->heartbeat_thread_nr, NULL,
+				(void *) plugin_heartbeat_thread, (void *) *j);
+		if (retval != 0) {
+			switch(retval) {
+				case EAGAIN:
+					ERROR("pthread_create returned an error; not enough system"
+						" resources to create a process for the new thread\n");
+					ERRORV("or more than %d threads are already active\n", PTHREAD_THREADS_MAX);
+	                return EXIT_FAILURE;
+			}
+		}
+		INFO("created heartbeat thread for %s\n", (*j)->name);
+		j = &(*j)->next;
+	}
+
+	// admin interface
+	// TODO
+
+	// cleanup and exit
+	// (not really; this is just to stop the parent from eating cpu time)
+	// XXX: paranoid cleanup ?
+	//		check if pointers are != NULL
+	//		destroy mutexes
+	//		free private data
+	INFO("joining plugin threads for graceful cleanup/shutdown... \n");
+	for (j = &plugins; *j; j = &(*j)->next) {
+		DEBUG("joining %s thread (#%ld)\n", (*j)->name, (*j)->thread_nr);
+		retval = pthread_join((*j)->thread_nr, NULL);
+		if (retval != 0) {
+			ERROR("pthread_join()\n");
+			return EXIT_FAILURE;
+		}
+
+		DEBUG("joining %s heartbeat thread (#%ld)\n", (*j)->name, (*j)->heartbeat_thread_nr);
+		retval = pthread_join((*j)->heartbeat_thread_nr, NULL);
+		if (retval != 0) {
+			ERROR("pthread_join()\n");
+			return EXIT_FAILURE;
+		}
+		DEBUG("thread #%ld exited; calling plugin cleanup() function\n", (*j)->heartbeat_thread_nr);
+
+		// free private data
+		// to really free all private data we have to call a cleanup function
+		// of the plugin
+		if ((*j)->cleanup != NULL) (*j)->cleanup();
+
+		// free server list
+		free((*j)->list);
+		for (i = 0; i < (*j)->num_sockets; i++) close((*j)->socket_d[i]);
+		for (i = 0; i < (*j)->num_msgs; i++) free((*j)->msg_out[i]);
+		free((*j)->msg_out);
+		free((*j)->msg_out_length);
+		DEBUG("%s clean up successful\n", (*j)->name);
+	}
+
+	DEBUG("closing dynamic libs ...\n");
+	INFO("unload plugins\n");
+	while (num_plugins-- > 0) {
+		DEBUG("closing dynamic lib %d (0x%x)\n", num_plugins, handle[num_plugins]);
+		dlclose(handle[num_plugins]);
+	}
+	DEBUG("dynamic libs successfully closed\n");
+
+	log_close();
+	return EXIT_SUCCESS;
+}
+
+void
+plugin_thread(void *arg)
+{
+	int retval; // temp var for return values
+	int i, j, packetlen;
+	char msg_in[MAX_PKT_LEN]; // buffer for incoming packet
+	struct masterserver_plugin *me = (struct masterserver_plugin *) arg;
+	unsigned int client_len = sizeof(me->client);
+	int n = 0; // for select()
+	fd_set rfds;
+
+	DEBUG("%s_thread: hello world\n", me->name);
+
+	// initialize msg_in buffer
+	memset(msg_in, 0, MAX_PKT_LEN);
+
+	// main loop
+	while (!master_shutdown) {
+		FD_ZERO(&rfds);
+		for (i = 0; i < me->num_sockets; i++) {
+			if (me->socket_d[i] > n) n = me->socket_d[i];
+			FD_SET(me->socket_d[i], &rfds);
+		}
+		retval = select(n+1, &rfds, NULL, NULL, NULL);
+		if (retval == -1) {
+			if (errno == EINTR) continue;
+			ERRORV("%s_thread: select() (errno: %d - %s)\n", me->name, errno,
+					strerror(errno));
+			pthread_exit((void *) -1);
+		}
+		for (i = 0; i < me->num_sockets; i++) {
+			if (FD_ISSET(me->socket_d[i], &rfds)) {
+				packetlen = recvfrom(me->socket_d[i], &msg_in, MAX_PKT_LEN-1, 0,
+						(struct sockaddr *) &me->client, &client_len);
+				if (packetlen == -1) {
+					ERRORV("%s_thread: recvfrom() (errno: %d - %s)\n", me->name,
+							errno, strerror(errno));
+					ERRORV("%s_thread: socket_d is %d\n", me->name,
+							me->socket_d[i]);
+					ERRORV("%s_thread: MAX_PKT_LEN is %d\n",
+						me->name, MAX_PKT_LEN);
+					pthread_exit((void *) -1);
+				}
+				DEBUG("%d bytes received\n", packetlen);
+
+				DEBUG("locking mutex\n");
+				retval = pthread_mutex_lock(&me->mutex);
+				if (retval != 0) {
+					ERRORV("%s_thread: pthread_mutex_lock() (retval: %d)\n",
+							me->name, retval);
+					pthread_exit((void *) -1);
+				}
+				DEBUG("mutex succesfully locked\n");
+
+				retval = me->process(msg_in, packetlen);
+				if (retval == -2) {
+					ERRORV("%s_thread: plugin reported: out of memory\n", me->name);
+					// TODO: cleanup?
+					pthread_exit((void *) -1);
+				} else if (retval == -1) {
+					//WARNING("%s_thread: plugin reported: invalid packet received\n", me->name);
+				} else if (retval == 0) {
+					//INFO("%s_thread: plugin reported: server successfully added\n", me->name);
+				} else if (retval == 1) {
+					DEBUG("sending %d packets to %s:%u\n",
+							me->num_msgs, inet_ntoa(me->client.sin_addr),
+							ntohs(me->client.sin_port));
+
+					for (j = 0; j < me->num_msgs; j++) {
+						retval = sendto(me->socket_d[i], me->msg_out[j],
+								me->msg_out_length[j], 0,
+								(struct sockaddr *) &me->client, client_len);
+						if (retval == -1) {
+							ERRORV("sendto() (errno: %d - %s)\n",
+									errno, strerror(errno));
+						} else DEBUG("%d bytes sent\n", retval);
+					}
+				} else if (retval == 2) {
+					// INFO("%s_thread: plugin reported: server deleted\n", me->name);
+				}
+
+				// clean up
+				memset(msg_in, 0, MAX_PKT_LEN);
+				if (me->num_msgs > 0) {
+					DEBUG("freeing outgoing packets\n");
+					for (j = 0; j < me->num_msgs; j++)
+						free(me->msg_out[j]);
+					me->num_msgs = 0;
+					free(me->msg_out);
+					free(me->msg_out_length);
+					me->msg_out = NULL;
+					me->msg_out_length = NULL;
+				}
+
+				DEBUG("unlocking mutex\n");
+				retval = pthread_mutex_unlock(&me->mutex);
+				if (retval != 0) {
+					ERROR("pthread_mutex_unlock()\n");
+					pthread_exit((void *) -1);
+				}
+			} // if(FD_ISSET())
+		} // for(i)
+	} // while()
+}
+
+void
+plugin_heartbeat_thread(void *arg)
+{
+	struct masterserver_plugin *me = (struct masterserver_plugin *) arg;
+	int i = 0;
+	int heartbeat_diff = 0;
+	int retval; // temp var for return values
+
+	DEBUG("%s_heartbeat_thread: hello world\n", me->name);
+
+	// main loop
+	while (!master_shutdown) {
+		DEBUG("sleeping %d seconds ...\n", me->heartbeat_timeout);
+		sleep(me->heartbeat_timeout);
+		DEBUG("waking up\n");
+
+		DEBUG("locking plugin mutex\n");
+		retval = pthread_mutex_lock(&me->mutex);
+		if (retval != 0) {
+			ERROR("pthread_mutex_lock()\n");
+			pthread_exit((void *) -1);
+		}
+
+		for (i = 0; i < me->num_servers; i++) {
+			heartbeat_diff = time(NULL) - me->list[i].lastheartbeat;
+			if (heartbeat_diff > 300) {
+				INFO("%s_heartbeat_thread: server %s:%d died (heartbeat_diff %d)\n",
+					me->name, inet_ntoa(me->list[i].ip), ntohs(me->list[i].port), heartbeat_diff);
+				delete_server(me, i);
+				i--;
+			} else {
+				DEBUG("server %s:%d is alive (heartbeat_diff %d)\n",
+						inet_ntoa(me->list[i].ip), ntohs(me->list[i].port),
+						heartbeat_diff);
+			}
+		}
+
+		DEBUG("unlocking mutex\n");
+		retval = pthread_mutex_unlock(&me->mutex);
+		if (retval != 0) {
+			ERROR("pthread_mutex_unlock\n");
+			pthread_exit((void *) -1);
+		}
+	} // while()
+}
+
+extern void
+delete_server(struct masterserver_plugin *me, int server_num)
+{
+	if (me->free_privdata != NULL) me->free_privdata(me->list[server_num].private_data);
+	me->num_servers--;
+	me->list[server_num] = me->list[me->num_servers];
+
+	DEBUG("reallocating server list (old size: %d -> new size: %d)\n",
+			(me->num_servers+2)*sizeof(serverlist_t),
+			(me->num_servers+1)*sizeof(serverlist_t));
+	me->list = (serverlist_t *) realloc(me->list, (me->num_servers+1)*sizeof(serverlist_t));
+	if (me->list == NULL) {
+		ERROR("(__)\n");
+		ERROR(" °°\\\\\\~\n");
+		ERROR("  !!!!\n");
+		pthread_exit((void *) -1);
+	}
+	DEBUG("reallocation successful\n");
+}
+
+void
+sigint_handler(int signum)
+{
+	DEBUG("caught SIGINT!\n");
+	master_shutdown = 1;
+}
+

+ 65 - 0
src/masterserver/masterserver.h

@@ -0,0 +1,65 @@
+#ifndef _MASTERSERVER_H
+#define _MASTERSERVER_H
+
+#include "logging.h"
+
+#ifndef MASTERSERVER_LIB_DIR
+#define MASTERSERVER_LIB_DIR "/usr/lib/lasange/masterserver"
+#endif
+
+int debug = 0; // global debug var
+int master_shutdown = 0; // to signal graceful shutdown (by sigint handler)
+char masterserver_version[] = "0.4.1";
+
+// XXX: merge struct in_addr and in_port_t to struct sockaddr_in ?
+typedef struct {
+	struct in_addr ip; // ip adress
+	in_port_t port; // port
+	int lastheartbeat; // timestamp
+	void *private_data; // data specific to plugin
+} serverlist_t;
+
+typedef struct {
+	int protocol;
+	in_port_t num;
+} port_t;
+
+// struct for plugins
+struct masterserver_plugin {
+	// the following has to be filled by the plugin
+	const char name[32]; // plugin name
+	const char *pversion; // plugin version
+	const char *cversion; // which masterserver version was this compiled against
+	port_t *port; // port(s) to listen on
+	int num_ports;
+	int heartbeat_timeout; // server heartbeat timeout in seconds
+
+	void (*info) (void); // show info about plugin
+	int (*process) (char *, int); // process a packet
+	void (*free_privdata) (void *);	// free private data
+	void (*cleanup) (void);	// free private data
+	// end plugin fill section
+
+	struct masterserver_plugin *next; // next plugin in linked list
+	pthread_mutex_t mutex; // mutex for this struct
+	unsigned int num_servers; // current number of servers in list
+	serverlist_t *list; // pointer to serverlist
+	char **msg_out; // array of packets to send to client
+	int *msg_out_length; // lengths of outgoing packets
+	int num_msgs; // how many packets are in msg_out array
+	unsigned int enabled; // plugin enabled?
+	int *socket_d; // socket(s)
+	int num_sockets;
+	struct sockaddr_in client; // client struct
+	struct sockaddr_in *server; // server struct
+	pthread_t thread_nr; // thread id
+	pthread_t heartbeat_thread_nr; // heartbeat thread id
+};
+
+// plugins call the following function
+extern void register_plugin(struct masterserver_plugin *);
+// generic function for deleting servers in plugin server list
+extern void delete_server(struct masterserver_plugin *, int);
+
+#endif // _MASTERSERVER_H
+

+ 31 - 0
src/masterserver/plugins/Makefile

@@ -0,0 +1,31 @@
+include ../common.mk
+
+PLUGINS = libq3.c libq2.c libh2.c libqw.c libd3.c libef.c
+OBJS = libq3.o libq2.o libh2.o libqw.o libd3.o libef.o
+LIBS = libq3.so libq2.so libh2.so libqw.so libd3.so libef.so
+
+.PHONY: all clean install
+
+all:	$(LIBS)
+
+%.o:	%.c
+	$(CC) $(CFLAGS_PLUGIN) -o $@ -c $<
+
+%.so:	%.o
+	$(CC) $(LDFLAGS_PLUGIN) -o $@ $<
+
+clean:
+	$(RM) $(OBJS) $(LIBS)
+
+install:
+	$(INSTALL) -m 755 -d $(libdir)/lasange/masterserver
+	$(INSTALLDATA) $(LIBS) $(libdir)/lasange/masterserver
+
+uninstall:
+	$(RM) $(libdir)/lasange/masterserver/libq3.so
+	$(RM) $(libdir)/lasange/masterserver/libh2.so
+	$(RM) $(libdir)/lasange/masterserver/libq2.so
+	$(RM) $(libdir)/lasange/masterserver/libqw.so
+	$(RM) $(libdir)/lasange/masterserver/libd3.so
+	$(RM) $(libdir)/lasange/masterserver/libef.so
+

+ 599 - 0
src/masterserver/plugins/libd3.c

@@ -0,0 +1,599 @@
+/* libd3.c: masterserver plugin for Doom 3 servers. */
+/* Copyright (C) 2003  Andre' Schulz
+ * This file is part of masterserver.
+ *
+ * masterserver is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * masterserver is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with masterserver; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * The author can be contacted at chickenman@exhale.de
+ */
+/*
+ * vim:sw=4:ts=4
+ */
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <pthread.h>
+#include <sys/socket.h> // for socket() etc.
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <math.h>
+
+#include "../masterserver.h"
+
+#define HEARTBEAT_TIMEOUT 300
+
+// message of the day
+#define D3M_MOTD "Insert MOTD here."
+
+// for logging stuff
+#undef LOG_SUBNAME
+#define LOG_SUBNAME "libd3" // logging subcategory description
+
+// d3 packet stuff
+const char	d3_pkt_header[]		= "\xff\xff";
+const int	d3_pkt_header_len	= 2;
+const char	d3_pkt_heartbeat[]	= "heartbeat\0";
+const int	d3_pkt_heartbeat_len= 10;
+const char	d3_pkt_getinfo[]	= "getInfo\0\0\0\0\0";
+const int	d3_pkt_getinfo_len	= 12;
+const char	d3_pkt_inforsp[]	= "infoResponse";
+const int	d3_pkt_inforsp_len	= 12;
+const char	d3_pkt_getsrv[]		= "getServers";
+const int	d3_pkt_getsrv_len	= 10;
+const char	d3_pkt_getsrvrsp[]	= "servers\0";
+const int	d3_pkt_getsrvrsp_len= 8;
+const char	d3_pkt_verchk[]		= "versionCheck";
+const int	d3_pkt_verchk_len	= 12;
+const char	d3_pkt_srvauth[]	= "srvAuth";
+const int	d3_pkt_srvauth_len	= 7;
+const char	d3_pkt_delimiter[]	= "\0";
+
+const char d3m_plugin_version[] = "0.1.1";
+static port_t d3m_ports[] = { { IPPROTO_UDP, 27650 } };
+
+// player info
+// FIXME
+typedef struct {
+	int id;
+	int ping;
+	int rate;
+	char *name;
+} d3m_player_data_t;
+
+// d3 plugin private data
+typedef struct {
+	int challenge;
+	//int sv_punkbuster;	// 0 | 1
+	char *fs_game;
+	int si_maxPlayers; // max num of clients
+	int si_timeLimit;
+	int si_fragLimit;
+	char *si_name; // server name
+	char *si_version; // self explanatory
+	char *si_gameType;	// deathmatch
+	int protocol;	// d3 network protocol version
+	char *si_map;	// self explanatory
+	char *gamename;	// which mod
+	int si_usepass;	// 0 | 1
+	int si_warmup;
+	int si_spectators;
+	int si_teamDamage;
+	int si_pure;
+	int si_idleServer;
+	int net_serverDedicated;
+	int si_maxplayers;
+
+	d3m_player_data_t *_player; // player info
+	// following is information not in packet
+	int _players; // # of players
+	int _challenge; // our challenge #
+} d3m_private_data_t;
+
+static void	info(void); // print information about plugin
+static void	free_privdata(void *);
+static int	process(char *, int); // process packet and return a value
+static int	process_heartbeat(char *);
+static int	process_getServers(char *);
+static int	send_getInfo();
+static int	process_infoResponse(char *, int);
+static void	cleanup(void);
+void		init_plugin(void) __attribute__ ((constructor)); 
+
+static
+struct masterserver_plugin d3m
+= { "d3m",
+	d3m_plugin_version,
+	masterserver_version,
+	d3m_ports,
+	1,
+	HEARTBEAT_TIMEOUT,
+	&info,
+	&process,
+	//&free_privdata,
+	NULL,
+	&cleanup
+};
+
+static void
+info(void)
+{
+	INFO("Doom 3 masterserver plugin v%s\n", d3m_plugin_version);
+	INFO("  compiled for masterserver v%s\n", masterserver_version);
+}
+
+static void
+free_privdata(void *data)
+{
+	//int i;
+	d3m_private_data_t *privdata = (d3m_private_data_t *) data;
+
+	if (data == NULL) return;
+
+	free(privdata->fs_game);
+	free(privdata->si_name);
+	free(privdata->si_version);
+	free(privdata->si_gameType);
+	free(privdata->si_map);
+	free(privdata->gamename);
+	// FIXME
+	/*for (i = 0; i < privdata->_players; i++)
+	 *	free(privdata->_player[i].name);
+	 *free(privdata->_player);
+	 */
+	// end FIXME
+	free(privdata);
+}
+
+static int
+process_heartbeat(char *packet)
+{
+	int server_dup = 0;
+	int time_diff, i;
+	serverlist_t *backup_ptr;
+
+	// first, check if server is already in our list
+	for (i = 0; i < d3m.num_servers; i++) {
+		if ((d3m.list[i].ip.s_addr == d3m.client.sin_addr.s_addr)
+				&& (d3m.list[i].port == d3m.client.sin_port)) {
+			DEBUG("duplicate server detected! (%s:%d)\n",
+					inet_ntoa(d3m.client.sin_addr), ntohs(d3m.client.sin_port));
+			server_dup = 1;
+			break;
+		}
+	}
+
+	INFO("heartbeat from %s:%d\n",
+			inet_ntoa(d3m.client.sin_addr), ntohs(d3m.client.sin_port));
+	// if not, then add it to the list
+	if (server_dup == 0) {
+		// server is not in our list so add its ip, port and a timestamp
+		d3m.list[d3m.num_servers].ip = d3m.client.sin_addr;
+		d3m.list[d3m.num_servers].port = d3m.client.sin_port;
+		d3m.list[d3m.num_servers].lastheartbeat = time(NULL);
+		DEBUG("this is server no.: %d | lastheartbeat: %d\n",
+				d3m.num_servers, d3m.list[d3m.num_servers].lastheartbeat);
+		// allocate memory for private data
+		d3m.list[d3m.num_servers].private_data = calloc(1, sizeof(d3m_private_data_t));
+
+		d3m.num_servers++;
+
+		DEBUG("reallocating server list (old size: %d -> new size: %d)\n",
+				d3m.num_servers * sizeof(serverlist_t),
+				(d3m.num_servers+1) * sizeof(serverlist_t));
+
+		// back up the current list pointer in case realloc() fails
+		backup_ptr = d3m.list;
+		d3m.list = (serverlist_t *) realloc(d3m.list, ((d3m.num_servers+1)*sizeof(serverlist_t)));
+		if (d3m.list == NULL) {
+			WARNING("realloc() failed trying to get %d bytes!\n",
+					(d3m.num_servers+1)*sizeof(serverlist_t));
+			// since the pointer is overwritten with NULL
+			// we'll recover by using the backup pointer
+			d3m.list = backup_ptr;
+			return -2;
+		} else DEBUG("reallocation successful\n");
+	} else {
+		time_diff = time(NULL) - d3m.list[i].lastheartbeat;
+		// if time_diff is 0 the server has shutdown (most likely)
+		if (time_diff == 0) {
+			INFO("server %s:%u is shutting down (time_diff %d)\n",
+					inet_ntoa(d3m.list[i].ip), ntohs(d3m.list[i].port),
+					time_diff);
+			delete_server(&d3m, i);
+			server_dup = 0;
+			return 2; // return "server-shutdown" code
+		} else {
+			// server is in already in our list so we just update the timestamp
+			d3m.list[i].lastheartbeat = time(NULL);
+			server_dup = 0;
+		}
+	}
+	// server added/updated
+	return 1;
+}
+
+static int
+send_getInfo()
+{
+	// prepare d3m.msg_out
+	d3m.num_msgs = 1;
+	d3m.msg_out_length = calloc(1, sizeof(int));
+	if (d3m.msg_out_length == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n", sizeof(int));
+		return -2; // TODO: define retval for errors
+	}
+	DEBUG("allocated %d bytes for msg_out_length[]\n", sizeof(int));
+
+	// d3m.msg_out_length[0] = d3_pkt_header_len + d3_pkt_getstatus_len;
+	d3m.msg_out_length[0] = d3_pkt_header_len + d3_pkt_getinfo_len;
+
+	// allocate the memory for the outgoing packet
+	d3m.msg_out = calloc(1, sizeof(char *));
+	if (d3m.msg_out == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n", sizeof(char *));
+		return -2; // TODO: define retval for errors
+	}
+
+	d3m.msg_out[0] = calloc(d3m.msg_out_length[0]+1, 1);
+	if (d3m.msg_out[0] == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n",
+				d3m.msg_out_length[0]);
+		return -2; // TODO: define retval for errors
+	}
+	DEBUG("allocated %d bytes for msg_out[0]\n", d3m.msg_out_length[0]);
+
+	memcpy(d3m.msg_out[0], d3_pkt_header, d3_pkt_header_len);
+	memcpy(d3m.msg_out[0]+d3_pkt_header_len, d3_pkt_getinfo, d3_pkt_getinfo_len);
+
+	return 1; // send "getInfo" packet
+}
+
+static int
+process_getServers(char *packet)
+{
+	int i, pkt_offset, tmp_port; // temp vars
+	int getsrv_protocol;
+	char getsrv_filter;
+	d3m_private_data_t *temp_priv_data;
+	char **backup_ptr; // for recovery from realloc() failure
+
+	INFO("getServers from %s:%u\n",
+			inet_ntoa(d3m.client.sin_addr), ntohs(d3m.client.sin_port));
+
+	// we need the protocol version from the packet so we parse it
+	memcpy(&getsrv_protocol, packet+d3_pkt_header_len+d3_pkt_getsrv_len+1, 4);
+	DEBUG("requested protocol is %d.%d (0x%08x)\n",
+		getsrv_protocol >> 16, getsrv_protocol & 0xffff, getsrv_protocol);
+
+	// parse the filter byte
+	getsrv_filter = *(packet+d3_pkt_header_len+d3_pkt_getsrv_len+1+4+1);
+	DEBUG("requested filter is 0x%02x\n", getsrv_filter);
+
+	// got the protocol version now we can assemble the outgoing packet(s)
+	DEBUG("assembling server list packet\n");
+
+	d3m.num_msgs = 1;
+
+	// allocate memory for the packets
+	d3m.msg_out = malloc(sizeof(char*));
+	if (d3m.msg_out == NULL) {
+		ERRORV("malloc() failed trying to get %d bytes!\n", sizeof(char*));
+		return -2;
+	}
+	d3m.msg_out[0] = calloc(1, d3_pkt_header_len+d3_pkt_getsrvrsp_len+1);
+	if (d3m.msg_out[0] == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n",
+			d3_pkt_header_len+d3_pkt_getsrvrsp_len+(d3m.num_servers*6));
+		return -2;
+	}
+
+	d3m.msg_out_length = malloc(sizeof(int));
+	if (d3m.msg_out_length == NULL) {
+		ERRORV("malloc() failed trying to get %d bytes!\n", sizeof(int));
+		return -2;
+	}
+	d3m.msg_out_length[0] = d3_pkt_header_len+d3_pkt_getsrvrsp_len+1;
+
+	// write header and command into packet
+	memcpy(d3m.msg_out[0], d3_pkt_header, d3_pkt_header_len);
+	pkt_offset = d3_pkt_header_len;
+	memcpy(d3m.msg_out[0]+pkt_offset, d3_pkt_getsrvrsp, d3_pkt_getsrvrsp_len);
+	pkt_offset += d3_pkt_getsrvrsp_len;
+	// walk the server list
+	for (i = 0; i < d3m.num_servers; i++) {
+		temp_priv_data = (d3m_private_data_t *) d3m.list[i].private_data;
+		// if the protocol matches, write ip/port into the packet
+		if (temp_priv_data->protocol == getsrv_protocol) {
+			backup_ptr = d3m.msg_out;
+			d3m.msg_out[0] = realloc(d3m.msg_out[0], d3m.msg_out_length[0]+6);
+			if (d3m.msg_out[0] == NULL) {
+				ERRORV("realloc() failed trying to get %d bytes!\n",
+					d3m.msg_out_length);
+				WARNING("recovering ...\n");
+				d3m.msg_out = backup_ptr;
+				return 1;
+			}
+			// copy data from server list into packet
+			memcpy(d3m.msg_out[0]+pkt_offset, &d3m.list[i].ip, 4);
+			pkt_offset += 4;
+			tmp_port = htons(d3m.list[i].port);
+			memcpy(d3m.msg_out[0]+pkt_offset, &tmp_port, 2);
+			pkt_offset += 2;
+
+			d3m.msg_out_length[0] += 6;
+		}
+
+	}
+
+	d3m.msg_out[0][pkt_offset] = '\0';
+	// XXX: fugly hack
+	d3m.msg_out_length[0]--;
+	DEBUG("d3m.msg_out_length[0] = %d (%d)\n", pkt_offset, d3m.msg_out_length[0]);
+
+	// packet with server list is ready
+	return 1;
+}
+
+static int
+process_infoResponse(char *packet, int packetlen)
+{
+	char *varname = NULL, *value = NULL, *packetend = packet+packetlen;
+	char *name = NULL;
+	int offset = 0, temp_size, i;
+	int server_dup = 0;
+	short prediction;
+	unsigned int rate;
+	unsigned char player_id;
+	d3m_private_data_t *private_data;
+	d3m_private_data_t *oldprivdata;
+
+	// check if source address is known
+	for (i = 0; i < d3m.num_servers; i++) {
+		if ((d3m.client.sin_addr.s_addr == d3m.list[i].ip.s_addr)
+				&& (d3m.client.sin_port == d3m.list[i].port)) {
+			server_dup = 1;
+			break;
+		}
+	}
+
+	// source address unknown
+	if (server_dup == 0) {
+		WARNING("unexpected \"infoResponse\" from %s:%d ignored\n",
+				inet_ntoa(d3m.client.sin_addr), ntohs(d3m.client.sin_port));
+		return -1;
+	}
+
+	oldprivdata = (d3m_private_data_t *)d3m.list[i].private_data;
+
+	// allocate memory for private data
+	private_data = calloc(1, sizeof(d3m_private_data_t));
+	if (private_data == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n",
+			sizeof(d3m_private_data_t));
+		return -2;
+	}
+
+	// parse challenge and version
+	packet += d3_pkt_header_len+d3_pkt_inforsp_len+1;
+	memcpy(&private_data->challenge, packet, 4);
+	packet += 4;
+	memcpy(&private_data->protocol, packet, 4);
+
+	DEBUG("challenge is %d\n", private_data->challenge);
+	DEBUG("protocol is %d.%d (0x%x)\n",
+		private_data->protocol >> 16, private_data->protocol & 0xffff,
+		private_data->protocol);
+	packet += 4;
+
+	// begin parsing the server info
+	DEBUG("begin parsing server info\n");
+	while (packet < packetend)
+	{
+		varname = packet;
+		value = strchr(packet, 0)+1;
+		packet = strchr(value, 0)+1;
+
+		// check if we're at the end of varname/value pairs
+		if ((*value == '\0') && (*varname == '\0'))
+			break;
+
+		DEBUG("%s = %s\n", varname, value);
+
+		// parse varname and assign the value to the struct
+		if (strcmp(varname, "fs_game") == 0) {
+			private_data->fs_game = strdup(value);
+			if (private_data->fs_game == NULL) {
+				ERRORV("strdup() failed to get %d bytes!\n", strlen(value)+1);
+				free_privdata(private_data);
+				return -2;
+			}
+		} else if (strcmp(varname, "si_maxPlayers") == 0) {
+			private_data->si_maxPlayers = atoi(value);
+		} else if (strcmp(varname, "si_timeLimit") == 0) {
+			private_data->si_timeLimit = atoi(value);
+		} else if (strcmp(varname, "si_fragLimit") == 0) {
+			private_data->si_fragLimit = atoi(value);
+		} else if (strcmp(varname, "si_name") == 0) {
+			private_data->si_name = strdup(value);
+			if (private_data->si_name == NULL) {
+				ERRORV("strdup() failed to get %d bytes!\n", strlen(value)+1);
+				free_privdata(private_data);
+				return -2;
+			}
+		} else if (strcmp(varname, "si_version") == 0) {
+			private_data->si_version = strdup(value);
+			if (private_data->si_version == NULL) {
+				ERRORV("strdup() failed to get %d bytes!\n", strlen(value)+1);
+				free_privdata(private_data);
+				return -2;
+			}
+		} else if (strcmp(varname, "si_gameType") == 0) {
+			private_data->si_gameType = strdup(value);
+			if (private_data->si_gameType == NULL) {
+				ERRORV("strdup() failed to get %d bytes!\n", strlen(value)+1);
+				free_privdata(private_data);
+				return -2;
+			}
+		} else if (strcmp(varname, "si_map") == 0) {
+			private_data->si_map = strdup(value);
+			if (private_data->si_map == NULL) {
+				ERRORV("strdup() failed to get %d bytes!\n", strlen(value)+1);
+				free_privdata(private_data);
+				return -2;
+			}
+		} else if (strcmp(varname, "gamename") == 0) {
+			private_data->gamename = strdup(value);
+			if (private_data->gamename == NULL) {
+				ERRORV("strdup() failed to get %d bytes!\n", strlen(value)+1);
+				free_privdata(private_data);
+				return -2;
+			}
+		} else if (strcmp(varname, "si_usepass") == 0) {
+			private_data->si_usepass = atoi(value);
+		} else if (strcmp(varname, "si_warmup") == 0) {
+			private_data->si_warmup = atoi(value);
+		} else if (strcmp(varname, "si_spectators") == 0) {
+			private_data->si_spectators = atoi(value);
+		} else if (strcmp(varname, "si_teamDamage") == 0) {
+			private_data->si_teamDamage = atoi(value);
+		} else if (strcmp(varname, "si_pure") == 0) {
+			private_data->si_pure = atoi(value);
+		} else if (strcmp(varname, "si_idleServer") == 0) {
+			private_data->si_idleServer = atoi(value);
+		} else if (strcmp(varname, "net_serverDedicated") == 0) {
+			private_data->net_serverDedicated = atoi(value);
+		} else if (strcmp(varname, "si_maxplayers") == 0) {
+			private_data->si_maxplayers = atoi(value);
+		} else {
+			WARNING("unknown option \"%s\" in statusResponse ignored\n", varname);
+		}
+	}
+	DEBUG("end parsing server info\n");
+
+	DEBUG("skipping player info\n");
+	// parse player info
+	// TODO
+#if 0
+	private_data->_players = 0;
+	for (	player_id = *packet++;
+			packet < packetend;
+			player_id = *packet++)
+	{
+		if (player_id == 32) break;
+
+		if (packet+7 > packetend) {
+			WARNING("invalid infoResponse packet: player info too short\n");
+			return -2;
+		}
+
+		memcpy(&prediction, packet, 2);
+		packet += 2;
+
+		memcpy(&rate, packet, 4);
+		packet += 4;
+
+		name = packet;
+		if ((packet = memchr(packet, 0, packetend-packet)) == 0) {
+			WARNING("invalid infoResponse packet: player name not null terminated\n");
+			return -2;
+		}
+		packet++;
+
+		// FIXME: recover from realloc() failure
+		private_data->_player = (d3m_player_data_t *) realloc(private_data->_player,
+				private_data->_players*sizeof(d3m_player_data_t));
+		if (private_data->_player == NULL) {
+			ERRORV("realloc() failed trying to get %d bytes!\n",
+					private_data->_players*sizeof(d3m_player_data_t));
+			return -2;
+		}
+		private_data->_player[private_data->_players].ping = prediction;
+		private_data->_player[private_data->_players].name = strdup(name);
+		DEBUG("name: \"%s\" ping: %d\n", prediction, name);
+		private_data->_players++;
+	}
+#endif
+	// TODO: osmask
+
+	// compare challenge to ours
+	if (private_data->challenge != oldprivdata->_challenge) {
+		WARNING("statusResponse challenge mismatch (%d != %d)\n",
+				private_data->challenge, oldprivdata->_challenge);
+		free_privdata(private_data);
+		return -1;
+	}
+
+	// if we already have parsed server/player info we have to free it first
+	if (d3m.list[i].private_data != NULL) free_privdata(d3m.list[i].private_data);
+	d3m.list[i].private_data = private_data;
+
+	return 0;
+}
+
+static int
+process(char *packet, int packetlen)
+{
+	int retval;
+
+	// check if packet is Doom 3 related
+	if (strncmp(packet, d3_pkt_header, d3_pkt_header_len) == 0) {
+		DEBUG("Doom 3 protocol marker detected!\n");
+		// which packet did we receive?
+		if (strcmp(packet+d3_pkt_header_len, d3_pkt_heartbeat) == 0) {
+			retval = process_heartbeat(packet);
+			if (retval == 1) {
+				send_getInfo();
+				return 1; // "send packet" code
+			}
+			return retval;
+		} else if (strncmp(packet+d3_pkt_header_len, d3_pkt_getsrv, d3_pkt_getsrv_len) == 0) {
+			return process_getServers(packet);
+		} else if (strncmp(packet+d3_pkt_header_len, d3_pkt_inforsp, d3_pkt_inforsp_len) == 0) {
+			return process_infoResponse(packet, packetlen);
+		} else if (strncmp(packet+d3_pkt_header_len, d3_pkt_verchk, d3_pkt_verchk_len) == 0) {
+			DEBUG("STUB: process_versionCheck()\n");
+			//return process_versionCheck(packet, packetlen);
+		} else if (strncmp(packet+d3_pkt_header_len, d3_pkt_srvauth, d3_pkt_srvauth_len) == 0) {
+			DEBUG("STUB: process_srvAuth()\n");
+			//return process_srvAuth(packet, packetlen);
+		}
+		WARNING("unknown packet received!\n");
+		return -1;
+	} // end if for 0xffff marker
+	WARNING("invalid packet received from %s:%d: Doom 3 protocol marker missing!\n", inet_ntoa(d3m.client.sin_addr), ntohs(d3m.client.sin_port));
+	return -1; // invalid packet
+}
+
+static void
+cleanup(void)
+{
+	int i;
+
+	if (d3m.num_servers > 0) {
+		for (i = 0; i < d3m.num_servers; i++) {
+			free_privdata(d3m.list[i].private_data);
+		}
+	}
+}
+
+void
+init_plugin(void)
+{
+	register_plugin(&d3m);
+}
+

+ 874 - 0
src/masterserver/plugins/libef.c

@@ -0,0 +1,874 @@
+/* libef.c: masterserver plugin for Elite Force servers. */
+/* Copyright (C) 2003  Andre' Schulz
+ * This file is part of masterserver.
+ *
+ * masterserver is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * masterserver is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with masterserver; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * The author can be contacted at chickenman@exhale.de
+ */
+/*
+ * vim:sw=4:ts=4
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <pthread.h>
+#include <sys/socket.h> // for socket() etc.
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <math.h>
+
+#include "../masterserver.h"
+
+#define HEARTBEAT_TIMEOUT 300
+
+// message of the day
+#define EFM_MOTD "Insert MOTD here."
+
+// for logging stuff
+#undef LOG_SUBNAME
+#define LOG_SUBNAME "libef" // logging subcategory description
+
+// ef packet stuff
+const char	ef_pkt_header[]		= "\xff\xff\xff\xff";
+const int	ef_pkt_header_len	= 4;
+const char	ef_pkt_heartbeat[]	= "\\heartbeat\\";
+const int	ef_pkt_heartbeat_len= 11;
+const char	ef_pkt_heartstop[]	= "\\heartstop\\";
+const int	ef_pkt_heartstop_len= 11;
+const char	ef_pkt_getinfo[]	= "getinfo\n";
+const int	ef_pkt_getinfo_len	= 8;
+const char	ef_pkt_inforsp[]	= "infoResponse\n";
+const int	ef_pkt_inforsp_len	= 13;
+const char	ef_pkt_getstatus[]	= "getstatus ";
+const int	ef_pkt_getstatus_len= 10;
+const char	ef_pkt_statusrsp[]	= "statusResponse\n";
+const int	ef_pkt_statusrsp_len= 15;
+const char	ef_pkt_getsrv[]		= "getservers";
+const int	ef_pkt_getsrv_len	= 10;
+const char	ef_pkt_getsrvrsp[]	= "getserversResponse ";
+const int	ef_pkt_getsrvrsp_len= 19;
+const char	ef_pkt_getmotd[]	= "getmotd";
+const int	ef_pkt_getmotd_len	= 7;
+const char	ef_pkt_motd[]		= "\xff\xff\xff\xffmotd \"\\challenge\\%d\\motd\\%s\"";
+const int	ef_pkt_motd_len		= 28;
+const char	ef_pkt_footer[]		= "\\EOT";
+const int	ef_pkt_footer_len	= 4;
+const char	ef_pkt_getkeyauth[]	= "getKeyAuthorize";
+const int	ef_pkt_getkeyauth_len= 15;
+
+const char efm_plugin_version[] = "0.1";
+static port_t efm_ports[] = {	{ IPPROTO_UDP, 27953 }, // master
+								{ IPPROTO_UDP, 27951 }, // motd
+								// { IPPROTO_UDP, 27952 }  // auth
+							};
+
+// player info
+typedef struct {
+	int score;
+	int ping;
+	char *name;
+} efm_player_data_t;
+
+// ef plugin private data
+typedef struct {
+	// statusResponse vars
+	int challenge;
+	int g_needpass;
+	int capturelimit;
+	int g_maxGameClients;
+	char *gamename;
+	int bot_minplayers;
+	int sv_allowDownload;
+	int sv_pure;
+	int sv_floodProtect;
+	int sv_maxPing;
+	int sv_minPing;
+	int sv_maxRate;
+	int sv_maxclients;
+	char *sv_hostname;
+	int sv_privateClients;
+	char *mapname;
+	int protocol;
+	int g_pModElimination;
+	int g_pModActionHero;
+	int g_pModDisintegration;
+	int g_pModAssimilation;
+	int g_pModSpecialties;
+	int g_gametype;
+	int timelimit;
+	int fraglimit;
+	int dmflags;
+	char *version;
+
+	// following is information not in packet
+	efm_player_data_t *_player; // player info
+	int _players; // # of players
+	int _challenge; // our challenge #
+} efm_private_data_t;
+
+static void	info(void); // print information about plugin
+static void	free_privdata(void *);
+static int	process(char *, int); // process packet and return a value
+static int	process_getmotd(char *, int);
+static int	process_getservers(char *);
+static int	process_heartbeat(char *);
+static int	process_heartstop(char *);
+static int	send_getstatus();
+static int	process_statusResponse(char *, int);
+static void	cleanup(void);
+void		init_plugin(void) __attribute__ ((constructor)); 
+
+static
+struct masterserver_plugin efm
+= { "efm",
+	efm_plugin_version,
+	masterserver_version,
+	efm_ports,
+	2,
+	HEARTBEAT_TIMEOUT,
+	&info,
+	&process,
+	&free_privdata,
+	&cleanup
+};
+
+static void
+info(void)
+{
+	INFO("Elite Force masterserver plugin v%s\n", efm_plugin_version);
+	INFO("  compiled for masterserver v%s\n", masterserver_version);
+}
+
+static void
+free_privdata(void *data)
+{
+    int i;
+	efm_private_data_t *privdata = (efm_private_data_t *) data;
+
+	if (data == NULL) return;
+
+    free(privdata->gamename);
+    free(privdata->sv_hostname);
+    free(privdata->mapname);
+    free(privdata->version);
+    for (i = 0; i < privdata->_players; i++)
+        free(privdata->_player[i].name);
+    free(privdata->_player);
+	free(privdata);
+}
+
+static int
+process_heartbeat(char *packet)
+{
+	int server_dup = 0;
+	int time_diff, i;
+	serverlist_t *backup_ptr;
+	char *ptr;
+	char *gamename;
+	int heartbeat;
+
+	// first, check if server is already in our list
+	for (i = 0; i < efm.num_servers; i++) {
+		if ((efm.list[i].ip.s_addr == efm.client.sin_addr.s_addr)
+				&& (efm.list[i].port == efm.client.sin_port)) {
+			DEBUG("duplicate server detected! (%s:%d)\n",
+					inet_ntoa(efm.client.sin_addr), ntohs(efm.client.sin_port));
+			server_dup = 1;
+			break;
+		}
+	}
+
+	// parse packet
+	packet += ef_pkt_header_len + ef_pkt_heartbeat_len;
+	ptr = strchr(packet, '\\');
+	if (ptr == NULL) {
+		WARNING("invalid heartbeat packet received from %s:%d ignored!\n",
+			inet_ntoa(efm.client.sin_addr), ntohs(efm.client.sin_port));
+		return -1;
+	}
+	*ptr = '\0';
+
+	heartbeat = atoi(packet);
+
+	packet = ptr+1;
+	ptr = strchr(packet, '\\');
+	if (ptr == NULL) {
+		WARNING("invalid heartbeat packet received from %s:%d ignored!\n",
+			inet_ntoa(efm.client.sin_addr), ntohs(efm.client.sin_port));
+		return -1;
+	}
+	*ptr = '\0';
+
+	if (strcmp(packet, "gamename") != 0) {
+		WARNING("invalid heartbeat packet received from %s:%d ignored!\n",
+			inet_ntoa(efm.client.sin_addr), ntohs(efm.client.sin_port));
+		return -1;
+	}
+	packet = ptr+1;
+
+	ptr = strchr(packet, '\\');
+	if (ptr == NULL) {
+		WARNING("invalid heartbeat packet received from %s:%d ignored!\n",
+			inet_ntoa(efm.client.sin_addr), ntohs(efm.client.sin_port));
+		return -1;
+	}
+	*ptr = '\0';
+
+	if (strcmp(packet, "STEF1") != 0) {
+		WARNING("invalid heartbeat packet received from %s:%d ignored!\n",
+			inet_ntoa(efm.client.sin_addr), ntohs(efm.client.sin_port));
+		return -1;
+	}
+	gamename = packet;
+	DEBUG("heartbeat = %d, gamename = \"%s\"\n", heartbeat, gamename);
+	// done parsing packet
+
+	INFO("heartbeat from %s:%d\n",
+			inet_ntoa(efm.client.sin_addr), ntohs(efm.client.sin_port));
+	// if not, then add it to the list
+	if (server_dup == 0) {
+		// server is not in our list so add its ip, port and a timestamp
+		efm.list[efm.num_servers].ip = efm.client.sin_addr;
+		efm.list[efm.num_servers].port = efm.client.sin_port;
+		efm.list[efm.num_servers].lastheartbeat = time(NULL);
+		DEBUG("this is server no.: %d | lastheartbeat: %d\n",
+				efm.num_servers, efm.list[efm.num_servers].lastheartbeat);
+		// allocate memory for private data
+		efm.list[efm.num_servers].private_data = calloc(1, sizeof(efm_private_data_t));
+
+		efm.num_servers++;
+
+		DEBUG("reallocating server list (old size: %d -> new size: %d)\n",
+				efm.num_servers * sizeof(serverlist_t),
+				(efm.num_servers+1) * sizeof(serverlist_t));
+
+		// back up the current list pointer in case realloc() fails
+		backup_ptr = efm.list;
+		efm.list = (serverlist_t *) realloc(efm.list, ((efm.num_servers+1)*sizeof(serverlist_t)));
+		if (efm.list == NULL) {
+			WARNING("realloc() failed trying to get %d bytes!\n",
+					(efm.num_servers+1)*sizeof(serverlist_t));
+			// since the pointer is overwritten with NULL
+			// we'll recover by using the backup pointer
+			efm.list = backup_ptr;
+			return -2;
+		} else DEBUG("reallocation successful\n");
+	} else {
+		time_diff = time(NULL) - efm.list[i].lastheartbeat;
+		// if time_diff is 0 the server has shutdown (most likely)
+		if (time_diff == 0) {
+			INFO("server %s:%u is shutting down (time_diff %d)\n",
+					inet_ntoa(efm.list[i].ip), ntohs(efm.list[i].port),
+					time_diff);
+			delete_server(&efm, i);
+			server_dup = 0;
+			return 2; // return "server-shutdown" code
+		} else {
+			// server is in already in our list so we just update the timestamp
+			efm.list[i].lastheartbeat = time(NULL);
+			server_dup = 0;
+		}
+	}
+	// server added/updated
+	return 1;
+}
+
+static int
+process_heartstop(char *packet)
+{
+	char *ptr;
+	int heartstop;
+	char *gamename;
+	int i;
+
+	// validate packet
+	packet += ef_pkt_header_len + ef_pkt_heartstop_len;
+	ptr = strchr(packet, '\\');
+	if (ptr == NULL) {
+		WARNING("invalid heartstop packet from %s:%d ignored!\n",
+			inet_ntoa(efm.client.sin_addr), ntohs(efm.client.sin_port));
+		return -1;
+	}
+
+	heartstop = atoi(packet);
+
+	packet = ptr+1;
+	ptr = strchr(packet, '\\');
+	if (ptr == NULL) {
+		WARNING("invalid heartstop packet from %s:%d ignored!\n",
+			inet_ntoa(efm.client.sin_addr), ntohs(efm.client.sin_port));
+		return -1;
+	}
+
+	*ptr = '\0';
+	if (strcmp(packet, "gamename") != 0) {
+		WARNING("invalid heartstop packet from %s:%d ignored!\n",
+			inet_ntoa(efm.client.sin_addr), ntohs(efm.client.sin_port));
+		return -1;
+	}
+
+	packet = ptr+1;
+	ptr = strchr(packet, '\\');
+	if (ptr == NULL) {
+		WARNING("invalid heartstop packet from %s:%d ignored!\n",
+			inet_ntoa(efm.client.sin_addr), ntohs(efm.client.sin_port));
+		return -1;
+	}
+
+	gamename = packet;
+	DEBUG("heartstop = %d, gamename = \"%s\"\n", heartstop, gamename);
+
+	// search for server's index in array and delete it if present
+	for (i = 0; i < efm.num_servers; i++) {
+		if ((efm.list[i].ip.s_addr == efm.client.sin_addr.s_addr)
+				&& (efm.list[i].port == efm.client.sin_port)) {
+			delete_server(&efm, i);
+			return 2;
+		}
+	}
+
+	WARNING("server %s:%d no found in server list!\n",
+		inet_ntoa(efm.client.sin_addr), ntohs(efm.client.sin_port));
+	return -1;
+}
+
+static int
+send_getstatus()
+{
+	int challenge, i;
+
+	// create challenge number
+	challenge = rand();
+	DEBUG("challenge: %d\n", challenge);
+
+	// prepare efm.msg_out
+	efm.num_msgs = 1;
+	efm.msg_out_length = calloc(1, sizeof(int));
+	if (efm.msg_out_length == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n", sizeof(int));
+		return -2; // TODO: define retval for errors
+	}
+	DEBUG("allocated %d bytes for msg_out_length[]\n", sizeof(int));
+
+	// efm.msg_out_length[0] = ef_pkt_header_len + ef_pkt_getstatus_len;
+	efm.msg_out_length[0] = ef_pkt_header_len
+			+ ef_pkt_getstatus_len + (int)(sizeof(int)*2.5);
+
+	// allocate the memory for the outgoing packet
+	efm.msg_out = calloc(1, sizeof(char *));
+	if (efm.msg_out == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n", sizeof(char *));
+		return -2; // TODO: define retval for errors
+	}
+
+	efm.msg_out[0] = calloc(efm.msg_out_length[0]+1, 1);
+	if (efm.msg_out[0] == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n",
+				efm.msg_out_length[0]);
+		return -2; // TODO: define retval for errors
+	}
+	DEBUG("allocated %d bytes for msg_out[0]\n", efm.msg_out_length[0]+1);
+
+	memcpy(efm.msg_out[0], ef_pkt_header, ef_pkt_header_len);
+	memcpy(efm.msg_out[0]+ef_pkt_header_len, ef_pkt_getstatus, ef_pkt_getstatus_len);
+	sprintf(efm.msg_out[0]+ef_pkt_header_len+ef_pkt_getstatus_len, "%d", challenge);
+
+	// write challenge into serverlist
+	for (i = 0; i < efm.num_servers; i++) {
+		if ((efm.client.sin_addr.s_addr == efm.list[i].ip.s_addr)
+				&& (efm.client.sin_port == efm.list[i].port)) {
+			((efm_private_data_t *) efm.list[i].private_data)->_challenge = challenge;
+			break;
+		}
+	}
+
+	return 1; // send "getstatus" packet
+}
+
+static int
+process_getservers(char *packet)
+{
+	int i, j, pkt_offset; // temp vars
+	int getsrv_protocol;
+	char *temp;
+	efm_private_data_t *temp_priv_data;
+
+	INFO("getservers from %s:%u\n",
+			inet_ntoa(efm.client.sin_addr), ntohs(efm.client.sin_port));
+
+	// we need the protocol version from the packet so we parse it
+	temp = packet+ef_pkt_header_len+ef_pkt_getsrv_len+1;
+	getsrv_protocol = atoi(temp);
+	DEBUG("requested protocol is %d\n", getsrv_protocol);
+
+	// got the protocol version now we can assemble the outgoing packet(s)
+	DEBUG("assembling server list packet\n");
+
+	/*
+	 * This is the new, badly documented packet assembler.
+	 */
+	// walk the server list
+	for (i = j = 0; (j < efm.num_servers) || (efm.num_msgs == 0); i++) {
+		efm.num_msgs++;
+
+		// then allocate memory for the packets
+		efm.msg_out = realloc(efm.msg_out, efm.num_msgs*sizeof(char *));
+		if (efm.msg_out == NULL) {
+			ERRORV("malloc() failed to get %d bytes!\n", efm.num_msgs*sizeof(char *));
+			return -2;
+		}
+		efm.msg_out_length = realloc(efm.msg_out_length, efm.num_msgs*sizeof(int));
+		if (efm.msg_out_length == NULL) {
+			ERRORV("malloc() failed to get %d bytes!\n", efm.num_msgs*sizeof(int));
+			return -2;
+		}
+
+		// get memory for header and command
+		efm.msg_out[i] = malloc(1289);
+		if (efm.msg_out[i] == NULL) {
+			ERROR("malloc() failed to get 1289 bytes!\n");
+			return -2;
+		}
+
+		// write header and command into packet
+		memcpy(efm.msg_out[i], ef_pkt_header, ef_pkt_header_len);
+		pkt_offset = ef_pkt_header_len;
+		memcpy(efm.msg_out[i]+pkt_offset, ef_pkt_getsrvrsp, ef_pkt_getsrvrsp_len);
+		pkt_offset += ef_pkt_getsrvrsp_len;
+
+		for (; (j < efm.num_servers) && (pkt_offset < 1284); j++) {
+			temp_priv_data = (efm_private_data_t *) efm.list[j].private_data;
+			// if the protocol matches, write ip/port into the packet
+			if (temp_priv_data->protocol == getsrv_protocol) {
+				// copy data from server list into packet
+				// FIXME: wrong byte order; somehow this doesn't work as expected
+				sprintf(efm.msg_out[i]+pkt_offset, "\\%08x%04hx",
+					htonl(efm.list[j].ip.s_addr), htons(efm.list[j].port));
+				pkt_offset += 13;
+			}
+		} // for j < 97
+
+		// write footer
+		memcpy(efm.msg_out[i]+pkt_offset, ef_pkt_footer, ef_pkt_footer_len);
+		pkt_offset += ef_pkt_footer_len;
+		efm.msg_out[i][pkt_offset] = '\0';
+		efm.msg_out_length[i] = pkt_offset;
+		DEBUG("efm.msg_out_length[%d] = %d\n", i, pkt_offset);
+	}
+
+	// packet with server list is ready
+	return 1;
+}
+
+static int
+process_statusResponse(char *packet, int packetlen)
+{
+	char *varname = NULL, *value = NULL;
+	char *score = NULL, *ping = NULL, *name = NULL;
+	int i;
+	int server_dup = 0, done = 0;
+	char *packetend = packet+packetlen;
+	efm_private_data_t *private_data = calloc(1, sizeof(efm_private_data_t));
+	efm_private_data_t *oldprivdata;
+
+	// check if source address is known
+	for (i = 0; i < efm.num_servers; i++) {
+		if ((efm.client.sin_addr.s_addr == efm.list[i].ip.s_addr)
+				&& (efm.client.sin_port == efm.list[i].port)) {
+			server_dup = 1;
+			break;
+		}
+	}
+
+	// source address not known
+	if (server_dup == 0) {
+		WARNING("unexpected \"statusResponse\" from %s:%d ignored\n",
+				inet_ntoa(efm.client.sin_addr), ntohs(efm.client.sin_port));
+		return -1;
+	}
+
+	oldprivdata = (efm_private_data_t *)efm.list[i].private_data;
+
+	// go to 1st "\" which is after the command string
+	packet = strpbrk(packet, "\\");
+	if (packet == NULL) {
+		WARNING("malformed statusResponse packet received from %s:%d!\n",
+			inet_ntoa(efm.client.sin_addr), ntohs(efm.client.sin_port));
+		return -1;
+	}
+
+	DEBUG("begin parsing server info\n");
+	while ((++packet < packetend) && !done)
+	{
+		// get variable name
+		varname = packet;
+
+		// go to next delimiter
+		packet = strpbrk(packet, "\\");
+		if (packet == NULL) {
+			ERRORV("malformed statusResponse packet received from %s:%d!\n",
+				inet_ntoa(efm.client.sin_addr), ntohs(efm.client.sin_port));
+			return -2;
+		}
+		// overwrite delimiter with \0
+		*packet = '\0';
+
+		// get value
+		value = ++packet;
+
+		// go to next delimiter
+		packet = strpbrk(packet, "\\\n");
+		if (packet == NULL) {
+			ERRORV("malformed statusResponse packet received from %s:%d!\n",
+				inet_ntoa(efm.client.sin_addr), ntohs(efm.client.sin_port));
+			return -2;
+		}
+
+		// check if we're at the end of the server info section
+		if (*packet == '\n') done = 1;
+		// overwrite delimiter with \0
+		*packet = '\0';
+
+		DEBUG("varname = \"%s\", value = \"%s\"\n", varname, value);
+		// parse varname and assign the value to the struct
+		if (strcmp(varname, "challenge") == 0) {
+			private_data->challenge = atoi(value);
+		} else if (strcmp(varname, "g_needpass") == 0) {
+			private_data->g_needpass = atoi(value);
+		} else if (strcmp(varname, "capturelimit") == 0) {
+			private_data->capturelimit = atoi(value);
+		} else if (strcmp(varname, "g_maxGameClients") == 0) {
+			private_data->g_maxGameClients = atoi(value);
+		} else if (strcmp(varname, "gamename") == 0) {
+			private_data->gamename = strdup(value);
+			if (private_data->gamename == NULL) {
+				ERRORV("strdup() failed to get %d bytes!\n", strlen(value)+1);
+				return -2;
+			}
+		} else if (strcmp(varname, "bot_minplayers") == 0) {
+			private_data->bot_minplayers = atoi(value);
+		} else if (strcmp(varname, "sv_allowDownload") == 0) {
+			private_data->sv_allowDownload = atoi(value);
+		} else if (strcmp(varname, "sv_pure") == 0) {
+			private_data->sv_pure = atoi(value);
+		} else if (strcmp(varname, "sv_floodProtect") == 0) {
+			private_data->sv_floodProtect = atoi(value);
+		} else if (strcmp(varname, "sv_maxPing") == 0) {
+			private_data->sv_maxPing = atoi(value);
+		} else if (strcmp(varname, "sv_minPing") == 0) {
+			private_data->sv_minPing = atoi(value);
+		} else if (strcmp(varname, "sv_maxRate") == 0) {
+			private_data->sv_maxRate = atoi(value);
+		} else if (strcmp(varname, "sv_maxclients") == 0) {
+			private_data->sv_maxclients = atoi(value);
+		} else if (strcmp(varname, "sv_hostname") == 0) {
+			private_data->sv_hostname = strdup(value);
+			if (private_data->sv_hostname == NULL) {
+				ERRORV("strdup() failed to get %d bytes!\n", strlen(value)+1);
+				return -2;
+			}
+		} else if (strcmp(varname, "sv_privateClients") == 0) {
+			private_data->sv_privateClients = atoi(value);
+		} else if (strcmp(varname, "mapname") == 0) {
+			private_data->mapname = strdup(value);
+			if (private_data->mapname == NULL) {
+				ERRORV("strdup() failed to get %d bytes!\n", strlen(value)+1);
+				return -2;
+			}
+		} else if (strcmp(varname, "protocol") == 0) {
+			private_data->protocol = atoi(value);
+		} else if (strcmp(varname, "g_pModElimination") == 0) {
+			private_data->g_pModElimination = atoi(value);
+		} else if (strcmp(varname, "g_pModActionHero") == 0) {
+			private_data->g_pModActionHero = atoi(value);
+		} else if (strcmp(varname, "g_pModDisintegration") == 0) {
+			private_data->g_pModDisintegration = atoi(value);
+		} else if (strcmp(varname, "g_pModAssimilation") == 0) {
+			private_data->g_pModAssimilation = atoi(value);
+		} else if (strcmp(varname, "g_pModSpecialties") == 0) {
+			private_data->g_pModSpecialties = atoi(value);
+		} else if (strcmp(varname, "g_gametype") == 0) {
+			private_data->g_gametype = atoi(value);
+		} else if (strcmp(varname, "timelimit") == 0) {
+			private_data->timelimit = atoi(value);
+		} else if (strcmp(varname, "fraglimit") == 0) {
+			private_data->fraglimit = atoi(value);
+		} else if (strcmp(varname, "dmflags") == 0) {
+			private_data->dmflags = atoi(value);
+		} else if (strcmp(varname, "version") == 0) {
+			private_data->version = strdup(value);
+			if (private_data->version == NULL) {
+				ERRORV("strdup() failed to get %d bytes!\n", strlen(value)+1);
+				return -2;
+			}
+		} //else {
+			// WARNING("unknown option \"%s\" in statusResponse ignored\n", varname);
+		//}
+	}
+	DEBUG("end parsing server info\n");
+
+	// parse player info
+	private_data->_players = 0;
+	while (++packet < packetend) {
+		// FIXME: recover from realloc() failure
+		private_data->_player = (efm_player_data_t *) realloc(private_data->_player,
+				(private_data->_players+1)*sizeof(efm_player_data_t));
+		if (private_data->_player == NULL) {
+			ERRORV("realloc() failed trying to get %d bytes!\n",
+					private_data->_players*sizeof(efm_player_data_t));
+			return -2;
+		}
+
+		// get player score
+		score = packet;
+
+		// go to next delimiter
+		if ((packet = strpbrk(packet, " ")) == NULL) {
+			ERRORV("malformed statusResponse packet received from %s:%d!\n",
+				inet_ntoa(efm.client.sin_addr), ntohs(efm.client.sin_port));
+			return -2;
+		}
+		// overwrite delimiter
+		*packet = '\0';
+
+		// parse player score
+		private_data->_player[private_data->_players].score = atoi(score);
+
+		// get player ping
+		ping = ++packet;
+
+		// go to next delimiter
+		if ((packet = strpbrk(packet, " ")) == NULL) {
+			ERRORV("malformed statusResponse packet received from %s:%d!\n",
+				inet_ntoa(efm.client.sin_addr), ntohs(efm.client.sin_port));
+			return -2;
+		}
+		// overwrite delimiter
+		*packet = '\0';
+
+		// parse player ping
+		private_data->_player[private_data->_players].ping = atoi(ping);
+
+		// get player name
+		name = ++packet;
+
+		// go to next delimiter
+		if ((packet = strpbrk(packet, "\n")) == NULL) {
+			ERRORV("malformed statusResponse packet received from %s:%d!\n",
+				inet_ntoa(efm.client.sin_addr), ntohs(efm.client.sin_port));
+			return -2;
+		}
+		// overwrite delimiter
+		*packet = '\0';
+
+		// parse player name
+		private_data->_player[private_data->_players].name = strdup(name);
+		if (private_data->_player[private_data->_players].name == NULL) {
+			ERRORV("strdup() failed to get %d bytes!\n", strlen(name)+1);
+			return -2;
+		}
+		//private_data->_player[private_data->_players].name[packet-name-1] = '\0';
+
+		DEBUG("player #%d name: \"%s\", ping: %d, score: %d\n",
+			private_data->_players, private_data->_player[private_data->_players].name,
+			private_data->_player[private_data->_players].ping,
+			private_data->_player[private_data->_players].score);
+
+		private_data->_players++;
+	}
+
+	// compare challenge to ours
+	if (private_data->challenge != oldprivdata->_challenge) {
+		WARNING("statusResponse challenge mismatch (%d != %d)\n",
+				private_data->challenge, oldprivdata->_challenge);
+		free_privdata(private_data);
+		return -1;
+	}
+
+	// if we already have parsed server/player info we have to free it first
+	if (efm.list[i].private_data != NULL) free_privdata(efm.list[i].private_data);
+	efm.list[i].private_data = private_data;
+
+	return 0;
+}
+
+static int
+process_getmotd(char *packet, int packetlen)
+{
+	char *version = NULL, *renderer = NULL, *cputype = NULL;
+	int mhz, memory, joystick, colorbits, challenge;
+	char *varname = NULL, *value = NULL;
+	char *packetend = packet+packetlen;
+
+	packet += ef_pkt_header_len+ef_pkt_getmotd_len+2;
+
+	while ((++packet < packetend) && (*packet != '\x0a')) {
+		// save position as variable name
+		varname = packet;
+
+		// go to next delimiter
+		packet = strpbrk(packet, "\\");
+		if (packet == NULL) {
+			WARNING("invalid \"getmotd\" from %s:%d received; ignored\n",
+					inet_ntoa(efm.client.sin_addr),
+					ntohs(efm.client.sin_port));
+			return -1;
+		}
+
+		// overwrite delimiter with \0
+		*packet = '\0';
+
+		// save next position as value
+		value = ++packet;
+
+		// go to next delimiter
+		packet = strpbrk(packet, "\\\"");
+		if (packet == NULL) {
+			WARNING("invalid \"getmotd\" from %s:%d received; ignored\n",
+					inet_ntoa(efm.client.sin_addr),
+					ntohs(efm.client.sin_port));
+			return -1;
+		}
+
+		// overwrite delimiter with \0
+		*packet = '\0';
+
+		// parse
+		if (strcmp(varname, "version") == 0) {
+			version = strdup(value);
+			if (version == NULL) {
+				ERRORV("strdup() failed to get %d bytes!\n", strlen(value)+1);
+				return -2;
+			}
+		} else if (strcmp(varname, "renderer") == 0) {
+			renderer = strdup(value);
+			if (renderer == NULL) {
+				ERRORV("strdup() failed to get %d bytes!\n", strlen(value)+1);
+				return -2;
+			}
+		} else if (strcmp(varname, "challenge") == 0) {
+			challenge = atoi(value);
+		} else if (strcmp(varname, "cputype") == 0) {
+			cputype = strdup(value);
+			if (cputype == NULL) {
+				ERRORV("strdup() failed to get %d bytes!\n", strlen(value)+1);
+				return -2;
+			}
+		} else if (strcmp(varname, "mhz") == 0) {
+			mhz = atoi(value);
+		} else if (strcmp(varname, "memory") == 0) {
+			memory = atoi(value);
+		} else if (strcmp(varname, "joystick") == 0) {
+			joystick = atoi(value);
+		} else if (strcmp(varname, "colorbits") == 0) {
+			colorbits = atoi(value);
+		} else {
+			WARNING("unknown variable \"%s\" in \"getmotd\" packet ignored\n",
+					varname);
+		}
+	}
+
+	INFO("getmotd from %s:%d (challenge = %d, version = \"%s\", renderer = \"%s\", cputype = \"%s\", mhz = %d, memory = %d, joystick = %d, colorbits = %d)\n",
+			inet_ntoa(efm.client.sin_addr), ntohs(efm.client.sin_port),
+			challenge, version, renderer, cputype, mhz, memory, joystick, colorbits);
+
+	// we got all we need to assemble the motd packet
+	efm.msg_out = calloc(1, sizeof(char *));
+	if (efm.msg_out == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n",
+				sizeof(char *));
+		return -2;
+	}
+	efm.msg_out_length = calloc(1, sizeof(int));
+	if (efm.msg_out_length == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n",
+				sizeof(int));
+		return -2;
+	}
+	efm.msg_out_length[0] = ef_pkt_header_len+ef_pkt_motd_len
+							+ strlen(EFM_MOTD)
+							+ (int)(sizeof(int)*2.5);
+	efm.msg_out[0] = calloc(efm.msg_out_length[0]+1, 1);
+	if (efm.msg_out[0] == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n",
+				efm.msg_out_length[0]);
+		return -2;
+	}
+	efm.num_msgs = 1;
+	sprintf(efm.msg_out[0], ef_pkt_motd, challenge, EFM_MOTD);
+
+	// clean up
+	free(version);
+	free(renderer);
+	free(cputype);
+
+	return 1;
+}
+
+static int
+process(char *packet, int packetlen)
+{
+	int retval;
+
+	// check if packet is efa related
+	if (strncmp(packet, ef_pkt_header, ef_pkt_header_len) == 0) {
+		DEBUG("EF protocol marker detected!\n");
+		// which packet did we receive?
+		if (strncmp(packet+ef_pkt_header_len, ef_pkt_heartbeat, ef_pkt_heartbeat_len) == 0) {
+			retval = process_heartbeat(packet);
+			if (retval == 1) {
+				send_getstatus();
+				return 1; // "send packet" code
+			}
+			return retval;
+		} else if (strncmp(packet+ef_pkt_header_len, ef_pkt_heartstop, ef_pkt_heartstop_len) == 0) {
+			return process_heartstop(packet);
+		} else if (strncmp(packet+ef_pkt_header_len, ef_pkt_getsrv, ef_pkt_getsrv_len) == 0) {
+			return process_getservers(packet);
+		} else if (strncmp(packet+ef_pkt_header_len, ef_pkt_statusrsp, ef_pkt_statusrsp_len) == 0) {
+			return process_statusResponse(packet, packetlen);
+		} else if (strncmp(packet+ef_pkt_header_len, ef_pkt_getmotd, ef_pkt_getmotd_len) == 0) {
+			return process_getmotd(packet, packetlen);
+		} else if (strncmp(packet+ef_pkt_header_len, ef_pkt_getkeyauth, ef_pkt_getkeyauth_len) == 0) {
+			DEBUG("STUB: process_getKeyAuthorize\n");
+		}
+		WARNING("unknown packet received!\n");
+		return -1;
+	} // end if for 0xff 0xff 0xff 0xff marker
+	WARNING("invalid packet received: EF protocol marker missing!\n");
+	return -1; // invalid packet
+}
+
+static void
+cleanup(void)
+{
+	int i;
+
+	if (efm.num_servers > 0) {
+		for (i = 0; i < efm.num_servers; i++) {
+			free_privdata(efm.list[i].private_data);
+		}
+	}
+}
+
+void
+init_plugin(void)
+{
+	register_plugin(&efm);
+}
+

+ 296 - 0
src/masterserver/plugins/libh2.c

@@ -0,0 +1,296 @@
+/* libh2.c: masterserver plugin for Heretic2 servers. */
+/* Copyright (C) 2003  Andre' Schulz
+ * This file is part of masterserver.
+ *
+ * masterserver is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * masterserver is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with masterserver; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * The author can be contacted at chickenman@exhale.de
+ */
+/*
+ * vim:sw=4:ts=4
+ */
+
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <sys/socket.h> // for socket() etc.
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "../masterserver.h"
+
+#define HEARTBEAT_TIMEOUT 300
+
+#undef LOG_SUBNAME
+#define LOG_SUBNAME "libh2" // logging subcategory description
+
+const char	h2_pkt_header[]		= "\xff\xff\xff\xff";
+const int	h2_pkt_header_len	= 4;
+// we just check for the keyword
+const char	h2_pkt_heartbeat[]	= "heartbeat";
+const int	h2_pkt_heartbeat_len= 9;
+const char	h2_pkt_query[]		= "query";
+const int	h2_pkt_query_len	= 5;
+const char	h2_pkt_servers[]	= "servers ";
+const int	h2_pkt_servers_len	= 8;
+const char	h2_pkt_shutdown[]	= "shutdown";
+const int	h2_pkt_shutdown_len	= 8;
+const char	h2_pkt_ping[]		= "ping";
+const int	h2_pkt_ping_len		= 4;
+const char	h2_pkt_ack[]		= "ack";
+const int	h2_pkt_ack_len		= 3;
+
+const char h2m_plugin_version[] = "0.4";
+static port_t h2m_ports[] = { { IPPROTO_UDP, 28900 } };
+
+static void	info(void); // print information about plugin
+static int	process(char *, int); // process packet and return a value
+static int	process_heartbeat(char *);
+static int	process_shutdown(char *);
+static int	process_ping(char *);
+static int	process_query(char *);
+void		init_plugin(void) __attribute__ ((constructor));
+
+static
+struct masterserver_plugin h2m
+= { "h2m",
+	h2m_plugin_version,
+	masterserver_version,
+	h2m_ports,
+	1,
+	HEARTBEAT_TIMEOUT,
+	&info,
+	&process,
+	NULL,	// free_privdata()
+	NULL	// cleanup()
+};
+
+static void
+info(void)
+{
+	INFO("heretic2 masterserver plugin v%s\n", h2m_plugin_version);
+	INFO("  compiled for masterserver v%s\n", masterserver_version);
+}
+
+static int
+process_heartbeat(char *packet)
+{
+	int i, server_dup = 0, time_diff; // temp vars
+
+	// first, check if server is already in our list
+	for (i = 0; i < h2m.num_servers; i++) {
+		if ((h2m.list[i].ip.s_addr == h2m.client.sin_addr.s_addr)
+				&& (h2m.list[i].port == h2m.client.sin_port)) {
+			DEBUG("duplicate server detected! (%s:%d)\n",
+				inet_ntoa(h2m.client.sin_addr), ntohs(h2m.client.sin_port));
+			server_dup = 1;
+			break;
+		}
+	}
+
+	INFO("heartbeat from %s:%d\n",
+			inet_ntoa(h2m.client.sin_addr), ntohs(h2m.client.sin_port));
+	// if not, then add it to the list
+	if (!server_dup) {
+		h2m.list[h2m.num_servers].ip = h2m.client.sin_addr;
+		h2m.list[h2m.num_servers].port = h2m.client.sin_port;
+		h2m.list[h2m.num_servers].lastheartbeat = time(NULL);
+		h2m.num_servers++;
+
+		DEBUG("reallocating server list (old size: %d -> new size: %d)\n",
+			h2m.num_servers * sizeof(serverlist_t),
+			(h2m.num_servers+1) * sizeof(serverlist_t));
+
+		h2m.list = realloc(h2m.list, ((h2m.num_servers + 1) * sizeof(serverlist_t)));
+		if (h2m.list == NULL) {
+			ERRORV("realloc() failed trying to get %d bytes!\n",
+					(h2m.num_servers+1)*sizeof(serverlist_t));
+			pthread_exit((void *) -1);
+		} else DEBUG("reallocation successful\n");
+	} else {
+		time_diff = time(NULL) - h2m.list[i].lastheartbeat;
+		// server is already in our list so we just update the timestamp
+		h2m.list[i].lastheartbeat = time(NULL);
+		server_dup = 0;
+	}
+	return 0; // server added to list
+} // process_heartbeat()
+
+static int
+process_shutdown(char *packet)
+{
+	int i, time_diff, server_dup = 0;
+
+	// sanity check num_servers
+	for (i = 0; i < h2m.num_servers; i++) {
+		if ((h2m.list[i].ip.s_addr == h2m.client.sin_addr.s_addr)
+				&& (h2m.list[i].port == h2m.client.sin_port)) {
+			server_dup = 1;
+			break;
+		}
+	}
+
+	if (server_dup) {
+		time_diff = time(NULL) - h2m.list[i].lastheartbeat;
+		INFO("server %s:%u is shutting down (time_diff %d)\n",
+			inet_ntoa(h2m.list[i].ip), ntohs(h2m.list[i].port), time_diff);
+		delete_server(&h2m, i);
+		return 2; // return "server shutdown" code
+	} else return -1; // invalid packet
+} // process_shutdown()
+
+static int
+process_ping(char *packet)
+{
+	INFO("ping from %s:%d\n", inet_ntoa(h2m.client.sin_addr), ntohs(h2m.client.sin_port));
+	// allocate memory for msg_out_length array
+	h2m.msg_out_length = calloc(1, sizeof(int));
+	if (h2m.msg_out_length == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n",
+				sizeof(int));
+		return -2;
+	}
+
+	// write length of packet
+	h2m.msg_out_length[0] = h2_pkt_header_len + h2_pkt_ack_len;
+
+	// allocate memory for packet pointers (we only send 1)
+	h2m.msg_out = calloc(1, sizeof(char *));
+	if (h2m.msg_out == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n",
+				sizeof(char *));
+		return -2;
+	}
+
+	// allocate memory for the packet itself
+	h2m.msg_out[0] = calloc(h2m.msg_out_length[0]+1, 1);
+	if (h2m.msg_out[0] == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n",
+				h2m.msg_out_length[0]+1);
+		return -2;
+	}
+
+	// write packet contents
+	memcpy(h2m.msg_out[0], h2_pkt_header, h2_pkt_header_len);
+	memcpy(h2m.msg_out[0]+h2_pkt_header_len, h2_pkt_ack, h2_pkt_ack_len);
+
+	h2m.num_msgs = 1;
+
+	return 1; // tell masterserver to send it out
+} // process_ping()
+
+static int
+process_query(char *packet)
+{
+	int i; // temp var
+	int msg_out_offset; // temp var for keeping track of where were writing the outgoing packet
+
+	INFO("query from %s:%d\n",
+		inet_ntoa(h2m.client.sin_addr), ntohs(h2m.client.sin_port));
+
+	/*
+	 * the following char array will be our outgoing packet.
+	 * first, we'll calculate the length.
+	 * the length consists of the following values:
+	 * - length of header -> h2_pkt_header_len
+	 * - length of command -> h2_pkt_servers_len
+	 * - delimiter between command and list
+	 * - number of servers in list
+	 *   -> h2m.num_servers * 6
+	 */
+
+	// allocate memory for msg_out_length array
+	// XXX: I don't know if there's any restriction in the h2m protocol
+	//		regarding the # of servers or bytes sent to the client.
+	//		I'll just leave it like this for now.
+	h2m.msg_out_length = calloc(1, sizeof(int));
+	if (h2m.msg_out_length == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n",
+				sizeof(int));
+		return -2; // TODO: define retval for errors
+	}
+
+	h2m.msg_out_length[0] = h2_pkt_header_len
+					 + h2_pkt_servers_len
+					 + (h2m.num_servers * 6);
+	DEBUG("%d + %d + (%d * 6) = %d\n",
+		h2_pkt_header_len, h2_pkt_servers_len,
+		h2m.num_servers, h2m.msg_out_length[0]);
+
+	// allocate memory for packet pointers
+	h2m.msg_out = calloc(1, sizeof(char *));
+	if (h2m.msg_out == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n",
+				sizeof(char *));
+		return -2; // TODO: define retval for errors
+	}
+
+	// allocate memory for the packet itself
+	h2m.msg_out[0] = calloc(h2m.msg_out_length[0]+1, 1);
+	if (h2m.msg_out[0] == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n",
+				h2m.msg_out_length[0]+1);
+		return -2; // TODO: define retval for errors
+	}
+
+	// copy h2_pkt_header into the packet
+	memcpy(h2m.msg_out[0], h2_pkt_header, h2_pkt_header_len);
+	memcpy(h2m.msg_out[0]+h2_pkt_header_len, h2_pkt_servers, h2_pkt_servers_len);
+	msg_out_offset = h2_pkt_header_len + h2_pkt_servers_len;
+
+	// create the packet
+	for (i = 0; i < h2m.num_servers; i++) {
+		// append ip and port to msg_out
+		memcpy(h2m.msg_out[0]+msg_out_offset, &h2m.list[i].ip, 4);
+		msg_out_offset += 4;
+		memcpy(h2m.msg_out[0]+msg_out_offset, &h2m.list[i].port, 2);
+		msg_out_offset += 2;
+	}
+
+	h2m.num_msgs = 1;
+
+	// packet with server list is ready
+	return 1;
+} // process_query()
+
+static int
+process(char *packet, int packetlen)
+{
+
+	// check if packet is h2 related
+	if (strncmp(packet, h2_pkt_header, h2_pkt_header_len) == 0) {
+		DEBUG("H2 protocol marker detected!\n");
+		// which packet did we receive?
+		if (strncmp(packet+h2_pkt_header_len, h2_pkt_heartbeat, h2_pkt_heartbeat_len) == 0) {
+			return process_heartbeat(packet);
+		} else if (strncmp(packet+h2_pkt_header_len, h2_pkt_shutdown, h2_pkt_shutdown_len) == 0) {
+			return process_shutdown(packet);
+		} else if (strncmp(packet+h2_pkt_header_len, h2_pkt_ping, h2_pkt_ping_len) == 0) {
+			return process_ping(packet);
+		}
+	} else if (strncmp(packet, h2_pkt_query, h2_pkt_query_len) == 0) {
+		return process_query(packet);
+	}
+	return -1; // invalid packet
+}
+
+void
+init_plugin(void)
+{
+	register_plugin(&h2m);
+}
+

+ 297 - 0
src/masterserver/plugins/libq2.c

@@ -0,0 +1,297 @@
+/* libq2.c: masterserver plugin for Quake2 servers. */
+/* Copyright (C) 2003  Andre' Schulz
+ * This file is part of masterserver.
+ *
+ * masterserver is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * masterserver is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with masterserver; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * The author can be contacted at chickenman@exhale.de
+ */
+/*
+ * vim:sw=4:ts=4
+ */
+
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <sys/socket.h> // for socket() etc.
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "../masterserver.h"
+
+#define HEARTBEAT_TIMEOUT 300
+
+#undef LOG_SUBNAME
+#define LOG_SUBNAME "libq2" // logging subcategory description
+
+const char	q2_pkt_header[]		= "\xff\xff\xff\xff";
+const int	q2_pkt_header_len	= 4;
+// we just check for the keyword
+const char	q2_pkt_heartbeat[]	= "heartbeat";
+const int	q2_pkt_heartbeat_len= 9;
+const char	q2_pkt_query[]		= "query";
+const int	q2_pkt_query_len	= 5;
+const char	q2_pkt_servers[]	= "servers ";
+const int	q2_pkt_servers_len	= 8;
+const char	q2_pkt_shutdown[]	= "shutdown";
+const int	q2_pkt_shutdown_len	= 8;
+const char	q2_pkt_ping[]		= "ping";
+const int	q2_pkt_ping_len		= 4;
+const char	q2_pkt_ack[]		= "ack";
+const int	q2_pkt_ack_len		= 3;
+
+const char q2m_plugin_version[] = "0.4";
+static port_t q2m_ports[] = { { IPPROTO_UDP, 27900 } };
+
+static void	info(void); // print information about plugin
+static int	process(char *, int); // process packet and return a value
+static int	process_heartbeat(char *);
+static int	process_shutdown(char *);
+static int	process_ping(char *);
+static int	process_query(char *);
+void		init_plugin(void) __attribute__ ((constructor));
+
+static
+struct masterserver_plugin q2m
+= { "q2m",
+	q2m_plugin_version,
+	masterserver_version,
+	q2m_ports,
+	1,
+	HEARTBEAT_TIMEOUT,
+	&info,
+	&process,
+	NULL,	// free_privdata()
+	NULL	// cleanup()
+};
+
+static void
+info(void)
+{
+	INFO("quake2 masterserver plugin v%s\n", q2m_plugin_version);
+	INFO("  compiled for masterserver v%s\n", masterserver_version);
+}
+
+static int
+process_heartbeat(char *packet)
+{
+	int i, server_dup = 0, time_diff; // temp vars
+
+	// first, check if server is already in our list
+	for (i = 0; i < q2m.num_servers; i++) {
+		if ((q2m.list[i].ip.s_addr == q2m.client.sin_addr.s_addr)
+				&& (q2m.list[i].port == q2m.client.sin_port)) {
+			DEBUG("duplicate server detected! (%s:%d)\n",
+				inet_ntoa(q2m.client.sin_addr), ntohs(q2m.client.sin_port));
+			server_dup = 1;
+			break;
+		}
+	}
+
+	INFO("heartbeat from %s:%d\n",
+			inet_ntoa(q2m.client.sin_addr), ntohs(q2m.client.sin_port));
+	// if not, then add it to the list
+	if (!server_dup) {
+		q2m.list[q2m.num_servers].ip = q2m.client.sin_addr;
+		q2m.list[q2m.num_servers].port = q2m.client.sin_port;
+		q2m.list[q2m.num_servers].lastheartbeat = time(NULL);
+		q2m.num_servers++;
+
+		DEBUG("reallocating server list (old size: %d -> new size: %d)\n",
+			q2m.num_servers * sizeof(serverlist_t),
+			(q2m.num_servers+1) * sizeof(serverlist_t));
+
+		q2m.list = realloc(q2m.list, ((q2m.num_servers + 1) * sizeof(serverlist_t)));
+		if (q2m.list == NULL) {
+			ERRORV("realloc() failed trying to get %d bytes!\n",
+					(q2m.num_servers+1)*sizeof(serverlist_t));
+			pthread_exit((void *) -1);
+		} else DEBUG("reallocation successful\n");
+	} else {
+		time_diff = time(NULL) - q2m.list[i].lastheartbeat;
+		// server is in already in our list so we just update the timestamp
+		q2m.list[i].lastheartbeat = time(NULL);
+		server_dup = 0;
+	}
+	return 0; // server added to list
+} // process_heartbeat()
+
+static int
+process_shutdown(char *packet)
+{
+	int i, time_diff, server_dup = 0;
+
+	// check if the server is in our list
+	for (i = 0; i < q2m.num_servers; i++) {
+		if ((q2m.list[i].ip.s_addr == q2m.client.sin_addr.s_addr)
+				&& (q2m.list[i].port == q2m.client.sin_port)) {
+			server_dup = 1;
+			break;
+		}
+	}
+
+	// if yes, remove it
+	if (server_dup) {
+		time_diff = time(NULL) - q2m.list[i].lastheartbeat;
+		INFO("server %s:%u is shutting down (time_diff %d)\n",
+			inet_ntoa(q2m.list[i].ip), ntohs(q2m.list[i].port), time_diff);
+		delete_server(&q2m, i);
+		return 2; // return "server shutdown" code
+	} else return -1; // invalid packet
+} // process_shutdown()
+
+static int
+process_ping(char *packet)
+{
+	INFO("ping from %s:%d\n", inet_ntoa(q2m.client.sin_addr), ntohs(q2m.client.sin_port));
+	// allocate memory for msg_out_length array
+	q2m.msg_out_length = calloc(1, sizeof(int));
+	if (q2m.msg_out_length == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n",
+				sizeof(int));
+		return -2;
+	}
+
+	// calculate length of packet
+	q2m.msg_out_length[0] = q2_pkt_header_len + q2_pkt_ack_len;
+
+	// allocate memory for packet pointers (we only send 1)
+	q2m.msg_out = calloc(1, sizeof(char *));
+	if (q2m.msg_out == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n",
+				sizeof(char *));
+		return -2;
+	}
+
+	// allocate memory for the packet itself
+	q2m.msg_out[0] = calloc(q2m.msg_out_length[0]+1, 1);
+	if (q2m.msg_out[0] == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n",
+				q2m.msg_out_length[0]+1);
+		return -2;
+	}
+
+	// write packet contents
+	memcpy(q2m.msg_out[0], q2_pkt_header, q2_pkt_header_len);
+	memcpy(q2m.msg_out[0]+q2_pkt_header_len, q2_pkt_ack, q2_pkt_ack_len);
+
+	q2m.num_msgs = 1;
+
+	return 1; // tell masterserver to send it out
+} // process_ping()
+
+static int
+process_query(char *packet)
+{
+	int i; // temp vars
+	int msg_out_offset; // temp var for keeping track of where were writing the outgoing packet
+
+	INFO("query from %s:%d\n",
+		inet_ntoa(q2m.client.sin_addr), ntohs(q2m.client.sin_port));
+
+	/*
+	 * the following char array will be our outgoing packet.
+	 * first, we'll calculate the length.
+	 * the length consists of the following values:
+	 * - length of header -> q2_pkt_header_len
+	 * - length of command -> q2_pkt_servers_len
+	 * - delimiter between command and list
+	 * - number of servers in list -> q2m.num_servers * 6
+	 */
+
+	// allocate memory for msg_out_length array
+	// XXX: I don't know if there's any restriction in the q2m protocol
+	//		regarding the # of servers or bytes sent to the client.
+	//		I'll just leave it like this for now.
+	q2m.msg_out_length = calloc(1, sizeof(int));
+	if (q2m.msg_out_length == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n",
+				sizeof(int));
+		return -2; // TODO: define retval for errors
+	}
+
+	q2m.msg_out_length[0] = q2_pkt_header_len
+					 + q2_pkt_servers_len
+					 + (q2m.num_servers * 6);
+	DEBUG("%d + %d + (%d * 6) = %d\n",
+		q2_pkt_header_len, q2_pkt_servers_len,
+		q2m.num_servers, q2m.msg_out_length[0]);
+
+	// allocate memory for packet pointers
+	q2m.msg_out = calloc(1, sizeof(char *));
+	if (q2m.msg_out == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n",
+				sizeof(char *));
+		return -2; // TODO: define retval for errors
+	}
+
+	// allocate memory for the packet itself
+	q2m.msg_out[0] = calloc(q2m.msg_out_length[0]+1, 1);
+	if (q2m.msg_out == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n",
+				q2m.msg_out_length[0]+1);
+		return -2; // TODO: define retval for errors
+	}
+
+	// copy q2_pkt_header into the packet
+	memcpy(q2m.msg_out[0], q2_pkt_header, q2_pkt_header_len);
+	memcpy(q2m.msg_out[0]+q2_pkt_header_len, q2_pkt_servers, q2_pkt_servers_len);
+	msg_out_offset = q2_pkt_header_len + q2_pkt_servers_len;
+
+	// create the packet
+	for (i = 0; i < q2m.num_servers; i++) {
+		// append ip and port to msg_out
+		memcpy(q2m.msg_out[0]+msg_out_offset, &q2m.list[i].ip, 4);
+		msg_out_offset += 4;
+		memcpy(q2m.msg_out[0]+msg_out_offset, &q2m.list[i].port, 2);
+		msg_out_offset += 2;
+	}
+
+	q2m.num_msgs = 1;
+
+	// return status 1
+	// packet with server list is ready
+	return 1;
+} // process_query()
+
+
+static int
+process(char *packet, int packetlen)
+{
+	// check if packet is q2 related
+	if (strncmp(packet, q2_pkt_header, q2_pkt_header_len) == 0) {
+		DEBUG("Q2 protocol marker detected!\n");
+		// which packet did we receive?
+		if (strncmp(packet+q2_pkt_header_len, q2_pkt_heartbeat, q2_pkt_heartbeat_len) == 0) {
+			return process_heartbeat(packet);
+		} else if (strncmp(packet+q2_pkt_header_len, q2_pkt_shutdown, q2_pkt_shutdown_len) == 0) {
+			return process_shutdown(packet);
+		} else if (strncmp(packet+q2_pkt_header_len, q2_pkt_ping, q2_pkt_ping_len) == 0) {
+			return process_ping(packet);
+		}
+	} else if (strncmp(packet, q2_pkt_query, q2_pkt_query_len) == 0) {
+		return process_query(packet);
+	}
+	return -1; // invalid packet
+}
+
+void
+init_plugin(void)
+{
+	register_plugin(&q2m);
+}
+

+ 734 - 0
src/masterserver/plugins/libq3.c

@@ -0,0 +1,734 @@
+/* libq3.c: masterserver plugin for Quake3 servers. */
+/* Copyright (C) 2003  Andre' Schulz
+ * This file is part of masterserver.
+ *
+ * masterserver is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * masterserver is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with masterserver; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * The author can be contacted at chickenman@exhale.de
+ */
+/*
+ * vim:sw=4:ts=4
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <pthread.h>
+#include <sys/socket.h> // for socket() etc.
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <math.h>
+
+#include "../masterserver.h"
+
+#define HEARTBEAT_TIMEOUT 300
+
+// message of the day
+#define Q3M_MOTD "Insert MOTD here."
+
+// for logging stuff
+#undef LOG_SUBNAME
+#define LOG_SUBNAME "libq3" // logging subcategory description
+
+// q3 packet stuff
+const char	q3_pkt_header[]		= "\xff\xff\xff\xff";
+const int	q3_pkt_header_len	= 4;
+const char	q3_pkt_heartbeat[]	= "heartbeat QuakeArena-1\n";
+const int	q3_pkt_heartbeat_len= 23;
+const char	q3_pkt_getinfo[]	= "getinfo\n";
+const int	q3_pkt_getinfo_len	= 8;
+const char	q3_pkt_inforsp[]	= "infoResponse\n";
+const int	q3_pkt_inforsp_len	= 13;
+const char	q3_pkt_getstatus[]	= "getstatus ";
+const int	q3_pkt_getstatus_len= 10;
+const char	q3_pkt_statusrsp[]	= "statusResponse\n";
+const int	q3_pkt_statusrsp_len= 15;
+const char	q3_pkt_getsrv[]		= "getservers";
+const int	q3_pkt_getsrv_len	= 10;
+const char	q3_pkt_getsrvrsp[]	= "getserversResponse";
+const int	q3_pkt_getsrvrsp_len= 18;
+const char	q3_pkt_getmotd[]	= "getmotd";
+const int	q3_pkt_getmotd_len	= 7;
+const char	q3_pkt_motd[]		= "\xff\xff\xff\xffmotd \"challenge\\%d\\motd\\%s\\\"";
+const int	q3_pkt_motd_len		= 28;
+const char	q3_pkt_footer[]		= "\\EOT";
+const int	q3_pkt_footer_len	= 4;
+
+const char q3m_plugin_version[] = "0.8";
+static port_t q3m_ports[] = {	{ IPPROTO_UDP, 27950 },	// master
+								{ IPPROTO_UDP, 27951 },	// motd
+								// { IPPROTO_UDP, 27952 }, // auth
+							};
+
+// player info
+typedef struct {
+	int score;
+	int ping;
+	char *name;
+} q3m_player_data_t;
+
+// q3 plugin private data
+typedef struct {
+	// statusResponse vars
+	int challenge;
+	int sv_punkbuster;	// 0 | 1
+	int g_maxGameClients;
+	int capturelimit;
+	int sv_maxclients;	// max num of clients
+	int timelimit;
+	int fraglimit;
+	int dmflags;		// bit field
+	int sv_maxPing;
+	int sv_minPing;
+	char *sv_hostname;	// server name
+	int sv_maxRate;
+	int sv_floodProtect; // 0 | 1
+	char *version;	// self explanatory
+	int g_gametype;	// 0 - FFA | 1 - Tournament | 2 - Single Player | 3 - TDM | 4 - CTF
+	int protocol;	// q3 network protocol version
+	char *mapname;	// self explanatory
+	int sv_privateClients;	// # of passworded player slots
+	int sv_allowDownload;	// 0 | 1
+	int bot_minplayers;
+	char *gamename;	// which mod
+	int g_needpass;	// 0 | 1
+	q3m_player_data_t *_player; // player info
+	// following is information not in packet
+	int _players; // # of players
+	int _challenge; // our challenge #
+} q3m_private_data_t;
+
+static void	info(void); // print information about plugin
+static void	free_privdata(void *);
+static int	process(char *, int); // process packet and return a value
+static int	process_getmotd(char *, int);
+static int	process_getservers(char *);
+static int	process_heartbeat(char *);
+static int	send_getstatus();
+static int	process_statusResponse(char *, int);
+static void	cleanup(void);
+void		init_plugin(void) __attribute__ ((constructor)); 
+
+static
+struct masterserver_plugin q3m
+= { "q3m",
+	q3m_plugin_version,
+	masterserver_version,
+	q3m_ports,
+	2,
+	HEARTBEAT_TIMEOUT,
+	&info,
+	&process,
+	&free_privdata,
+	&cleanup
+};
+
+static void
+info(void)
+{
+	INFO("quake3 masterserver plugin v%s\n", q3m_plugin_version);
+	INFO("  compiled for masterserver v%s\n", masterserver_version);
+}
+
+static void
+free_privdata(void *data)
+{
+    int i;
+	q3m_private_data_t *privdata = (q3m_private_data_t *) data;
+
+	if (data == NULL) return;
+
+    free(privdata->sv_hostname);
+    free(privdata->version);
+    free(privdata->mapname);
+    free(privdata->gamename);
+    for (i = 0; i < privdata->_players; i++)
+        free(privdata->_player[i].name);
+    free(privdata->_player);
+	free(privdata);
+}
+
+static int
+process_heartbeat(char *packet)
+{
+	int server_dup = 0;
+	int time_diff, i;
+	serverlist_t *backup_ptr;
+
+	// first, check if server is already in our list
+	for (i = 0; i < q3m.num_servers; i++) {
+		if ((q3m.list[i].ip.s_addr == q3m.client.sin_addr.s_addr)
+				&& (q3m.list[i].port == q3m.client.sin_port)) {
+			DEBUG("duplicate server detected! (%s:%d)\n",
+					inet_ntoa(q3m.client.sin_addr), ntohs(q3m.client.sin_port));
+			server_dup = 1;
+			break;
+		}
+	}
+
+	INFO("heartbeat from %s:%d\n",
+			inet_ntoa(q3m.client.sin_addr), ntohs(q3m.client.sin_port));
+	// if not, then add it to the list
+	if (server_dup == 0) {
+		// server is not in our list so add its ip, port and a timestamp
+		q3m.list[q3m.num_servers].ip = q3m.client.sin_addr;
+		q3m.list[q3m.num_servers].port = q3m.client.sin_port;
+		q3m.list[q3m.num_servers].lastheartbeat = time(NULL);
+		DEBUG("this is server no.: %d | lastheartbeat: %d\n",
+				q3m.num_servers, q3m.list[q3m.num_servers].lastheartbeat);
+		// allocate memory for private data
+		q3m.list[q3m.num_servers].private_data = calloc(1, sizeof(q3m_private_data_t));
+
+		q3m.num_servers++;
+
+		DEBUG("reallocating server list (old size: %d -> new size: %d)\n",
+				q3m.num_servers * sizeof(serverlist_t),
+				(q3m.num_servers+1) * sizeof(serverlist_t));
+
+		// back up the current list pointer in case realloc() fails
+		backup_ptr = q3m.list;
+		q3m.list = (serverlist_t *) realloc(q3m.list, ((q3m.num_servers+1)*sizeof(serverlist_t)));
+		if (q3m.list == NULL) {
+			WARNING("realloc() failed trying to get %d bytes!\n",
+					(q3m.num_servers+1)*sizeof(serverlist_t));
+			// since the pointer is overwritten with NULL
+			// we'll recover by using the backup pointer
+			q3m.list = backup_ptr;
+			return -2;
+		} else DEBUG("reallocation successful\n");
+	} else {
+		time_diff = time(NULL) - q3m.list[i].lastheartbeat;
+		// if time_diff is 0 the server has shutdown (most likely)
+		if (time_diff == 0) {
+			INFO("server %s:%u is shutting down (time_diff %d)\n",
+					inet_ntoa(q3m.list[i].ip), ntohs(q3m.list[i].port),
+					time_diff);
+			delete_server(&q3m, i);
+			server_dup = 0;
+			return 2; // return "server-shutdown" code
+		} else {
+			// server is in already in our list so we just update the timestamp
+			q3m.list[i].lastheartbeat = time(NULL);
+			server_dup = 0;
+		}
+	}
+	// server added/updated
+	return 1;
+}
+
+static int
+send_getstatus()
+{
+	int challenge, i;
+
+	// create challenge number
+	challenge = rand();
+	DEBUG("challenge: %d\n", challenge);
+
+	// prepare q3m.msg_out
+	q3m.num_msgs = 1;
+	q3m.msg_out_length = calloc(1, sizeof(int));
+	if (q3m.msg_out_length == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n", sizeof(int));
+		return -2; // TODO: define retval for errors
+	}
+	DEBUG("allocated %d bytes for msg_out_length[]\n", sizeof(int));
+
+	// q3m.msg_out_length[0] = q3_pkt_header_len + q3_pkt_getstatus_len;
+	q3m.msg_out_length[0] = q3_pkt_header_len
+			+ q3_pkt_getstatus_len + (int)(sizeof(int)*2.5);
+
+	// allocate the memory for the outgoing packet
+	q3m.msg_out = calloc(1, sizeof(char *));
+	if (q3m.msg_out == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n", sizeof(char *));
+		return -2; // TODO: define retval for errors
+	}
+
+	q3m.msg_out[0] = calloc(q3m.msg_out_length[0]+1, 1);
+	if (q3m.msg_out[0] == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n",
+				q3m.msg_out_length[0]);
+		return -2; // TODO: define retval for errors
+	}
+	DEBUG("allocated %d bytes for msg_out[0]\n", q3m.msg_out_length[0]+1);
+
+	memcpy(q3m.msg_out[0], q3_pkt_header, q3_pkt_header_len);
+	memcpy(q3m.msg_out[0]+q3_pkt_header_len, q3_pkt_getstatus, q3_pkt_getstatus_len);
+	sprintf(q3m.msg_out[0]+q3_pkt_header_len+q3_pkt_getstatus_len, "%d", challenge);
+
+	// write challenge into serverlist
+	for (i = 0; i < q3m.num_servers; i++) {
+		if ((q3m.client.sin_addr.s_addr == q3m.list[i].ip.s_addr)
+				&& (q3m.client.sin_port == q3m.list[i].port)) {
+			((q3m_private_data_t *) q3m.list[i].private_data)->_challenge = challenge;
+			break;
+		}
+	}
+
+	return 1; // send "getstatus" packet
+}
+
+static int
+process_getservers(char *packet)
+{
+	int i, j, pkt_offset; // temp vars
+	int getsrv_protocol;
+	char *temp;
+	q3m_private_data_t *temp_priv_data;
+
+	INFO("getservers from %s:%u\n",
+			inet_ntoa(q3m.client.sin_addr), ntohs(q3m.client.sin_port));
+
+	// we need the protocol version from the packet so we parse it
+	temp = packet+q3_pkt_header_len+q3_pkt_getsrv_len+1;
+	getsrv_protocol = atoi(temp);
+	DEBUG("requested protocol is %d\n", getsrv_protocol);
+
+	// got the protocol version now we can assemble the outgoing packet(s)
+	DEBUG("assembling server list packet\n");
+
+	/*
+	 * packet assembler follows
+	 */
+
+	// walk the server list
+	for (i = j = 0; (j < q3m.num_servers) || (q3m.num_msgs == 0); i++) {
+		q3m.num_msgs++;
+
+		// allocate memory for the packets
+		q3m.msg_out = realloc(q3m.msg_out, q3m.num_msgs*sizeof(char *));
+		if (q3m.msg_out == NULL) {
+			ERRORV("malloc() failed to get %d bytes!\n", q3m.num_msgs*sizeof(char *));
+			return -2;
+		}
+		q3m.msg_out_length = realloc(q3m.msg_out_length, q3m.num_msgs*sizeof(int));
+		if (q3m.msg_out_length == NULL) {
+			ERRORV("malloc() failed to get %d bytes!\n", q3m.num_msgs*sizeof(int));
+			return -2;
+		}
+
+		// get memory for header and command
+		q3m.msg_out[i] = malloc(811);
+		if (q3m.msg_out[i] == NULL) {
+			ERROR("malloc() failed to get 811 bytes!\n");
+			return -2;
+		}
+
+		// write header and command into packet
+		memcpy(q3m.msg_out[i], q3_pkt_header, q3_pkt_header_len);
+		pkt_offset = q3_pkt_header_len;
+		memcpy(q3m.msg_out[i]+pkt_offset, q3_pkt_getsrvrsp, q3_pkt_getsrvrsp_len);
+		pkt_offset += q3_pkt_getsrvrsp_len;
+
+		for (; (j < q3m.num_servers) && (pkt_offset < 806); j++) {
+			temp_priv_data = (q3m_private_data_t *) q3m.list[j].private_data;
+			// if the protocol matches, write ip/port into the packet
+			if (temp_priv_data->protocol == getsrv_protocol) {
+				// copy data from server list into packet
+				memcpy(q3m.msg_out[i]+pkt_offset, "\\", 1);
+				pkt_offset++;
+				memcpy(q3m.msg_out[i]+pkt_offset, &q3m.list[j].ip, 4);
+				pkt_offset += 4;
+				memcpy(q3m.msg_out[i]+pkt_offset, &q3m.list[j].port, 2);
+				pkt_offset += 2;
+			}
+		} // for j < 112
+
+		// write footer
+		memcpy(q3m.msg_out[i]+pkt_offset, q3_pkt_footer, q3_pkt_footer_len);
+		pkt_offset += q3_pkt_footer_len;
+		q3m.msg_out[i][pkt_offset] = '\0';
+		q3m.msg_out_length[i] = pkt_offset;
+		DEBUG("q3m.msg_out_length[%d] = %d\n", i, pkt_offset);
+	}
+
+	// packet with server list is ready
+	return 1;
+}
+
+static int
+process_statusResponse(char *packet, int packetlen)
+{
+	char *varname = NULL, *value = NULL;
+	char *score = NULL, *ping = NULL, *name = NULL;
+	int i;
+	int server_dup = 0, done = 0;
+	char *packetend = packet+packetlen;
+	q3m_private_data_t *private_data = calloc(1, sizeof(q3m_private_data_t));
+	q3m_private_data_t *oldprivdata;
+
+	// check if source address is known
+	for (i = 0; i < q3m.num_servers; i++) {
+		if ((q3m.client.sin_addr.s_addr == q3m.list[i].ip.s_addr)
+				&& (q3m.client.sin_port == q3m.list[i].port)) {
+			server_dup = 1;
+			break;
+		}
+	}
+
+	// source address not known
+	if (server_dup == 0) {
+		WARNING("unexpected \"statusResponse\" from %s:%d ignored\n",
+				inet_ntoa(q3m.client.sin_addr), ntohs(q3m.client.sin_port));
+		return -1;
+	}
+
+	oldprivdata = (q3m_private_data_t *)q3m.list[i].private_data;
+
+	// go to 1st "\" which is after the command string
+	packet = strpbrk(packet, "\\");
+	if (packet == NULL) {
+		WARNING("malformed statusResponse packet received from %s:%d!\n",
+			inet_ntoa(q3m.client.sin_addr), ntohs(q3m.client.sin_port));
+		return -1;
+	}
+
+	DEBUG("begin parsing server info\n");
+	while ((++packet < packetend) && !done)
+	{
+		// get variable name
+		varname = packet;
+
+		// go to next delimiter
+		packet = strpbrk(packet, "\\");
+		if (packet == NULL) {
+			ERRORV("malformed statusResponse packet received from %s:%d!\n",
+				inet_ntoa(q3m.client.sin_addr), ntohs(q3m.client.sin_port));
+			return -2;
+		}
+		// overwrite delimiter with \0
+		*packet = '\0';
+
+		// get value
+		value = ++packet;
+
+		// go to next delimiter
+		packet = strpbrk(packet, "\\\n");
+		if (packet == NULL) {
+			ERRORV("malformed statusResponse packet received from %s:%d!\n",
+				inet_ntoa(q3m.client.sin_addr), ntohs(q3m.client.sin_port));
+			return -2;
+		}
+
+		// check if we're at the end of the server info section
+		if (*packet == '\n') done = 1;
+		// overwrite delimiter with \0
+		*packet = '\0';
+
+		DEBUG("varname = \"%s\", value = \"%s\"\n", varname, value);
+		// parse varname and assign the value to the struct
+		if (strcmp(varname, "challenge") == 0) {
+			private_data->challenge = atoi(value);
+		} else if (strcmp(varname, "sv_punkbuster") == 0) {
+			private_data->sv_punkbuster = atoi(value);
+		} else if (strcmp(varname, "g_maxGameClients") == 0) {
+			private_data->g_maxGameClients = atoi(value);
+		} else if (strcmp(varname, "capturelimit") == 0) {
+			private_data->capturelimit = atoi(value);
+		} else if (strcmp(varname, "sv_maxclients") == 0) {
+			private_data->sv_maxclients = atoi(value);
+		} else if (strcmp(varname, "timelimit") == 0) {
+			private_data->timelimit = atoi(value);
+		} else if (strcmp(varname, "fraglimit") == 0) {
+			private_data->fraglimit = atoi(value);
+		} else if (strcmp(varname, "dmflags") == 0) {
+			private_data->dmflags = atoi(value);
+		} else if (strcmp(varname, "sv_maxPing") == 0) {
+			private_data->sv_maxPing = atoi(value);
+		} else if (strcmp(varname, "sv_minPing") == 0) {
+			private_data->sv_minPing = atoi(value);
+		} else if (strcmp(varname, "sv_hostname") == 0) {
+			private_data->sv_hostname = strdup(value);
+			if (private_data->sv_hostname == NULL) {
+				ERRORV("strdup() failed to get %d bytes!\n", strlen(value)+1);
+				return -2;
+			}
+		} else if (strcmp(varname, "sv_maxRate") == 0) {
+			private_data->sv_maxRate = atoi(value);
+		} else if (strcmp(varname, "sv_floodProtect") == 0) {
+			private_data->sv_floodProtect = atoi(value);
+		} else if (strcmp(varname, "version") == 0) {
+			private_data->version = strdup(value);
+			if (private_data->version == NULL) {
+				ERRORV("strdup() failed to get %d bytes!\n", strlen(value)+1);
+				return -2;
+			}
+		} else if (strcmp(varname, "g_gametype") == 0) {
+			private_data->g_gametype = atoi(value);
+		} else if (strcmp(varname, "protocol") == 0) {
+			private_data->protocol = atoi(value);
+		} else if (strcmp(varname, "mapname") == 0) {
+			private_data->mapname = strdup(value);
+			if (private_data->mapname == NULL) {
+				ERRORV("strdup() failed to get %d bytes!\n", strlen(value)+1);
+				return -2;
+			}
+		} else if (strcmp(varname, "sv_privateClients") == 0) {
+			private_data->sv_privateClients = atoi(value);
+		} else if (strcmp(varname, "sv_allowDownload") == 0) {
+			private_data->sv_allowDownload = atoi(value);
+		} else if (strcmp(varname, "bot_minplayers") == 0) {
+			private_data->bot_minplayers = atoi(value);
+		} else if (strcmp(varname, "gamename") == 0) {
+			private_data->gamename = strdup(value);
+			if (private_data->gamename == NULL) {
+				ERRORV("calloc() failed to get %d bytes!\n", strlen(value)+1);
+				return -2;
+			}
+		} else if (strcmp(varname, "g_needpass") == 0) {
+			private_data->g_needpass = atoi(value);
+		} //else {
+			// WARNING("unknown option \"%s\" in statusResponse ignored\n", varname);
+		//}
+	}
+	DEBUG("end parsing server info\n");
+
+	// parse player info
+	private_data->_players = 0;
+	while (++packet < packetend) {
+		// FIXME: recover from realloc() failure
+		private_data->_player = (q3m_player_data_t *) realloc(private_data->_player,
+				(private_data->_players+1)*sizeof(q3m_player_data_t));
+		if (private_data->_player == NULL) {
+			ERRORV("realloc() failed trying to get %d bytes!\n",
+					private_data->_players*sizeof(q3m_player_data_t));
+			return -2;
+		}
+
+		// get player score
+		score = packet;
+
+		// go to next delimiter
+		if ((packet = strpbrk(packet, " ")) == NULL) {
+			ERRORV("malformed statusResponse packet received from %s:%d!\n",
+				inet_ntoa(q3m.client.sin_addr), ntohs(q3m.client.sin_port));
+			return -2;
+		}
+		// overwrite delimiter
+		*packet = '\0';
+
+		// parse player score
+		private_data->_player[private_data->_players].score = atoi(score);
+
+		// get player ping
+		ping = ++packet;
+
+		// go to next delimiter
+		if ((packet = strpbrk(packet, " ")) == NULL) {
+			ERRORV("malformed statusResponse packet received from %s:%d!\n",
+				inet_ntoa(q3m.client.sin_addr), ntohs(q3m.client.sin_port));
+			return -2;
+		}
+		// overwrite delimiter
+		*packet = '\0';
+
+		// parse player ping
+		private_data->_player[private_data->_players].ping = atoi(ping);
+
+		// get player name
+		name = ++packet;
+
+		// go to next delimiter
+		if ((packet = strpbrk(packet, "\n")) == NULL) {
+			ERRORV("malformed statusResponse packet received from %s:%d!\n",
+				inet_ntoa(q3m.client.sin_addr), ntohs(q3m.client.sin_port));
+			return -2;
+		}
+		// overwrite delimiter
+		*packet = '\0';
+
+		// parse player name
+		private_data->_player[private_data->_players].name = strdup(name);
+		if (private_data->_player[private_data->_players].name == NULL) {
+			ERRORV("strdup() failed to get %d bytes!\n",
+					strlen(name)+1);
+			return -2;
+		}
+
+		DEBUG("player #%d name: \"%s\", ping: %d, score: %d\n",
+			private_data->_players, private_data->_player[private_data->_players].name,
+			private_data->_player[private_data->_players].ping,
+			private_data->_player[private_data->_players].score);
+
+		private_data->_players++;
+	}
+
+	// compare challenge to ours
+	if (private_data->challenge != oldprivdata->_challenge) {
+		WARNING("statusResponse challenge mismatch (%d != %d)\n",
+				private_data->challenge, oldprivdata->_challenge);
+		free_privdata(private_data);
+		return -1;
+	}
+
+	// if we already have parsed server/player info we have to free it first
+	if (q3m.list[i].private_data != NULL) free_privdata(q3m.list[i].private_data);
+	q3m.list[i].private_data = private_data;
+
+	return 0;
+}
+
+static int
+process_getmotd(char *packet, int packetlen)
+{
+	char *version, *renderer, *challenge;
+	char *varname = NULL, *value = NULL;
+	char *packetend = packet+packetlen;
+
+	packet += q3_pkt_header_len+q3_pkt_getmotd_len+2;
+
+	while ((++packet < packetend) && (*packet != '\x0a')) {
+		// save position as variable name
+		varname = packet;
+
+		// go to next delimiter
+		packet = strpbrk(packet, "\\");
+		if (packet == NULL) {
+			WARNING("invalid \"getmotd\" from %s:%d received; ignored\n",
+					inet_ntoa(q3m.client.sin_addr),
+					ntohs(q3m.client.sin_port));
+			return -1;
+		}
+
+		// overwrite delimiter with \0
+		*packet = '\0';
+
+		// save next position as value
+		value = ++packet;
+
+		// go to next delimiter
+		packet = strpbrk(packet, "\\\"");
+		if (packet == NULL) {
+			WARNING("invalid \"getmotd\" from %s:%d received; ignored\n",
+					inet_ntoa(q3m.client.sin_addr),
+					ntohs(q3m.client.sin_port));
+			return -1;
+		}
+
+		// overwrite delimiter with \0
+		*packet = '\0';
+
+		// parse
+		if (strcmp(varname, "version") == 0) {
+			version = strdup(value);
+			if (version == NULL) {
+				ERRORV("strdup() failed to get %d bytes!\n", strlen(value)+1);
+				return -2;
+			}
+		} else if (strcmp(varname, "renderer") == 0) {
+			renderer = strdup(value);
+			if (renderer == NULL) {
+				ERRORV("strdup() failed to get %d bytes!\n", strlen(value)+1);
+				return -2;
+			}
+		} else if (strcmp(varname, "challenge") == 0) {
+			challenge = strdup(value);
+			if (challenge == NULL) {
+				ERRORV("strdup() failed to get %d bytes!\n", strlen(value)+1);
+				return -2;
+			}
+		} else {
+			WARNING("unknown variable \"%s\" in \"getmotd\" packet ignored\n",
+					varname);
+		}
+	}
+
+	INFO("getmotd from %s:%d running \"%s\" with a \"%s\"\n",
+			inet_ntoa(q3m.client.sin_addr), ntohs(q3m.client.sin_port),
+			version, renderer);
+
+	// we got all we need to assemble the motd packet
+	q3m.msg_out = calloc(1, sizeof(char *));
+	if (q3m.msg_out == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n",
+				sizeof(char *));
+		return -2;
+	}
+	q3m.msg_out_length = calloc(1, sizeof(int));
+	if (q3m.msg_out_length == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n",
+				sizeof(int));
+		return -2;
+	}
+	q3m.msg_out_length[0] = q3_pkt_header_len+q3_pkt_motd_len
+							+ strlen(Q3M_MOTD)
+							+ (int)(sizeof(int)*2.5);
+	q3m.msg_out[0] = calloc(q3m.msg_out_length[0]+1, 1);
+	if (q3m.msg_out[0] == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n",
+				q3m.msg_out_length[0]);
+		return -2;
+	}
+	q3m.num_msgs = 1;
+	sprintf(q3m.msg_out[0], q3_pkt_motd, atoi(challenge), Q3M_MOTD);
+
+	// clean up
+	free(version);
+	free(renderer);
+	free(challenge);
+
+	return 1;
+}
+
+static int
+process(char *packet, int packetlen)
+{
+	int retval;
+
+	// check if packet is q3a related
+	if (strncmp(packet, q3_pkt_header, q3_pkt_header_len) == 0) {
+		DEBUG("Q3A protocol marker detected!\n");
+		// which packet did we receive?
+		if (strcmp(packet+q3_pkt_header_len, q3_pkt_heartbeat) == 0) {
+			retval = process_heartbeat(packet);
+			if (retval == 1) {
+				send_getstatus();
+				return 1; // "send packet" code
+			}
+			return retval;
+		} else if (strncmp(packet+q3_pkt_header_len, q3_pkt_getsrv, q3_pkt_getsrv_len) == 0) {
+			return process_getservers(packet);
+		} else if (strncmp(packet+q3_pkt_header_len, q3_pkt_statusrsp, q3_pkt_statusrsp_len) == 0) {
+			return process_statusResponse(packet, packetlen);
+		} else if (strncmp(packet+q3_pkt_header_len, q3_pkt_getmotd, q3_pkt_getmotd_len) == 0) {
+			return process_getmotd(packet, packetlen);
+		}
+		WARNING("unknown packet received!\n");
+		return -1;
+	} // end if for 0xff 0xff 0xff 0xff marker
+	WARNING("invalid packet received: Q3A protocol marker missing!\n");
+	return -1; // invalid packet
+}
+
+static void
+cleanup(void)
+{
+	int i;
+
+	if (q3m.num_servers > 0) {
+		for (i = 0; i < q3m.num_servers; i++) {
+			free_privdata(q3m.list[i].private_data);
+		}
+	}
+}
+
+void
+init_plugin(void)
+{
+	register_plugin(&q3m);
+}
+

+ 379 - 0
src/masterserver/plugins/libqw.c

@@ -0,0 +1,379 @@
+/* libqw.c: masterserver plugin for QuakeWorld servers. */
+/* Copyright (C) 2003  Andre' Schulz
+ * This file is part of masterserver.
+ *
+ * masterserver is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * masterserver is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with masterserver; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * The author can be contacted at chickenman@exhale.de
+ */
+/*
+ * vim:sw=4:ts=4
+ */
+
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <sys/socket.h> // for socket() etc.
+#include <netinet/in.h>
+#include <arpa/inet.h>
+
+#include "../masterserver.h"
+
+#define HEARTBEAT_TIMEOUT 300
+
+// for logging stuff
+#undef LOG_SUBNAME
+#define LOG_SUBNAME "libqw" // logging subcategory description
+
+// qw packet stuff
+// many thanks go to id software and quakeforge
+const char	qw_pkt_header[]		= "\xff\xff\xff\xff";
+const int	qw_pkt_header_len	= 4;
+const char	qw_pkt_heartbeat[]	= "a"; // more data
+const int	qw_pkt_heartbeat_len= 1;
+const char	qw_pkt_shutdown[]	= "C";
+const int	qw_pkt_shutdown_len	= 1;
+const char	qw_pkt_slistreq[]	= "c";
+const int	qw_pkt_slistreq_len	= 1;
+const char	qw_pkt_slistrep[]	= "d\n"; // more data
+const int	qw_pkt_slistrep_len	= 2;
+const char	qw_pkt_ping[]		= "k";
+const int	qw_pkt_ping_len		= 1;
+const char	qw_pkt_ack[]		= "l";
+const int	qw_pkt_ack_len		= 1;
+const char	qw_pkt_nack[]		= "m";
+const int	qw_pkt_nack_len		= 1;
+
+// extended stuff
+/*
+const char	qw_pkt_connreq[]	= "b\n%s\n%s\n%s\n"; // connection request
+const int	qw_pkt_connreq_len	= 2;
+const char	qw_pkt_nuser_req[]	= "e\n%s\n"; // new user
+const int	qw_pkt_nuser_req_len= 2;
+const char	qw_pkt_nuser_rep[]	= "f\n%d"; // new user reply
+const int	qw_pkt_nuser_rep_len= 2;
+const char	qw_pkt_connreqs[]	= "i\n%s:%d\n%d\n%s\n";
+const int	qw_pkt_connreqs_len	= 2;
+const char	qw_pkt_userp_req[]	= "o\n%s\n%s";
+const int	qw_pkt_userp_req_len= 2;
+const char	qw_pkt_userp_rep[]	= "p\n\\*userid\\%d%s";
+const int	qw_pkt_userp_rep_len= 2;
+const char	qw_pkt_setinfo[]	= "r\n%s\n%s\n%s\n%s\n";
+const int	qw_pkt_setinfo_len	= 2;
+const char	qw_pkt_seen_req[]	= "u\n%s\n";
+const int	qw_pkt_seen_req_len	= 2;
+const char	qw_pkt_seen_rep[]	= "v\n%s";
+const int	qw_pkt_seen_rep_len	= 2;
+const char	qw_pkt_clientcmd[]	= "B"; // more data
+const int	qw_pkt_clientcmd_len= 1;
+const char	qw_pkt_print[]		= "n"; // more data
+const int	qw_pkt_print_len	= 1;
+const char	qw_pkt_echo[]		= "e"; // more data?
+const int	qw_pkt_echo_len		= 1;
+*/
+
+const char qwm_plugin_version[] = "0.2";
+static port_t qwm_ports[] = { { IPPROTO_UDP, 27000 } };
+
+// player info
+typedef struct {
+	int score;
+	int ping;
+	char *name;
+} qwm_player_data_t;
+
+// q3 plugin private data
+typedef struct {
+	// statusResponse vars
+	int fraglimit;
+	int timelimit;
+	int teamplay;
+	int samelevel;
+	int maxspectators;
+	int deathmatch;
+	int spawn;
+	int watervis;
+	char *version;
+	char *progs;
+
+	qwm_player_data_t *_player; // player info
+	// following is information not in packet
+	int _players; // # of players
+} qwm_private_data_t;
+
+static void	info(void); // print information about plugin
+static int	process(char *, int); // process packet and return a value
+static int	process_heartbeat(char *);
+static int	process_slistreq();
+static int	process_ping();
+static int	process_shutdown();
+//static void cleanup(void);
+void		init_plugin(void) __attribute__ ((constructor));
+
+static
+struct masterserver_plugin qwm
+= { "qwm",
+	qwm_plugin_version,
+	masterserver_version,
+	qwm_ports,
+	1,
+	HEARTBEAT_TIMEOUT,
+	&info,
+	&process,
+	NULL,	// free_privdata()
+	NULL	// cleanup()
+};
+
+static void
+info(void)
+{
+	INFO("quakeworld masterserver plugin v%s\n", qwm_plugin_version);
+	INFO("  compiled for masterserver v%s\n", masterserver_version);
+}
+
+static int
+process_heartbeat(char *packet)
+{
+	int server_dup = 0;
+	int time_diff, i;
+
+	// first, check if server is already in our list
+	for (i = 0; i < qwm.num_servers; i++) {
+		if ((qwm.list[i].ip.s_addr == qwm.client.sin_addr.s_addr)
+				&& (qwm.list[i].port == qwm.client.sin_port)) {
+			DEBUG("duplicate server detected! (%s:%d)\n",
+					inet_ntoa(qwm.client.sin_addr), ntohs(qwm.client.sin_port));
+			server_dup = 1;
+			break;
+		}
+	}
+
+	INFO("heartbeat from %s:%u\n",
+			inet_ntoa(qwm.client.sin_addr), ntohs(qwm.client.sin_port));
+	// if not, then add it to the list
+	if (!server_dup) {
+		qwm.list[qwm.num_servers].ip = qwm.client.sin_addr;
+		qwm.list[qwm.num_servers].port = qwm.client.sin_port;
+		qwm.list[qwm.num_servers].lastheartbeat = time(NULL);
+		DEBUG("this is server no.: %d | lastheartbeat: %d\n",
+				qwm.num_servers, qwm.list[qwm.num_servers].lastheartbeat);
+		// allocate memory for private data
+		// XXX: disabled for now
+		//qwm.list[qwm.num_servers].private_data = calloc(1, sizeof(qwm_private_data_t));
+
+		qwm.num_servers++;
+
+		DEBUG("reallocating server list (old size: %d -> new size: %d)\n",
+				qwm.num_servers * sizeof(serverlist_t),
+				(qwm.num_servers+1) * sizeof(serverlist_t));
+		qwm.list = (serverlist_t *) realloc(qwm.list, ((qwm.num_servers+1)*sizeof(serverlist_t)));
+		if (qwm.list == NULL) {
+			//WARNING("can't increase qwm.list size; out of memory!\n");
+			ERRORV("realloc() failed trying to get %d bytes!\n",
+					(qwm.num_servers+1)*sizeof(serverlist_t));
+			// since the pointer is overwritten with NULL
+			// we can't recover; so just exit here
+			// XXX: maybe save the old pointer somewhere so
+			//		we can continue?
+			// FIXME: don't pthread_exit() here instead return -3 or so
+			pthread_exit((void *) -1);
+		} else DEBUG("reallocation successful\n");
+	} else {
+		time_diff = time(NULL) - qwm.list[i].lastheartbeat;
+		// server is in already in our list so we just update the timestamp
+		qwm.list[i].lastheartbeat = time(NULL);
+	}
+	// server added/updated
+	return 0;
+}
+
+static int
+process_slistreq()
+{
+	int i, pkt_offset, pkt_servers = 0; // temp vars
+	//qwm_private_data_t *temp_priv_data;
+
+	INFO("slist_req from %s:%u\n",
+			inet_ntoa(qwm.client.sin_addr), ntohs(qwm.client.sin_port));
+
+	/*
+	 * This is the new, badly documented packet assembler.
+	 */
+	qwm.msg_out = malloc(sizeof(char *));
+	if (qwm.msg_out == NULL) {
+		ERRORV("malloc() failed trying to get %d bytes!\n", sizeof(char *));
+		return -2;
+	}
+
+	qwm.msg_out_length = malloc(sizeof(int));
+	if (qwm.msg_out_length == NULL) {
+		ERRORV("malloc() failed trying to get %d bytes!\n", sizeof(int));
+		return -2;
+	}
+
+	qwm.msg_out_length[0] = qw_pkt_header_len + qw_pkt_slistrep_len
+							+ (qwm.num_servers*6);
+
+	// get memory for header and command
+	qwm.msg_out[0] = calloc(qwm.msg_out_length[0]+1, 1);
+	if (qwm.msg_out[0] == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n",
+				qwm.msg_out_length[0]+1);
+		return -2;
+	}
+
+	DEBUG("assembling server list packet\n");
+
+	// write header and command into packet
+	memcpy(qwm.msg_out[0], qw_pkt_header, qw_pkt_header_len);
+	pkt_offset = qw_pkt_header_len;
+	memcpy(qwm.msg_out[0]+pkt_offset, qw_pkt_slistrep, qw_pkt_slistrep_len);
+	pkt_offset += qw_pkt_slistrep_len;
+
+	for (i = 0; i < qwm.num_servers; i++) {
+		//temp_priv_data = (qwm_private_data_t *) qwm.list[i].private_data;
+		DEBUG("pkt_offset: %d\n", pkt_offset);
+
+		// copy data from server list into packet
+		memcpy(qwm.msg_out[0]+pkt_offset, &qwm.list[i].ip, 4);
+		pkt_offset += 4;
+		memcpy(qwm.msg_out[0]+pkt_offset, &qwm.list[i].port, 2);
+		pkt_offset += 2;
+		pkt_servers++;
+	}
+
+	DEBUG("pkt_offset: %d\n", pkt_offset);
+	qwm.num_msgs = 1;
+
+	// packet with server list is ready
+	return 1;
+}
+
+static int
+process_ping()
+{
+	INFO("ping from %s:%u\n",
+			inet_ntoa(qwm.client.sin_addr), ntohs(qwm.client.sin_port));
+
+	// prepare qwm.msg_out
+	qwm.num_msgs = 1;
+	qwm.msg_out_length = calloc(1, sizeof(int));
+	if (qwm.msg_out_length == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n", sizeof(int));
+		return -2; // TODO: define retval for errors
+	}
+	DEBUG("allocated %d bytes for msg_out_length[]\n", sizeof(int));
+
+	qwm.msg_out_length[0] = qw_pkt_header_len + qw_pkt_ack_len;
+
+	// allocate the memory for the outgoing packet
+	qwm.msg_out = calloc(1, sizeof(char *));
+	if (qwm.msg_out == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n", sizeof(char *));
+		return -2; // TODO: define retval for errors
+	}
+
+	qwm.msg_out[0] = calloc(qwm.msg_out_length[0]+1, 1);
+	if (qwm.msg_out[0] == NULL) {
+		ERRORV("calloc() failed trying to get %d bytes!\n",
+				qwm.msg_out_length[0]+1);
+		return -2; // TODO: define retval for errors
+	}
+	DEBUG("allocated %d bytes for msg_out[0]\n", qwm.msg_out_length[0]);
+
+	memcpy(qwm.msg_out[0], qw_pkt_header, qw_pkt_header_len);
+	memcpy(qwm.msg_out[0]+qw_pkt_header_len, qw_pkt_ack, qw_pkt_ack_len);
+
+	return 1; // send "ack" packet
+}
+
+static int
+process_shutdown()
+{
+	int i, time_diff, server_dup = 0;
+
+	for (i = 0; i < qwm.num_servers; i++) {
+		if ((qwm.list[i].ip.s_addr == qwm.client.sin_addr.s_addr)
+				&& (qwm.list[i].port == qwm.client.sin_port)) {
+			server_dup = 1;
+			break;
+		}
+	}
+
+	if (server_dup) {
+		time_diff = time(NULL) - qwm.list[i].lastheartbeat;
+		INFO("%s:%u is shutting down (time_diff %d)\n",
+			inet_ntoa(qwm.list[i].ip), ntohs(qwm.list[i].port), time_diff);
+		delete_server(&qwm, i);
+		return 2; // return "server shutdown" code
+	} else return -1; // invalid packet
+} // process_shutdown()
+
+
+
+static int
+process(char *packet, int packetlen)
+{
+	switch(packet[0]) {
+		// which packet did we receive?
+		case 'a':
+			return process_heartbeat(packet);
+			break;
+		case 'c':
+			return process_slistreq();
+			break;
+		case 'k':
+			return process_ping();
+			break;
+		case 'C':
+			return process_shutdown();
+			break;
+		default:
+			WARNING("unknown packet received!\n");
+			return -1;
+	} // end switch()
+}
+
+/*
+static void
+cleanup(void)
+{
+	int i, j;
+	qwm_private_data_t *tmp_privdata;
+
+	if (qwm.num_servers > 0) {
+		for (i = 0; i < qwm.num_servers; i++) {
+			tmp_privdata = (qwm_private_data_t *) qwm.list[i].private_data;
+			for (j = 0; j < tmp_privdata->_players; j++)
+				free(tmp_privdata->_player[j].name);
+			free(tmp_privdata->_player);
+			free(tmp_privdata->version);
+			free(tmp_privdata->mapname);
+			free(tmp_privdata->gamename);
+			free(tmp_privdata->sv_hostname);
+			free(tmp_privdata);
+			free(qwm.list[i].private_data);
+		}
+	}
+}*/
+
+void
+init_plugin(void)
+{
+	register_plugin(&qwm);
+}
+

BIN
src/orig_archives/masterserver-0.4.1.tar.bz2


+ 1 - 0
src/orig_archives/masterserver-0.4.1.tar.bz2.md5

@@ -0,0 +1 @@
+5adf9779e95f911784645bd09ecdb98e  masterserver-0.4.1.tar.bz2