<?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-2011, Sebastian Staudt
*
* @license http://www.opensource.org/licenses/bsd-license.php New BSD License
*/
require_once STEAM_CONDENSER_PATH . 'steam/packets/A2M_GET_SERVERS_BATCH2_Packet.php';
require_once STEAM_CONDENSER_PATH . 'steam/packets/C2M_CHECKMD5_Packet.php';
require_once STEAM_CONDENSER_PATH . 'steam/packets/S2M_HEARTBEAT2_Packet.php';
require_once STEAM_CONDENSER_PATH . 'steam/servers/Server.php';
require_once STEAM_CONDENSER_PATH . 'steam/sockets/MasterServerSocket.php';
/**
* 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:27010';
/**
* @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
*/
private $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;
}
/**
* Request a challenge number from the master server.
*
* This is used for further communication with the master server.
*
* Please note that this is <b>not</b> needed for finding servers using
* {@link getServers()}.
*
* @return The challenge number returned from the master server
* @see sendHeartbeat()
* @throws IOException if the request fails
* @throws SteamCondenserException if a problem occurs while parsing the
* reply
* @throws TimeoutException if the request times out
*/
public function getChallenge() {
while(true) {
try {
$this->socket->send(new C2M_CHECKMD5_Packet());
return $this->socket->getReply()->getChallenge();
} catch(Exception $e) {
if($this->rotateIp()) {
throw $e;
}
}
}
}
/**
* 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\</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';
while(true) {
try {
do {
$this->socket->send(new A2M_GET_SERVERS_BATCH2_Packet($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[] = array($hostName, $portNumber);
} else {
$finished = true;
}
}
$failCount = 0;
} catch(TimeoutException $e) {
$failCount ++;
if($failCount == self::$retries) {
throw $e;
}
}
} while(!$finished);
break;
} catch(Exception $e) {
if($this->rotateIp() && !$force) {
throw $e;
}
}
}
return $serverArray;
}
/**
* Initializes the socket to communicate with the master server
*
* @see MasterServerSocket
*/
public function initSocket() {
$this->socket = new MasterServerSocket($this->ipAddress, $this->port);
}
/**
* Sends a constructed heartbeat to the master server
*
* This can be used to check server versions externally.
*
* @param array $data The heartbeat data to send to the master server
* @return The reply from the master server â usually zero or more packets.
* Zero means either the heartbeat was accepted by the master or
* there was a timeout. So usually it's best to repeat a heartbeat
* a few times when not receiving any packets.
* @throws SteamCondenserException if heartbeat data is missing the
* challenge number or the reply cannot be parsed
*/
public function sendHeartbeat($data) {
while(true) {
try {
$this->socket->send(new S2M_HEARTBEAT2_Packet($data));
$replyPackets = array();
try {
do {
$replyPackets[] = $this->socket->getReply();
} while(true);
} catch(TimeoutException $e) {}
return $replyPackets;
} catch(Exception $e) {
if($this->rotateIp()) {
throw $e;
}
}
}
}
}
?>