koraktor/steam-condenser-php

View on GitHub
lib/SteamCondenser/Servers/MasterServer.php

Summary

Maintainability
B
5 hrs
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;

use SteamCondenser\Exceptions\TimeoutException;
use SteamCondenser\Servers\Packets\A2MGETSERVERSBATCH2Packet;
use SteamCondenser\Servers\Sockets\MasterServerSocket;

/**
 * This class represents a Steam master server and can be used to get game
 * servers which are publicly available
 *
 * An intance of this class can be used much like Steam's server browser to get
 * a list of available game servers, including filters to narrow down the
 * search results.
 *
 * @author     Sebastian Staudt
 * @package    steam-condenser
 * @subpackage servers
 */
class MasterServer extends Server {

    /**
     * @var string The master server address to query for GoldSrc game servers
     */
    const GOLDSRC_MASTER_SERVER = 'hl1master.steampowered.com:27011';

    /**
     * @var string The master server address to query for GoldSrc game servers
     */
    const SOURCE_MASTER_SERVER = 'hl2master.steampowered.com:27011';

    /**
     * @var int The region code for the US east coast
     */
    const REGION_US_EAST_COAST = 0x00;

    /**
     * @var int The region code for the US west coast
     */
    const REGION_US_WEST_COAST = 0x01;

    /**
     * @var int The region code for South America
     */
    const REGION_SOUTH_AMERICA = 0x02;

    /**
     * @var int The region code for Europe
     */
    const REGION_EUROPE = 0x03;

    /**
     * @var int The region code for Asia
     */
    const REGION_ASIA = 0x04;

    /**
     * @var int The region code for Australia
     */
    const REGION_AUSTRALIA = 0x05;

    /**
     * @var int The region code for the Middle East
     */
    const REGION_MIDDLE_EAST = 0x06;

    /**
     * @var int The region code for Africa
     */
    const REGION_AFRICA = 0x07;

    /**
     * @var int The region code for the whole world
     */
    const REGION_ALL = 0xFF;

    /**
     * @var int
     */
    private static $retries = 3;

    /**
     * @var MasterServerSocket
     */
    protected $socket;

    /**
     * Sets the number of consecutive requests that may fail, before getting
     * the server list is cancelled (default: 3)
     *
     * @param int $retries The number of allowed retries
     */
    public static function setRetries($retries) {
        self::$retries = $retries;
    }

    /**
     * Returns a list of game server matching the given region and filters
     *
     * Filtering:
     * Instead of filtering the results sent by the master server locally, you
     * should at least use the following filters to narrow down the results
     * sent by the master server.
     *
     * <b>Note:</b> Receiving all servers from the master server is taking
     * quite some time.
     *
     * Available filters:
     *
     * <ul>
     * <li><var>\type\d</var>: Request only dedicated servers
     * <li><var>\secure\1</var>: Request only secure servers
     * <li><var>\gamedir\[mod]</var>: Request only servers of a specific mod
     * <li><var>\map\[mapname]</var>: Request only servers running a specific
     *     map
     * <li><var>\linux\1</var>: Request only linux servers
     * <li><var>\emtpy\1</var>: Request only **non**-empty servers
     * <li><var>\full\1</var>: Request only servers **not** full
     * <li><var>\proxy\1</var>: Request only spectator proxy servers
     * </ul>
     *
     * @param int $regionCode The region code to specify a location of the
     *        game servers
     * @param string $filter The filters that game servers should match
     * @param bool $force Return a list of servers even if an error occured
     *        while fetching them from the master server
     * @return array A list of game servers matching the given
     *         region and filters
     * @see setTimeout()
     * @see A2M_GET_SERVERS_BATCH2_Packet
     * @throws SteamCondenserException if a problem occurs while parsing the
     *         reply
     * @throws TimeoutException if too many timeouts occur while querying the
     *         master server
     */
    public function getServers($regionCode = MasterServer::REGION_ALL , $filter = '', $force = false) {
        $failCount   = 0;
        $finished    = false;
        $portNumber  = 0;
        $hostName    = '0.0.0.0';
        $serverArray = [];

        while(true) {
            $failCount = 0;
            try {
                do {
                    $this->socket->send(new A2MGETSERVERSBATCH2Packet($regionCode, "$hostName:$portNumber", $filter));
                    try {
                        $serverStringArray = $this->socket->getReply()->getServers();

                        foreach($serverStringArray as $serverString) {
                            $serverString = explode(':', $serverString);
                            $hostName = $serverString[0];
                            $portNumber = $serverString[1];

                            if($hostName != '0.0.0.0' && $portNumber != 0) {
                                $serverArray[] = [$hostName, $portNumber];
                            } else {
                                $finished = true;
                            }
                        }
                        $failCount = 0;
                    } catch(TimeoutException $e) {
                        $failCount ++;
                        if($failCount == self::$retries) {
                            throw $e;
                        }
                        $this->logger->info("Request to master server {$this->ipAddress} timed out, retrying...");
                    }
                } while(!$finished);
                break;
            } catch(TimeoutException $e) {
                if ($this->rotateIp()) {
                    if ($force) {
                        break;
                    }
                    throw $e;
                }
                $this->logger->info("Request to master server failed, retrying {$this->ipAddress}...");
            }
        }

        return array_unique($serverArray, SORT_REGULAR);
    }

    /**
     * Initializes the socket to communicate with the master server
     *
     * @see MasterServerSocket
     */
    public function initSocket() {
        $this->socket = new MasterServerSocket($this->ipAddress, $this->port);
    }

}