#!/usr/bin/tcl
#
# hlcmd - A command-line interface to hlds_l
#
# NOTE: hlds_l must be started by halfd for this tool to function correctly.
#
# See LICENSE for license details.
#
# Run with no arguments for usage details
# Non-interactive mode useful for cron jobs, rc startup, etc.
#

######################################################################
# Standard procedures
#
proc checkForServer {} {
	global gameId fds svrInfo

	if [file exists $fds(pid)] {
		set fd [open $fds(pid) r]
		set pid [gets $fd]
		close $fd

		catch {wait -nohang}

		if [processExists $pid] {
			# Somebody's running on that process!
			if { ![file exists $fds(cmd)] || ![file exists $fds(log)] } {
				puts stderr "pid $pid exists, but cmd pipe/log file are missing."
				puts stderr "Please make sure hlds_l is not running!"
				exit 1
			}
		} else {
			# hlcmd may be run as another user, so check for "not owner"
#			if ![regexp -nocase "not owner" $err] {
				serverDown
				return
#			}
		}

		statusBar "Found a running server, connecting..."
		update idletasks

		set fds(pidnum) $pid
		set fds(cmdfd) [open $fds(cmd) w]
		set fds(logfd) [open $fds(log) r]
		seek $fds(logfd) 0 end
		fcntl $fds(cmdfd) CLOEXEC
		fcntl $fds(cmdfd) NOBUF
		fcntl $fds(logfd) CLOEXEC

		set svrInfo(reconnect) 1
		#getServerStatus 1 1
		serverUp
	} else {
		set svrInfo(reconnect) 0
		serverDown
	}
}
			

proc stopServer { {running 1} {wait 29} } {
        global fds vote

	catch {wait -nohang}
        if $running {
	catch {
		puts $fds(cmdfd) quit
		flush $fds(cmdfd)
	}
        }

        # Wait for it to die
        set i 0
        while { $i < $wait } {
                catch {wait -nohang}
                if ![processExists $fds(pidnum)] break
                incr i
                update idletasks
                sleep 1
        }

        if [cequal $i [expr $wait + 1]] {
                # Kill it
                catch {kill 2 $fds(pidnum)}
                sleep 1
                catch {kill 9 $fds(pidnum)}
                catch {wait -nohang}
                .alert.text configure -text \
                        "Unclean shutdown at [clock format [clock clicks]]"
        }
        serverDown

        statusBar "Server stopped : [now]"
}

proc processExists { pid } {
	global errorCode

	if [catch {kill 0 $pid} err] {
		set code [lindex $errorCode 1]
		if ![cequal $code ESRCH] {
			return 1
		} else {
			return 0
		}
	}

	return 1
}

proc say { text } {
	global fds
	puts $fds(cmdfd) "say $text"
	flush $fds(cmdfd)

	statusBar "Said: $text"
}

proc execCmd {{cmd ""}} {
	global fds 

	if [cequal $cmd ""] return

	puts $fds(cmdfd) $cmd
	flush $fds(cmdfd)

	statusBar "Sent '$cmd' to server"

	if {[cequal $cmd quit] || [cequal $cmd exit]} {
		stopServer 0
	}
}

proc readConfig { {init 0} } {
	global config text gameId myhldir fds oldfoulprefix cfgPostfix plat
	global svrInfo args env

	if [catch { open $myhldir/$gameId/addons/halfd/halfd.cfg$cfgPostfix r } fd] {
		if [info exists fds(logfd)] {
			myEcho ERR [format $text(30) $fd]
			myExit 1
		} else {
			puts stdout [format $text(81) $fd]
			exit 1
		}
	}

	defaultConfig

	readConfigFile $fd
	close $fd

	if ![catch { open $myhldir/halfd.cfg r } fd] {
		if [info exists fds(mylogfd)] { myEcho INFO $text(197) }
		readConfigFile $fd
		close $fd
	} else {
		if [info exists fds(mylogfd)] { myEcho DBUG "Global config file not found: $fd" }
	}

	# Arguments always override halfd.cfg
	if { $config(debug) < $args(debug) } {
		set config(debug) $args(debug)
	}

	if { $config(console) < $args(console) } {
		set config(console) $args(console)
	}

	# Fix newapisep - look for 0x*, this will be the actual character
	if [cequal [csubstr $config(newapisep) 0 2] 0x] {
		if [catch { format %c $config(newapisep) } sep] {
			myEcho ERR [format $text(311) $sep]
			myEcho ERR $text(312)
			set config(newapisep) [format %c 0x09]
		} else {
			set config(newapisep) $sep
		}
	}

	if [info exists fds(mylogfd)] { validateConfig }

	# Fix foul-prefix
	if ![cequal $config(foulprefix) ""] {
		if [cequal [string first " " $config(foulprefix)] -1] {
			set pfx " $config(foulprefix)"
		} else {
			set pfx $config(foulprefix)
		}
		regsub -all ""  $pfx "\\" pfx
		regsub -all " " $pfx " \*" pfx
		set config(foulprefix) "\[$pfx\]"
	}

	# Fix halfbot-prefix
	if ![cequal $config(halfbotprefix) ""] {
		set pfx " $config(halfbotprefix)"
		regsub -all ""  $pfx "\\" pfx
		regsub -all " " $pfx " \*" pfx
		set config(halfbotprefix) "\[$pfx\]"
	}

	if { ![cequal $oldfoulprefix $config(foulprefix)] && !$init } {
		set oldfoulprefix $config(foulprefix)
		readBadwords
	} else {
		set oldfoulprefix $config(foulprefix)
	}

	if ![cequal $config(testplat) ""] {
		set plat $config(testplat)
		myEcho INFO "Forcing platform to '$plat'.  I hope you know what you're doing!"
	}

	if { !$init && $config(plugins) } registerPlugins

	# Process 'envvars'
	if ![cequal $config(envvars) ""] {
		foreach envvar $config(envvars) {
			lassign [split $envvar =] var value
			set env($var) $value
			myEcho INFO [format $text(345) $var $value]
		}
	}
}

