lib/steam-condenser/servers/source_server.rb
# This code is free software; you can redistribute it and/or modify it under
# the terms of the new BSD License.
#
# Copyright (c) 2008-2013, Sebastian Staudt
require 'steam-condenser/error/rcon_ban'
require 'steam-condenser/error/rcon_no_auth'
require 'steam-condenser/servers/game_server'
require 'steam-condenser/servers/master_server'
require 'steam-condenser/servers/packets/rcon/rcon_auth_request'
require 'steam-condenser/servers/packets/rcon/rcon_auth_response'
require 'steam-condenser/servers/packets/rcon/rcon_exec_request'
require 'steam-condenser/servers/packets/rcon/rcon_terminator'
require 'steam-condenser/servers/sockets/rcon_socket'
require 'steam-condenser/servers/sockets/source_socket'
module SteamCondenser
module Servers
# This class represents a Source game server and can be used to query
# information about and remotely execute commands via RCON on the server
#
# A Source game server is an instance of the Source Dedicated Server (SrcDS)
# running games using Valve's Source engine, like Counter-Strike: Source,
# Team Fortress 2 or Left4Dead.
#
# @author Sebastian Staudt
# @see GoldSrcServer
class SourceServer
include GameServer
include SteamCondenser::Logging
# Returns a master server instance for the default master server for Source
# games
#
# @return [MasterServer] The Source master server
def self.master
MasterServer.new *MasterServer::SOURCE_MASTER_SERVER
end
# Creates a new instance of a server object representing a Source server,
# i.e. SrcDS instance
#
# @param [String] address Either an IP address, a DNS name or one of them
# combined with the port number. If a port number is given, e.g.
# 'server.example.com:27016' it will override the second argument.
# @param [Fixnum] port The port the server is listening on
# @raise [Error] if an host name cannot be resolved
def initialize(address, port = 27015)
super
end
# Disconnects the TCP-based channel used for RCON commands
#
# @see RCONSocket#close
def disconnect
@rcon_socket.close
end
# Initializes the sockets to communicate with the Source server
#
# @see RCONSocket
# @see SourceSocket
def init_socket
@rcon_socket = Sockets::RCONSocket.new @ip_address, @port
@socket = Sockets::SourceSocket.new @ip_address, @port
end
# Authenticates the connection for RCON communication with the server
#
# @param [String] password The RCON password of the server
# @return [Boolean] whether authentication was successful
# @see #rcon_authenticated?
# @see #rcon_exec
def rcon_auth(password)
@rcon_request_id = rand 2**16
@rcon_socket.send_packet Packets::RCON::RCONAuthRequest.new @rcon_request_id, password
reply = @rcon_socket.reply
raise Error::RCONBan if reply.nil?
reply = @rcon_socket.reply
@rcon_authenticated = reply.request_id == @rcon_request_id
end
# Remotely executes a command on the server via RCON
#
# @param [String] command The command to execute on the server via RCON
# @return [String] The output of the executed command
# @raise [RCONBanException] if the IP of the local machine has been banned on
# the game server
# @raise [RCONNoAuthException] if not authenticated with the server
# @see #rcon_auth
def rcon_exec(command)
raise Error::RCONNoAuth unless @rcon_authenticated
@rcon_socket.send_packet Packets::RCON::RCONExecRequest.new(@rcon_request_id, command)
is_multi = false
response = []
begin
response_packet = @rcon_socket.reply
if response_packet.nil? || response_packet.is_a?(Packets::RCON::RCONAuthResponse)
@rcon_authenticated = false
raise Error::RCONNoAuth
end
if !is_multi && response_packet.response.size > 0
is_multi = true
@rcon_socket.send_packet Packets::RCON::RCONTerminator.new(@rcon_request_id)
end
response << response_packet.response
end while is_multi && !(response[-2] == '' && response[-1] == '')
response.join('').strip
end
end
end
end