koraktor/steam-condenser-php

View on GitHub
lib/SteamCondenser/Servers/Sockets/GoldSrcSocket.php

Summary

Maintainability
A
1 hr
Test Coverage
<?php
/**
 * This code is free software; you can redistribute it and/or modify it under
 * the terms of the new BSD License.
 *
 * Copyright (c) 2008-2015, Sebastian Staudt
 *
 * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
 */

namespace SteamCondenser\Servers\Sockets;

use \SteamCondenser\Exceptions\RCONBanException;
use \SteamCondenser\Servers\Packets\SteamPacketFactory;

/**
 * This class represents a socket used to communicate with game servers based
 * on the GoldSrc engine (e.g. Half-Life, Counter-Strike)
 *
 * @author     Sebastian Staudt
 * @package    steam-condenser
 * @subpackage sockets
 */
class GoldSrcSocket extends SteamSocket {

    /**
     * @var boolean
     */
    private $isHLTV;

    /**
     * @var int
     */
    private $rconChallenge = -1;

    /**
     * Creates a new socket to communicate with the server on the given IP
     * address and port
     *
     * @param string $ipAddress Either the IP address or the DNS name of the
     *        server
     * @param int $portNumber The port the server is listening on
     * @param bool $isHLTV <var>true</var> if the target server is a HTLV
     *        instance. HLTV behaves slightly different for RCON commands, this
     *        flag increases compatibility.
     */
    public function __construct($ipAddress, $portNumber = 27015, $isHLTV = false) {
        parent::__construct($ipAddress, $portNumber);

        $this->isHLTV = $isHLTV;
    }

    /**
     * Reads a packet from the socket
     *
     * The Source query protocol specifies a maximum packet size of 1,400
     * bytes. Bigger packets will be split over several UDP packets. This
     * method reassembles split packets into single packet objects.
     *
     * @return \SteamCondenser\Servers\Packets\SteamPacket The packet replied
     *         from the server
     */
    public function getReply() {
        $bytesRead = $this->receivePacket(1400);

        if($this->buffer->getLong() == -2) {
            do {
                $requestId = $this->buffer->getLong();
                $packetCountAndNumber = $this->buffer->getByte();
                $packetCount = $packetCountAndNumber & 0xF;
                $packetNumber = ($packetCountAndNumber >> 4) + 1;

                $splitPackets[$packetNumber - 1] = $this->buffer->get();

                $this->logger->debug("Received packet $packetNumber of $packetCount for request #$requestId");

                if(sizeof($splitPackets) < $packetCount) {
                    try {
                        $bytesRead = $this->receivePacket();
                    } catch(\SteamCondenser\Exceptions\TimeoutException $e) {
                        $bytesRead = 0;
                    }
                } else {
                    $bytesRead = 0;
                }
            } while($bytesRead > 0 && $this->buffer->getLong() == -2);

            $packet = SteamPacketFactory::reassemblePacket($splitPackets);
        } else {
            $packet = SteamPacketFactory::getPacketFromData($this->buffer->get());
        }

        $this->logger->debug("Received packet of type \"" . get_class($packet) . "\"");

        return $packet;
    }

    /**
     * Executes the given command on the server via RCON
     *
     * @param string $password The password to authenticate with the server
     * @param string $command The command to execute on the server
     * @return RCONGoldSrcResponse The response replied by the server
     * @see rconChallenge()
     * @see rconSend()
     * @throws RCONBanException if the IP of the local machine has been banned
     *         on the game server
     * @throws RCONNoAuthException if the password is incorrect
     */
    public function rconExec($password, $command) {
        if($this->rconChallenge == -1 || $this->isHLTV) {
            $this->rconGetChallenge();
        }

        $this->rconSend("rcon {$this->rconChallenge} $password $command");
        if($this->isHLTV) {
            try {
                $response = $this->getReply()->getResponse();
            } catch(\SteamCondenser\Exceptions\TimeoutException $e) {
                $response = '';
            }
        } else {
            $response = $this->getReply()->getResponse();
        }

        if(trim($response) == 'Bad rcon_password.') {
            throw new \SteamCondenser\Exceptions\RCONNoAuthException();
        } elseif(trim($response) == 'You have been banned from this server.') {
            throw new \SteamCondenser\Exceptions\RCONBanException();
        }

        $this->rconSend("rcon {$this->rconChallenge} $password");

        do {
            $responsePart = $this->getReply()->getResponse();
            $response .= $responsePart;
        } while(strlen($responsePart) > 0);

        return $response;
    }

    /**
     * Requests a challenge number from the server to be used for further
     * requests
     *
     * @throws RCONBanException if the IP of the local machine has been banned
     *         on the game server
     * @see rconSend()
     */
    public function rconGetChallenge() {
        $this->rconSend('challenge rcon');
        $response = trim($this->getReply()->getResponse());

        if($response == 'You have been banned from this server.') {
            throw new RCONBanException();
        }

        $this->rconChallenge = floatval(substr($response, 14));
    }

    /**
     * Wraps the given command in a RCON request packet and send it to the
     * server
     *
     * @param string $command The RCON command to send to the server
     */
    public function rconSend($command) {
        $this->send(new \SteamCondenser\Servers\Packets\RCON\RCONGoldSrcRequest($command));
    }
}