proc defaultConfig {} {
	global config

	# Clear the array
	foreach id [array names config] { 
		if [cequal $id admininstalled] continue
		unset config($id)
	}

	# Set up the defaults
	set config(rs)         1	;# Restart y/n
	set config(rsDelay)    10	;# Restart Delay
	set config(cm)         1	;# Execute custom map.cfg y/n
	set config(refresh)    60	;# Refresh timeout

	set config(vote)       1	;# Voting enabled y/n
	set config(autovote)   1	;# Voting sessions start automatically?
	set config(autovotetime)   60	;# Auto-vote starts (votetime+autovotetime) secs
	set config(votetime)   180	;# Number of seconds in a voting session
	set config(minvotes)   4	;# Min # of votes to win (unless majority)
	set config(votekw)     ""	;# Voting keyword
	set config(votekwtime) 0 	;# # of seconds after map starts when votekw is allowed
	set config(votefreq)   90	;# Frequency of voting keyword
	set config(novotemaps) ""	;# Maps that can't be voted on
	set config(votemaps)   ""	;# Maps that can only be voted on
	set config(maxextend)  0	;# Maximum amount of time a map can be extended
	set config(voteprogress) 1	;# Display progress as players vote?

	set config(votetext)   {EXAMPLE: "vote nml" would vote for No Man's Land}

	set config(votemodnames) ""
	set config(votemoddirs)  ""
	set config(modmax)       0
	set config(modswitch)    1
	set config(modprimary)   ""

	set config(autostart)  1	;# Autostart hlds_run y/n
	set config(localmode)  0	;# Only accept connections from localhost
	set config(localadmin) 0	;# Only accept ADMINISTRATORs from localhost

	set config(port) 27015		;# Port hlds_run will listen on
	set config(dport) 3000		;# Port halfd will listen on
	set config(bindip) ""		;# IP address halfd will bind to

	set config(stats)     0	;# Execute stats after each match?
	set config(statspath) "./tfc/tfstats/tfstats_l";# Path to stats pgm
	set config(statsargs) "%L outputDir=/home/httpd/html/tfstats";# Stats args
	set config(statswait) 0
	set config(statsinbg) 0

	set config(serverMsg)      ""	;# Server Message
	set config(serverMsgTimes) ""	;# Times to display server msg
	set config(usrauth)        ""	;# USER password
	set config(admauth)        ""	;# ADMINISTRATOR password
	set config(newapiauth)     ""	;# ADMINISTRATOR password for NEWAPI
	set config(newapiusrauth)  ""	;# USER  password for NEWAPI
	set config(minhack)        0	;# Map time hack in in minutes
	set config(maplist)        30	;# Prevent 'maplist' spamming (seconds)
	set config(maplistdisplay) 20	;# Max amount of maps maplist will display
	set config(modlist)        30	;# Prevent 'modlist' spamming (seconds)
	set config(mapcycle)       30	;# Prevent 'mapcycle' spamming (seconds)
	set config(timeleft)       30	;# Prevent 'timeleft' spamming (seconds)
	set config(nextmap)        30	;# Prevent 'nextmap' spamming (seconds)
	set config(size)           30	;# Prevent 'size' spamming (seconds)
	set config(whattime)       30	;# Prevent 'whattime' spamming (seconds)
	set config(votestatus)     30	;# Prevent 'votestatus' spamming (seconds)
	set config(mypenalty)      30	;# Prevent 'mypenalty' spamming (seconds)

	set config(deleteLogs)     1	;# Delete empty log files (# kills)
	set config(deletecust)     0	;# Delete custom.hpk before launching server?

	set config(args) "+exec autoexec.cfg"	;# Args to pass to hlds_run
	set config(maps) ""						;# Builtin maps

	set config(autorotate) 0		;# Auto-rotate halfd.log?
	set config(rotatetype) 0		;# Rotate to .old or to .yymmddhhmm?

	set config(tkcheck)   0
	set config(sgtkcheck) 0
	set config(tkdecay)   3600
	set config(tkcounts)  "3 5 10"
	set config(tkargs) "-1 30 0"
	set config(tkpenalty) "kick banid banid"
	set config(tkwarnmsg) "%P has %C TK-warnings left before action is taken."
	set config(tkpenmsg) "%P gets %T for %C TKs!"
	set config(tkforgive) 1
	set config(tkroundlimit) 0
	set config(tkroundpenalty) 1

	set config(foulcheck)   0
	set config(foulname)    0
	set config(foulcounts)  3
	set config(fouldecay)   3600
	set config(foulargs)    0
	set config(foulpenalty) kick
	set config(foulwarnmsg) "%P has %C FOUL warnings left before %T."
	set config(foulpenmsg)	"%P gets %T for profanity. (%C)"
	set config(foulargs1)    30
	set config(foulpenalty1) banid
	set config(foulmsg1)    "%P is being kicked for using inappropriate language."
	set config(foulprefix) "\'\"\_"


	set config(debug) 0
	set config(tcldebug) 0

	set config(banlog) 0

	set config(fortune) 0
	set config(fortunepath) "/usr/games/fortune"
	set config(fortuneargs) "-s"

	set config(nic) ""

	set config(maxconns)   0
	set config(localconns) 1

	set config(getmodels) 0
	set config(getmodelsfromrole) 0

	set config(modesleep) 1

	set config(checkpass)  1	;# should we check to see if the server is pwd'd

	set config(exclude)    0	;# should we look for halfd.exclude?

	set config(getteam)    0	;# should we get and report on teams?

	set config(alwaysrunstats) 0;# Should stats always run even if no kills on a map?

	set config(adminlog) 0		;# Should we log administrator activity?

	# Status hacks :(
	set config(ipfromstats)  1	;# should we determine IP from 'status'?
	set config(statusfields) 9	;# how many fields does 'status' return
	set config(statuscmd)    "status";# what's the 'status' command to run?
	set config(vehiclehack)  0	;# Ignore vehicle-tk's in cs?

	# Weapon limits
	set config(weaponlimit) 0	;# Should we limit usage of weapons?
	set config(weapondefault) "";# What are default weapons if map-specific not there?
	set config(weaponmsg) "WEAPONS BANNED on %M are:"

	set config(weapondecay)   3600
	set config(weaponcounts)  1
	set config(weaponargs)    0
	set config(weaponpenalty) kick
	set config(weaponwarnmsg) "%P has %C weapon-warnings left before action is taken."
	set config(weaponpenmsg)  "%P gets %T for %C weapon violations!"

	set config(banip) 0

	set config(console) 0

	set config(pingcheck)   0
	set config(pingmin)     0
	set config(pingmax)     0
	set config(pingwarn)    3
	set config(pingact)     -1
	set config(pingmaxmsg)  "Pings above %G are not allowed.  %P leave or be kicked."
	set config(pingminmsg)  "Pings below %G are not allowed.  %P leave or be kicked."
	set config(pingactmsg)  "%P is being kicked for ping violations."
	set config(pingwarnmsg) "%P has %C more ping warnings before penalty."
	set config(pingpenmsg)  "%P gets %T for %C ping violations."
	set config(pingdecay)   0
	set config(pingcounts)  "3 5"
	set config(pingargs)  "0 0"
	set config(pingpenalty) "kick kick"

	set config(fahack) 0
	set config(oldhack) 0
	set config(oldprefix)  ""
	set config(oldpostfix) ""

	set config(checkwpt) 0
	set config(maxbots) 0

	set config(nullmap) 15

	set config(idle) 0
	set config(idlemsg) "%P has been idle too long."
	set config(idleaction) 0

	set config(leech) 0
	set config(leechmsg) "Please download maps from http://www.clanakb.org"
	set config(leechaction) 0

	set config(lowstatus) 1

	set config(hlbpaddr) localhost
	set config(hlbpport) 2000
	set config(hlbpuser) ""
	set config(hlbppass) ""
	set config(hlbpserveraddr) 192.168.1.4
	set config(hlbpserverport) 27015
	set config(hlbpwait) 30
	set config(hlbpoutput) 0
	set config(hlbpcommands) 1

	set config(saycmd)   say
	set config(sayquotes) 0
	set config(saysleep) 0

	set config(lastNlines) 5

	set config(testplat) ""

	set config(oldlogsdir) oldlogs

	set config(adminmod) 0

	set config(attackcheck)        0
	set config(attackdecay)        600
	set config(attackcounts)       "10 20 30 40"
	set config(attackargs)         "0 0 0 10"
	set config(attackpenalty)      "kick kick kick banid"
	set config(attackwarnmsg)      "%P has %C attack-warnings left before action is taken."
	set config(attackpenmsg)       "%P gets %T for %C attacks!"
	set config(attackforgive)      1
	set config(attackroundlimit)   0
	set config(attackroundpenalty) 1

	set config(suicidecheck)        0
	set config(suicidedecay)        60
	set config(suicidecounts)       "10 20 30 40"
	set config(suicideargs)         "0 0 0 10"
	set config(suicidepenalty)      "kick kick kick banid"
	set config(suicidewarnmsg)      "%P has %C suicide-warnings left before action is taken."
	set config(suicidepenmsg)       "%P gets %T for %C suicides!"
	set config(suicideteams)        ""

	set config(halfbot)			0
	set config(halfbotmax)		20
	set config(halfbotprefix)	"\'\"\_"
	set config(halfbotauth)		0

	set config(nullname)		0
	set config(nullpenalty)		banid
	set config(nullargs)		30

	set config(plugins)			0
	set config(pluginsource) 	halfd_plugin_example.tcl

	set config(teamcheck)		0
	set config(teamdecay)		10
	set config(teamcounts)		3
	set config(teamargs)		10
	set config(teampenalty)		banid
	set config(teamwarnmsg)		""
	set config(teampenmsg)		"%P gets %T for trying to crash the server!"

	set config(hltvwonid)		1448

	set config(allowsource) 	0

	set config(quiet) 			0

	set config(email) 			""
	set config(emailcmd) 		/bin/mail
	set config(emailtest) 		0

	set config(enforcepass) 	""

	set config(broadcastwarn) 	0
	set config(broadcastpen) 	1

	set config(getdeaths) 		0
	set config(senddeaths) 		1

	set config(checkserver) 	30

	set config(hlds_run) 		""
	set config(hlds_boost) 		0

	set config(rsattempts)		10

	set config(oldeventhandler)	0

	set config(hlbpreset)		0

	set config(initmap)			""

	set config(newapisep)      "\t"	;# Separator for new API
	set config(newapiquotesname)   0 ;# Wrap names with quotes
	set config(newapiquotestext)   0 ;# Wrap text with quotes?

	set config(taunts)   1 ;# Allows TAUNTSON and TAUNTSOFF commands

	set config(dodhack)  0 ;# Ignore second DoD chat message

	set config(hostagecheck)		0
	set config(hostagedecay)		600
	set config(hostagecounts)		"3 6"
	set config(hostageargs)			"0 10"
	set config(hostagepenalty)		"kick banid"
	set config(hostagewarnmsg)      "%P has %C hostage-kill-warnings left before %T."
	set config(hostagepenmsg)		"%P gets %T for killing hostages!"

	set config(hostageroundlimit)   0
	set config(hostageroundpenalty) 1

	set config(killszget)	0

	set config(ignoretkta) ""
	set config(votewinmsg) ""
	set config(voteignore2) 1

	set config(getteamfromuser) 0

	set config(fork) 0
	set config(forkport) 9000

	set config(envvars) ""

	set config(archivecore) 0

	set config(getstats) 1
	set config(maxsamples) 60
}


proc readConfigFile { fd } {
	global config

	# Read the file..
	while 1 {
		#set line [string trim [gets $fd]]
		set line [gets $fd]
		if [eof $fd] break
		if [cequal $line EOF] break

		if [cequal [csubstr $line 0 1] "#"] continue
		if [cequal [csubstr $line 0 1] "\["] continue
		if [cequal $line ""] continue

		lassign [parseConfigFile $line] key val
		set config($key) $val
	}
}

proc parseConfigFile { data } {
	global text config

	# Look for "key=value" pairs
	set list [split $data =]
	set len [llength $list]
	if { $len < 2 } {
		myEcho WARN $text(114)
		myEcho WARN '$data'
		return
	} elseif { $len > 2 } {
		# We have an "=" in the value...
		set key [lvarpop list]
		set val [join $list =]
	} else {
		lassign $list key val
	}

	set key [string trim $key]

	if ![cequal $key foulprefix] {
		set val [string trim $val]
	}

	return [list $key $val]
}


proc validateConfig {} {
	global config text penaltyList

	proc tf { num } {
		if { [cequal $num 1] || [cequal $num 0] } {
			return 1
		} else {
			return 0
		}
	}

	set validated 1

	# Set up a list of booleans
	lappend bools autostart
	lappend bools rs
	lappend bools localmode
	lappend bools localadmin
	lappend bools cm
	lappend bools deleteLogs
	lappend bools tkcheck
	lappend bools tkforgive
	lappend bools foulcheck
	lappend bools foulname
	lappend bools vote
	lappend bools autovote
	lappend bools voteprogress
	lappend bools stats
	lappend bools localconns
	lappend bools ipfromstats
	lappend bools checkpass
	lappend bools exclude
	lappend bools sgtkcheck
	lappend bools alwaysrunstats
	lappend bools autorotate
	lappend bools rotatetype
	lappend bools weaponlimit
	lappend bools deletecust
	lappend bools modswitch
	lappend bools pingcheck
	lappend bools suicidecheck
	lappend bools fahack
	lappend bools oldhack
	lappend bools checkwpt
	lappend bools hlbpoutput
	lappend bools adminmod
	lappend bools admininstalled
	lappend bools attackcheck
	lappend bools halfbot
	lappend bools plugins
	lappend bools allowsource
	lappend bools quiet
	lappend bools broadcastwarn
	lappend bools broadcastpen
	lappend bools getdeaths
	lappend bools senddeaths
	lappend bools getmodelsfromrole
	lappend bools hlds_boost
	lappend bools oldeventhandler
	lappend bools dodhack
	lappend bools statsinbg
	lappend bools hostagecheck
	lappend bools killszget
	lappend bools fork
	lappend bools archivecore
	lappend bools halfbotauth
	lappend bools getstats
	foreach bool $bools {
		if ![tf $config($bool)] {
			myEcho ERR [format $text(126) $bool $config($bool)]
			set config($bool) 0
			set validated 0
		}
	}

	# Set up a list of digits
	lappend ints dport
	lappend ints port
	lappend ints rsDelay
	lappend ints refresh
	lappend ints tkdecay
	lappend ints fouldecay
	lappend ints votetime
	lappend ints maplist
	lappend ints maplistdisplay
	lappend ints modlist
	lappend ints mapcycle
	lappend ints timeleft
	lappend ints nextmap
	lappend ints minhack
	lappend ints votefreq
	lappend ints maxconns
	lappend ints statusfields
	lappend ints tcldebug
	lappend ints adminlog
	lappend ints maxextend
	lappend ints debug
	lappend ints modmax
	lappend ints pingmin
	lappend ints pingwarn
	lappend ints pingdecay
	lappend ints whattime
	lappend ints votestatus
	lappend ints tkroundpenalty
	lappend ints tkroundlimit
	lappend ints nullmap
	lappend ints votekwtime
	lappend ints hlbpport
	lappend ints hlbpserverport
	lappend ints hlbpwait
	lappend ints lastNlines
	lappend ints saysleep
	lappend ints statswait
	lappend ints banlog
	lappend ints attackdecay
	lappend ints attackroundpenalty
	lappend ints attackroundlimit
	lappend ints suicidedecay
	lappend ints halfbotmax
	lappend ints nullname
	lappend ints teamdecay
	lappend ints teamcheck
	lappend ints checkserver
	lappend ints rsattempts
	lappend ints hlbpreset
	lappend ints maxsamples
	foreach int $ints {
		if ![ctype digit $config($int)] {
			myEcho ERR [format $text(127) $int $config($int)]
			set config($int) 0
			set validated 0
		}
	}

	# Set up a list-of-list-of-digits :)
	lappend listints serverMsgTimes
	lappend listints tkcounts
	lappend listints tkargs
	lappend listints weaponcounts
	lappend listints weaponargs
	lappend listints foulcounts
	lappend listints foulargs
	lappend listints pingcounts
	lappend listints pingargs
	lappend listints attackcounts
	lappend listints attackargs
	lappend listints suicidecounts
	lappend listints suicideargs
	lappend listints teamcounts
	lappend listints teamargs
	lappend listints hostagecounts
	lappend listints hostageargs
	foreach listint $listints {
		foreach int $config($listint) {
			if ![ctype digit $int] {
				# Handle negative integers
				if { ![cequal [csubstr $int 0 1] -] || ![ctype digit [csubstr $int 1 end]] } {
					myEcho ERR [format $text(146) $listint $config($listint)]
					myEcho ERR $text(147)
					set config($listint) ""
					set validated 0
				}
			}
		}
	}

	set lists ""

	foreach penalty $penaltyList {
		set cnt [llength $config(${penalty}counts)]
		set pen [llength $config(${penalty}penalty)]
		set arg [llength $config(${penalty}args)]

		lappend lists ${penalty}penalty

		if { ![cequal $cnt $pen] || ![cequal $cnt $arg] } {
			myEcho ERR [format $text(202) $penalty]
			myEcho ERR [format $text(203) $penalty $cnt $pen $arg]
			set validated 0
		}
	}

	# Set up a list-of-lists
	lappend lists maps
	lappend lists votemaps
	lappend lists novotemaps
	lappend lists votemodnames
	lappend lists votemoddirs
	lappend lists oldprefix
	lappend lists oldpostfix
	foreach el $lists {
		if [catch { lindex $config($el) 0 } err] {
			myEcho ERR [format $text(283) $el]
			myEcho ERR "(Error was: $err)"
			set validated 0
		}
	}


	if ![cequal $config(votemodnames) ""] {
		if [catch {
			if ![cequal [llength $config(votemodnames)] [llength $config(votemoddirs)]] {
				myEcho ERR [format $text(169) votemodnames votemoddirs]
				set validated 0
				set config(votemodnames) ""
			}
		} err] {
			myEcho ERR "Error validating votemod: $err"
			set validated 0
			set config(votemodnames) ""
		}
	}

	if $config(modmax) {
		if [cequal $config(modprimary) ""] {
			myEcho ERR $text(185)
			set validated 0
		}

		set idx [lsearch -exact $config(votemodnames) $config(modprimary)]
		if [cequal $idx -1] {
			myEcho ERR [format $text(184) modprimary votemodnames]
			set config(modmax) 0
			set validated 0
		}
	}

	set config(suicideteams) [string tolower $config(suicideteams)]

	if [cequal $config(hlds_run) hlds_boost] {
		myEcho ERR "hlds_run option for hlds_boost has been deprecated!  Instead, set"
		myEcho ERR "'hlds_boost=1' in halfd.cfg"
		set hlds_run hlds
		set hlds_boost 1
		set validated 0
	}

	# getteamfromuser
	if { $config(getteamfromuser) && [expr !$config(getmodels) || $config(getmodelsfromrole)] } {
		myEcho ERR [format $text(323) getteamfromuser "getmodels=1 getmodelsfromrole=0"]
		myEcho ERR [format $text(324) "getmodels=$config(getmodels) and getmodelsfromrole=$config(getmodelsfromrole)"]
		myEcho ERR [format $text(325) getteamfromuser]
		set config(getteamfromuser) 0
		set validated 0
	}

	# regexps
	if [catch { regexp $config(ignoretkta) "" } err] {
		myEcho ERR "Error in regular expression \"$config(ignoretkta)\": $err"
		myEcho ERR "Disabling ignoretkta..."
		set config(ignoretkta) ""
		set validated 0
	}

	# password uniqueness
	set auths "usrauth admauth newapiauth newapiusrauth" 
	foreach auth $auths {
		foreach auth2 $auths {
			if { ![cequal $config($auth) ""] && ![cequal $auth $auth2] } {
				if [cequal $config($auth) $config($auth2)] {
					myEcho ERR [format $text(348) $auth $auth2]
					myEcho ERR $text(349)
					set validated 0
				}
			}
		}
	}

	if !$validated { 
		myEcho ERR $text(148)
	} else {
		myEcho INFO $text(149)
	}
}


proc myEcho { type data } {
	echo "hlcmd: $type $data"
}


proc now {} {
	return [clock format [clock seconds]]
}

proc readText {} {
	global text myhldir vnum

	set file $myhldir/halfd.txt

	if [catch {open $file r} fd] {
		puts stderr "00 - FATAL: can't open 'halfd.txt': $fd"
		exit 1
	}

	# Initialize the array; just in case someone doesn't copy the
	# file to the right place
	loop i 0 1000 { set text($i) "" }

	set i 0
	while 1 {
		if [eof $fd] break
		set text($i) [gets $fd]
		incr i
	}

	close $fd
}

######################################################################
# Procedures modified
#
proc determineIP {} {
	global text config

	if [cequal $config(nic) ""] {
		myEcho INFO $text(130)
		return ""
	}

	# Thanks to Eric Hameleers for this code / idea!
	if [catch {exec ifconfig $config(nic) | grep inet} ipdata] {
		myEcho INFO [format $text(131) $config(nic) $ipdata]
		return ""
	}

	set tagaddr [lindex $ipdata 1]
	set addr    [lindex [split $tagaddr :] 1]

	myEcho INFO [format $text(132) $config(nic) $addr]

	return $addr
}

proc launchServer {} {
	global gameId fds config vote after text myhldir gameId plat
	global env

	if ![cequal $plat unix]  {
		myEcho DBUG "Can't launch server from halfd.  Use HLBP."
		return
	}

	cancelEvents

	set vote(insession) 0

	if ![file exists $fds(cmd)] {
	if [catch {exec mkfifo $fds(cmd)} err] {
		set msg "FATAL Error creating command pipe: '$err'"
		myEcho ERR $msg
		sendClient ERR  $msg
		myExit
	}
	catch { chmod 0664 $fds(cmd) }
	}

	# Copy existing log file to .old
	if $config(rotatetype) {
		set yymmddhhmm [clock format [clock seconds] -format %Y%m%d%H%M]
		if [cequal $plat windows] { append yymmddhhmm .log }
		set mylogtail [file tail $fds(log)]
		if [catch {file rename -force $fds(log) $myhldir/$gameId/addons/halfd/$config(oldlogsdir)/$mylogtail.$yymmddhhmm} err] {
			myEcho WARN "Error renaming log file: $err"
		}
	} else {
		catch {file rename -force $fds(log) $fds(log).old}
	}

	set fds(cmdfd) [open $fds(cmd) {RDWR NONBLOCK}]
	fcntl $fds(cmdfd) CLOEXEC
	fcntl $fds(cmdfd) NOBUF

	# Delete custom.hpk if so configured
	if $config(deletecust) {
		if ![catch { file size $gameId/custom.hpk } err] {
			myEcho INFO [format $text(178) $err]
			if [catch { unlink $gameId/custom.hpk } err] {
				myEcho WARN [format $text(179) $err]
			}
		} else {
			myEcho INFO "Couldn't read custom.hpk: $err"
		}
	}
		

	set myArgs $config(args)
	set ip [determineIP]
	if ![cequal $ip ""] { append myArgs " +ip $ip" }

	if $config(hlds_boost) {
		if [file exists booster.so] {
			set env(LD_PRELOAD) "$myhldir/booster.so"
			set env(HLDS_FPS)   200
			append myArgs " +sys_ticrate 10000 +sv_maxupdaterate 100"
			myEcho INFO $text(289)
		} else {
			myEcho WARN $text(288)
		}
	}

	# START THE SERVER
	set hlds_run [determineHLDS]
	myEcho INFO $text(32)
	myEcho INFO "./$hlds_run -game $gameId -port $config(port) $myArgs"

	if [catch {exec \
		./$hlds_run -game $gameId -port $config(port) \
		$myArgs >& $fds(log) < $fds(cmd) &} \
	    pid] {

		catch {
			unset env(LD_PRELOAD)
			unset env(HLDS_FPS)
		}

		sendClient ERR [format $text(33) $pid]
		myEcho     ERR [format $text(33) $pid]
		serverDown

		if $config(rs) {
			set msg  [format $text(34) $config(rsDelay)]
			statusBar $msg
			myEcho INFO $msg
			set after(launch) [after [expr $config(rsDelay) * 1000] launchServer]
		}

		return
	}

	# Get rid of booster envvars if they're set.  LD_PRELOAD will hose
	# any other executables (e.g. ifconfig, mail)
	catch {
		unset env(LD_PRELOAD)
		unset env(HLDS_FPS)
	}

	set fd [open $fds(pid) w]
	puts $fd $pid
	close $fd

	mySleep 5
	statusBar "The server is starting.  Waiting for it to come all the way up..."
	mySleep 10

	myEcho INFO [format $text(35) $pid $gameId]

	initModMax
	
	set fds(pidnum) $pid
	set fds(logfd) [open $fds(log) {RDONLY NONBLOCK}]

if 0 {
	if [info exists fds(rma)] {
		catch [close $fds(rma)]
	}
	set fds(rma) [open $myhldir/$gameId/addons/halfd/hlds_l.log.rma w]
}

	fcntl $fds(logfd) CLOEXEC
	fcntl $fds(logfd) NOBUF

	myEcho DBUG [format $text(268) [pid] $fds(pidnum)]

	# TEST
	if ![cequal $config(initmap) ""] {
		myEcho DBUG "Initializing map to $config(initmap)"
		set after(initmap) [after 5000 { execCmd "changelevel $config(initmap)" }]
	}

	serverUp
	processLogFile 1 1
	getServerStatus 1
}

proc determineHLDS {} {
	global text config

	if ![cequal $config(hlds_run) ""] { return $config(hlds_run) }

	set hlds hlds_i486

	if [file readable /proc/cpuinfo] {
		if [catch { exec grep "model name" /proc/cpuinfo } modelname] {
			myEcho WARN "Error determining CPU: $modelname"
		} else {
			if [regexp -nocase "pentium|celeron" $modelname] {
				set hlds hlds_i686
			} elseif [regexp -nocase "AMD" $modelname] {
				set hlds hlds_amd
			}
		}
	} else {
		if [catch { exec uname } uname] {
			myEcho WARN "Error executing uname: $uname"
		} else {
			if [regexp -nocase freebsd $uname] {
				if [catch { exec dmesg | grep CPU: } dmesg] {
					myEcho WARN "Error executing dmesg: $dmesg"
				} else {
					if [regexp AMD $dmesg] {
						set hlds hlds_amd
					} elseif [regexp 686 $dmesg] {
						set hlds hlds_i686
					}
				}
			}
		}
	}

	myEcho INFO [format $text(359) $hlds]

	return $hlds
}
			
proc oldlaunchServer {} {
	global gameId fds config vote after text myhldir

	foreach id [after info] { 
		set a1 ""
		set a2 ""
		catch { set a1 $after(tk) }
		catch { set a2 $after(offenses) }
		if { [cequal $id $a1] || [cequal $id $a2] } continue
		after cancel $id
	}
	set vote(insession) 0

	if ![file exists $fds(cmd)] {
	if [catch {exec mkfifo $fds(cmd)} err] {
		set msg "FATAL Error creating command pipe: '$err'"
		myEcho ERR $msg
		sendClient ERR  $msg
		myExit
	}
	catch { chmod 0664 $fds(cmd) }
	}

	# Copy existing log file to .old
	if $config(rotatetype) {
		set yymmddhhmm [clock format [clock seconds] -format %Y%m%d%H%M]
		set mylogtail [file tail $fds(log)]
		if [catch {file rename -force $fds(log) $myhldir/$gameId/addons/halfd/oldlogs/$mylogtail.$yymmddhhmm} err] {
			myEcho WARN "Error renaming log file: $err"
		}
	} else {
		catch {file rename -force $fds(log) $fds(log).old}
	}

	set fds(cmdfd) [open $fds(cmd) {RDWR NONBLOCK}]
	fcntl $fds(cmdfd) CLOEXEC
	fcntl $fds(cmdfd) NOBUF

	# Delete custom.hpk if so configured
	if $config(deletecust) {
		if ![catch { file size custom.hpk } err] {
			myEcho INFO [format $text(178) $err]
			if [catch { unlink custom.hpk } err] {
				myEcho WARN [format $text(179) $err]
			}
		}
	}
		

	set myArgs $config(args)
	set ip [determineIP]
	if ![cequal $ip ""] { append myArgs " +ip $ip" }

	# START THE SERVER
	# Removed 'nohup' 9 May 2000 - nohup runs at nice 10(!)
	myEcho INFO $text(32)
	myEcho INFO "./$config(hlds_run) -game $gameId -port $config(port) $myArgs"

	if [catch {exec \
		./$config(hlds_run) -game $gameId -port $config(port) \
		$myArgs >& $fds(log) < $fds(cmd) &} \
	    pid] {
		sendClient ERR [format $text(33) $pid]
		myEcho     ERR [format $text(33) $pid]
		serverDown

		if $config(rs) {
			set msg  [format $text(34) $config(rsDelay)]
			statusBar $msg
			myEcho INFO $msg
			set after(launch) [after [expr $config(rsDelay) * 1000] launchServer]
		}
		return
	}

	set fd [open $fds(pid) w]
	puts $fd $pid
	close $fd

	sleep 5

	myEcho INFO [format $text(35) $pid $gameId]
	
	set fds(pidnum) $pid
	set fds(logfd) [open $fds(log) {RDONLY NONBLOCK}]

	fcntl $fds(logfd) CLOEXEC
	fcntl $fds(logfd) NOBUF

	serverUp
	#processLogFile 1 1
	#getServerStatus 1
}


######################################################################
# Procedures redefined
#
proc serverUp {} {
	global fds pid

	set fd [open $fds(pid) r]
	set pid [gets $fd]
	close $fd

	global serverUp
	set serverUp 1
}

proc serverDown {} {
	global serverUp
	set serverUp 0
}

proc statusBar { msg } {
	echo $msg
}

proc popupNotify { w msg } {
	echo $msg
}

proc destroy { bleh } {}

proc showHelp {} {
	puts stderr "
Usage: hlcmd \[-g <game>\] \[-d <hldir>\] \[-e <extension>\] \[command\]

Where: <game>      = a MOD name.  (e.g. tfc or cstrike)
                     Default = \"valve\"

       <hldir>     = The directory where hlds is installed.
                     This overrides the environment variable HLDIR.

       <extension> = a string specifying which hlds_ld.cfg to read.
                     This allows you to run multiple copies of the same game from
                     one \$HLDIR/mod directory.  See FAQ for more information.

       command     = A command to send to the server.
                     If blank, hlcmd will run interactively.

Special commands interpreted by hlcmd are as follows:

stop  = stops a running server
start = starts a server
watch = watch the output from a running server (interactive mode only)

q, quit, exit = exits hlcmd

Any other commands are sent directly to the server.

"
}

proc usage {} {
	showHelp
	exit 1
}

proc parseArgs { argc argv } {
	global args env plat defaultgame

	set args(debug) 	0 
	set args(gameid)	$defaultgame 
	set args(extension)	"" 
	set args(hldir) ""
	set args(cmd) ""
	set args(console) 0
	set args(killhlds) 0

	if [info exists env(HLDIR)] {
		set args(hldir)	$env(HLDIR)
	}

	set i 0
	while 1 {
		set arg [lindex $argv $i]

		switch -exact -- $arg {
			-debug {
				# Debug
				incr i
				set arg [lindex $argv $i]

				if ![ctype digit $arg] {
					puts stderr "-debug requires an argument; must be an integer!"
					usage
				}

				set args(debug) $arg
			}

			-h {
				# Help
				usage
			}

			-d {
				# HLDIR
				incr i
				set arg [lindex $argv $i]

				if [cequal $arg ""] {
					usage
				}

				if ![file exists "$arg/hlds"] {
					puts stderr "-d argument must be a directory containing hlds"
					usage
				}

				set args(hldir) $arg
			}

			-g {
				# Game
				incr i
				set arg [lindex $argv $i]

				if [cequal $arg ""] {
					usage
				}

				if [cequal [csubstr $arg 0 1] -] {
					puts stderr "-g requires an argument; must be a valid MOD."
					usage
				}

				set args(gameid) $arg
			}

			-e {
				# Extension
				incr i
				set arg [lindex $argv $i]

				if [cequal $arg ""] {
					usage
				}

				if [cequal [csubstr $arg 0 1] -] {
					puts stderr "-e requires an argument; must be a file extension."
					usage
				}

				set args(extension) $arg
			}

			default {
				if ![cequal $arg ""] {
					lappend args(cmd) $arg
				}
			}
		}

		incr i
		if { $i > $argc } break
	}

	if { [cequal $args(hldir) ""] && [cequal $plat unix] } {
		puts stderr "Must either use -d argument or HLDIR environment variable."
		usage
	}
}

######################################################################
# Main
#
proc main { argc argv } {
	global env fds gameId serverUp vers pid myhldir tcl_platform
	global args cfgPostfix plat

	if ![info exists tcl_platform(platform)] {
		puts stderr "ERROR: Can't determine platform!  Exit."
		usage
	} else {
		set plat $tcl_platform(platform)
	}

	if ![cequal $plat unix] {
		puts stderr "ERROR: This tool only works on unix."
		exit 0
	}

	parseArgs $argc $argv

	set serverUp 0
	set gameId $args(gameid)

	set myhldir $args(hldir)

	cd $myhldir

	if ![cequal $args(extension) ""] {
		set cfgPostfix ".$args(extension)"
	} else {
		set cfgPostfix ""
	}

	set fds(pid) $myhldir/$gameId/addons/halfd/hlds_l.pid$cfgPostfix
	set fds(cmd) $myhldir/$gameId/addons/halfd/hlds_l.cmd$cfgPostfix
	set fds(log) $myhldir/$gameId/addons/halfd/hlds_l.log$cfgPostfix

	readText
	readConfig

	checkForServer


	set cmd $args(cmd)
	if ![cequal $cmd ""] {
		# Non-interactive mode

		# Check for 'start'; special
		if [cequal $cmd start] {
			if $serverUp {
				echo "04 - Server for '$gameId' is already running!" 
				exit 1
			}

			launchServer 
			echo "Server for game '$gameId' started successfully." 
			exit 0
		}

		if !$serverUp {
			echo "01 - Can't find a server for game '$gameId'.  Aborting command."
			exit 1
		}

		if { [cequal $cmd quit] || [cequal $cmd exit] } { exit 0 }
		if [cequal $cmd stop] { set cmd quit }

		if [cequal $cmd watch] {
			# We will go into interactive mode if the server's up
			if !$serverUp {
				echo "Server is down.  Please start before attempting"
				echo "to watch it!"
				exit 0
			}
		} else {
			# We will execute the cmd and quit
			foreach el $cmd {
				if [cequal $el ""] { set el \"\" }
				append myCmd "$el "
			}

			execCmd $myCmd
			select {} {} {} 0.5
			while 1 {
				echo [gets $fds(logfd)]
				if [eof $fds(logfd)] break
			}
			exit 0
		}
	} 

	echo "hlcmd version $vers\n"
	echo "game = '$gameId', hldir = '$myhldir'"

	while 1 {
		if $serverUp {
			puts -nonewline "SERVER UP hlcmd> "
			flush stdout
		} else {
			puts -nonewline "SERVER DOWN hlcmd> "
			flush stdout
		}

		if [cequal $cmd ""] {
			set cmd [gets stdin]
		}

		if { $serverUp && ![processExists $pid] } {
			echo "Server died!"
			serverDown
			continue
		}

		if { [cequal $cmd ""] && $serverUp } continue

		if $serverUp {
			switch -exact -- [string tolower $cmd] {
				q -
				quit -
				exit { exit 0 }

				help {
					showHelp
				}

				stop {
					puts -nonewline "Stop Server \[y/N\]: "
					flush stdout
					set yn [gets stdin]
					if [cequal [string tolower $yn] y] {
						stopServer
					}
				}

				watch {
					seek $fds(logfd) 0 end
					echo "Watching server...hit <ctrl-c> to exit..."
					signal error 2
					catch {
					while 1 {
						set data [gets $fds(logfd)]
						if [eof $fds(logfd)] {
							sleep 1
							continue
						}
						if [cequal $data ""] continue
						if [regexp "^name" $data] continue
						if [catch {llength $data} len] { set len 0 }
						if [cequal $len 2] continue
						if [regexp "^\[^ \]|map" $data] {
							echo $data
						}
					}
					} err
					signal default 2
					echo "Done watching: $err"
				}

				default {
					seek $fds(logfd) 0 end
					execCmd $cmd
					select {} {} {} 0.5
					while 1 {
						echo [gets $fds(logfd)]
						if [eof $fds(logfd)] break
					}
				}
			}
		} else {
			checkForServer
			switch -exact -- [string tolower $cmd] {
				q -
				exit -
				quit {
					exit 0
				}

				help {
					showHelp
				}
	
				start {
					if !$serverUp { launchServer }
				}


				default {
					if !$serverUp {
					echo "Server is down.  Valid commands are 'start, quit, help'"
					}
				}
			}
		}

		set cmd ""
	}
}

# Can you BELIEVE that sleep() doesn't work on windoze!?!! !AGH!!
proc mySleep { seconds } {
	global plat dontprocess sleepwait

	if [cequal $plat unix] {
		# Ah. Sanity.
		if ![ctype digit $seconds] {
			# Assume floating-point
			set dontprocess 1
			select {} {} {} $seconds
			set dontprocess 0
		} else {
			sleep $seconds
		}
	} else {
		# %&*&@#$!
		set sleepwait 0
		set sleeptime [expr int([expr $seconds * 1000])]
		after $sleeptime { set sleepwait 1 }
		set dontprocess 1
		vwait sleepwait
		set dontprocess 0
	}
}

proc readBadwords {} {}

proc cancelEvents {} {}

proc initModMax {} {}

proc processLogFile {a b} {}

proc getServerStatus {a} {}


proc initVars {} {
	global oldfoulprefix

	set oldfoulprefix ""
}


#set fd [open hlcmd.dbg w]
#cmdtrace on $fd
initVars

set vers 2.20b15
set defaultgame valve

global env
append env(LD_LIBRARY_PATH) :.

main [llength $argv] $argv

