#!/usr/bin/env ruby

=begin

    ALIEN ARENA RCON UTIL (for server admins) V1.0
    Copyright (C) 2007 Tony Jackson

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

    Tony Jackson can be contacted at tonyj@cooldark.com
	
=end

require 'socket'

# send rcon string to all servers in array and get responses(not yet implemented)
def send_rcon(servers, password, message)
	if servers == nil
		return []
	end

	query = '˙˙˙˙rcon ' + password + ' ' + message + ' ' + "\x00"
	connections = Hash.new
	failed = Array.new
	
	# Get the time before sending any packets, to time server response time (ping)
	starttime = Time.now
	
	# send a UDP packet to each server in the array
	servers.each do
		| server |  # Here server is of form 'ip:port' string
		begin
			socket = UDPSocket.new  # open works in the same way
			socket.connect(server.split(':')[0], server.split(':')[1])
			socket.send(query, 0)
			#socket.send(nil, 0)  # Test failure case
			connections[socket] = server # hash keyed on socket resource, containing server string
		rescue
			puts 'Failed to send to server '+server
			# some failure making a socket or sending a message - add to list of failed servers
			#socket.close - can't use this - may not even be open
			failed << server
			next
		end
	end
	
	# remove servers from list if socket failed to create/send UDP packet
	servers -= failed
	
	# check that we have at least one open UDP socket
	if connections.length == 0
		return servers
	end
	
	selectsocketlist = connections.keys  # get list of sockets from hash
	while result = select(selectsocketlist, nil, nil, 0.5) # select() waits for one or more socket to get a read event, or times out
		ping = (Time.now - starttime)*1000
		# store the time at which this server (or multiple servers) responded, and store the replies in our array of buffers
		result[0].each do
			|socket|
			begin
				# here connections[socket] gives us the 'ip:port' string of the associated server
				puts 'Server '+connections[socket]+' replied in '+ping.to_s+'ms:'
				reply = socket.recv(2048) # big enough to cover both games server replies and master reply with 256 servers (payload 12+6*256 bytes)
				puts reply.split("\n")[1..-1]
				selectsocketlist.delete(socket) # delete from array  now that we have a reply
				socket.close
			rescue # catchs both socket errors and nils in the split :)
				next
			end
		end
	end
	
	# here selectsocketlist will contain an array of failed sockets
	selectsocketlist.each do
		|socket|
		servers -= [connections[socket]]
		socket.close
	end
	
	# return array of servers that responded to our queries
	return servers
end
	

# Program starts here

# $* is array of args
if $*.include?('--help') or ($*.length < 3) then
	puts ''
	puts 'RCon utility for Alien Arena servers'
	puts ''
	puts '  Usage:'
	puts '  rcon <server:port> password <commandstring>'
	puts '  Optional arguments:'
	puts ''
	puts '  --help  This message'
	puts ''
	exit
end

send_rcon([$*[0]], $*[1], $*[2..-1].join(' '))

