331 lines
9.2 KiB
PHP
331 lines
9.2 KiB
PHP
<?php
|
|
if (!DEFINED('EGP'))
|
|
exit(header('Refresh: 0; URL=http://' . $_SERVER['SERVER_NAME'] . '/404'));
|
|
|
|
class SourceQuery
|
|
{
|
|
const GOLDSOURCE = 0;
|
|
const SOURCE = 1;
|
|
const A2S_PING = 0x69;
|
|
const A2S_INFO = 0x54;
|
|
const A2S_PLAYER = 0x55;
|
|
const A2S_RULES = 0x56;
|
|
const A2S_SERVERQUERY_GETCHALLENGE = 0x57;
|
|
const S2A_PING = 0x6A;
|
|
const S2A_CHALLENGE = 0x41;
|
|
const S2A_INFO = 0x49;
|
|
const S2A_INFO_OLD = 0x6D;
|
|
const S2A_PLAYER = 0x44;
|
|
const S2A_RULES = 0x45;
|
|
const S2A_RCON = 0x6C;
|
|
const SERVERDATA_EXECCOMMAND = 2;
|
|
const SERVERDATA_AUTH = 3;
|
|
const SERVERDATA_RESPONSE_VALUE = 0;
|
|
const SERVERDATA_AUTH_RESPONSE = 2;
|
|
|
|
private $Rcon;
|
|
private $Socket;
|
|
private $Connected;
|
|
private $Challenge;
|
|
private $UseOldGetChallengeMethod;
|
|
|
|
public function __construct(BaseSocket $Socket = null)
|
|
{
|
|
$this->Socket = $Socket ?: new Socket();
|
|
}
|
|
|
|
public function __destruct()
|
|
{
|
|
$this->Disconnect();
|
|
}
|
|
|
|
public function Connect($Address, $Port, $Timeout = 3, $Engine = self::SOURCE)
|
|
{
|
|
$this->Disconnect();
|
|
|
|
if (!is_int($Timeout) || $Timeout < 0)
|
|
return false;
|
|
|
|
$this->Socket->Open($Address, (int)$Port, $Timeout, (int)$Engine);
|
|
|
|
$this->Connected = true;
|
|
}
|
|
|
|
public function SetUseOldGetChallengeMethod($Value)
|
|
{
|
|
$Previous = $this->UseOldGetChallengeMethod;
|
|
|
|
$this->UseOldGetChallengeMethod = $Value === true;
|
|
|
|
return $Previous;
|
|
}
|
|
|
|
public function Disconnect()
|
|
{
|
|
$this->Connected = false;
|
|
$this->Challenge = 0;
|
|
|
|
$this->Socket->Close();
|
|
|
|
if ($this->Rcon) {
|
|
$this->Rcon->Close();
|
|
|
|
$this->Rcon = null;
|
|
}
|
|
}
|
|
|
|
public function Ping()
|
|
{
|
|
if (!$this->Connected)
|
|
return false;
|
|
|
|
$this->Socket->Write(self::A2S_PING);
|
|
$Buffer = $this->Socket->Read();
|
|
|
|
return $Buffer->GetByte() === self::S2A_PING;
|
|
}
|
|
|
|
public function GetInfo()
|
|
{
|
|
if (!$this->Connected)
|
|
return false;
|
|
|
|
$this->Socket->Write(self::A2S_INFO, "Source Engine Query\0");
|
|
$Buffer = $this->Socket->Read();
|
|
|
|
$Type = $Buffer->GetByte();
|
|
|
|
if ($Type === self::S2A_INFO_OLD && $this->Socket->Engine === self::GOLDSOURCE) {
|
|
$Server['Address'] = $Buffer->GetString();
|
|
$Server['HostName'] = $Buffer->GetString();
|
|
$Server['Map'] = $Buffer->GetString();
|
|
$Server['ModDir'] = $Buffer->GetString();
|
|
$Server['ModDesc'] = $Buffer->GetString();
|
|
$Server['Players'] = $Buffer->GetByte();
|
|
$Server['MaxPlayers'] = $Buffer->GetByte();
|
|
$Server['Protocol'] = $Buffer->GetByte();
|
|
$Server['Dedicated'] = Chr($Buffer->GetByte());
|
|
$Server['Os'] = Chr($Buffer->GetByte());
|
|
$Server['Password'] = $Buffer->GetByte() === 1;
|
|
$Server['IsMod'] = $Buffer->GetByte() === 1;
|
|
|
|
if ($Server['IsMod']) {
|
|
$Mod['Url'] = $Buffer->GetString();
|
|
$Mod['Download'] = $Buffer->GetString();
|
|
$Buffer->Get(1);
|
|
$Mod['Version'] = $Buffer->GetLong();
|
|
$Mod['Size'] = $Buffer->GetLong();
|
|
$Mod['ServerSide'] = $Buffer->GetByte() === 1;
|
|
$Mod['CustomDLL'] = $Buffer->GetByte() === 1;
|
|
}
|
|
|
|
$Server['Secure'] = $Buffer->GetByte() === 1;
|
|
$Server['Bots'] = $Buffer->GetByte();
|
|
|
|
if (isset($Mod))
|
|
$Server['Mod'] = $Mod;
|
|
|
|
return $Server;
|
|
}
|
|
|
|
if ($Type !== self::S2A_INFO)
|
|
return false;
|
|
|
|
if ($Type !== self::S2A_INFO)
|
|
return false;
|
|
|
|
$Server['Protocol'] = $Buffer->GetByte();
|
|
$Server['HostName'] = $Buffer->GetString();
|
|
$Server['Map'] = $Buffer->GetString();
|
|
$Server['ModDir'] = $Buffer->GetString();
|
|
$Server['ModDesc'] = $Buffer->GetString();
|
|
$Server['AppID'] = $Buffer->GetShort();
|
|
$Server['Players'] = $Buffer->GetByte();
|
|
$Server['MaxPlayers'] = $Buffer->GetByte();
|
|
$Server['Bots'] = $Buffer->GetByte();
|
|
$Server['Dedicated'] = Chr($Buffer->GetByte());
|
|
$Server['Os'] = Chr($Buffer->GetByte());
|
|
$Server['Password'] = $Buffer->GetByte() === 1;
|
|
$Server['Secure'] = $Buffer->GetByte() === 1;
|
|
|
|
if ($Server['AppID'] === 2400) {
|
|
$Server['GameMode'] = $Buffer->GetByte();
|
|
$Server['WitnessCount'] = $Buffer->GetByte();
|
|
$Server['WitnessTime'] = $Buffer->GetByte();
|
|
}
|
|
|
|
$Server['Version'] = $Buffer->GetString();
|
|
|
|
if ($Buffer->Remaining() > 0) {
|
|
$Server['ExtraDataFlags'] = $Flags = $Buffer->GetByte();
|
|
|
|
if ($Flags & 0x80)
|
|
$Server['GamePort'] = $Buffer->GetShort();
|
|
|
|
if ($Flags & 0x10) {
|
|
$SteamIDLower = $Buffer->GetUnsignedLong();
|
|
$SteamIDInstance = $Buffer->GetUnsignedLong();
|
|
$SteamID = 0;
|
|
|
|
if (PHP_INT_SIZE === 4) {
|
|
if (extension_loaded('gmp')) {
|
|
$SteamIDLower = gmp_abs($SteamIDLower);
|
|
$SteamIDInstance = gmp_abs($SteamIDInstance);
|
|
$SteamID = gmp_strval(gmp_or($SteamIDLower, gmp_mul($SteamIDInstance, gmp_pow(2, 32))));
|
|
} else
|
|
return false;
|
|
} else
|
|
$SteamID = $SteamIDLower | ($SteamIDInstance << 32);
|
|
|
|
$Server['SteamID'] = $SteamID;
|
|
|
|
unset($SteamIDLower, $SteamIDInstance, $SteamID);
|
|
}
|
|
|
|
if ($Flags & 0x40) {
|
|
$Server['SpecPort'] = $Buffer->GetShort();
|
|
$Server['SpecName'] = $Buffer->GetString();
|
|
}
|
|
|
|
if ($Flags & 0x20)
|
|
$Server['GameTags'] = $Buffer->GetString();
|
|
|
|
if ($Flags & 0x01)
|
|
$Server['GameID'] = $Buffer->GetUnsignedLong() | ($Buffer->GetUnsignedLong() << 32);
|
|
|
|
if ($Buffer->Remaining() > 0)
|
|
return false;
|
|
}
|
|
|
|
return $Server;
|
|
}
|
|
|
|
public function GetPlayers()
|
|
{
|
|
if (!$this->Connected)
|
|
return false;
|
|
|
|
$this->GetChallenge(self::A2S_PLAYER, self::S2A_PLAYER);
|
|
|
|
$this->Socket->Write(self::A2S_PLAYER, $this->Challenge);
|
|
$Buffer = $this->Socket->Read(14000);
|
|
$Type = $Buffer->GetByte();
|
|
|
|
if ($Type !== self::S2A_PLAYER)
|
|
return false;
|
|
|
|
$Players = [];
|
|
$Count = $Buffer->GetByte();
|
|
|
|
while ($Count-- > 0 && $Buffer->Remaining() > 0) {
|
|
$Player['Id'] = $Buffer->GetByte();
|
|
$Player['Name'] = $Buffer->GetString();
|
|
$Player['Frags'] = $Buffer->GetLong();
|
|
$Player['Time'] = (int)$Buffer->GetFloat();
|
|
$Player['TimeF'] = GMDate(($Player['Time'] > 3600 ? "H:i:s" : "i:s"), $Player['Time']);
|
|
|
|
$Players[] = $Player;
|
|
}
|
|
|
|
return $Players;
|
|
}
|
|
|
|
public function GetRules()
|
|
{
|
|
if (!$this->Connected)
|
|
return false;
|
|
|
|
$this->GetChallenge(self::A2S_RULES, self::S2A_RULES);
|
|
$this->Socket->Write(self::A2S_RULES, $this->Challenge);
|
|
$Buffer = $this->Socket->Read();
|
|
$Type = $Buffer->GetByte();
|
|
|
|
if ($Type !== self::S2A_RULES)
|
|
return false;
|
|
|
|
$Rules = [];
|
|
$Count = $Buffer->GetShort();
|
|
|
|
while ($Count-- > 0 && $Buffer->Remaining() > 0) {
|
|
$Rule = $Buffer->GetString();
|
|
$Value = $Buffer->GetString();
|
|
|
|
if (!empty($Rule))
|
|
$Rules[$Rule] = $Value;
|
|
}
|
|
|
|
return $Rules;
|
|
}
|
|
|
|
private function GetChallenge($Header, $ExpectedResult)
|
|
{
|
|
if ($this->Challenge)
|
|
return;
|
|
|
|
if ($this->UseOldGetChallengeMethod)
|
|
$Header = self::A2S_SERVERQUERY_GETCHALLENGE;
|
|
|
|
$this->Socket->Write($Header, "\xFF\xFF\xFF\xFF");
|
|
$Buffer = $this->Socket->Read();
|
|
$Type = $Buffer->GetByte();
|
|
|
|
switch ($Type) {
|
|
case self::S2A_CHALLENGE:
|
|
{
|
|
$this->Challenge = $Buffer->Get(4);
|
|
|
|
return;
|
|
}
|
|
case $ExpectedResult:
|
|
{
|
|
return;
|
|
}
|
|
case 0:
|
|
{
|
|
return;
|
|
}
|
|
default:
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
public function SetRconPassword($Password)
|
|
{
|
|
if (!$this->Connected) {
|
|
return false;
|
|
}
|
|
|
|
switch ($this->Socket->Engine) {
|
|
case SourceQuery::GOLDSOURCE:
|
|
{
|
|
$this->Rcon = new GoldSourceRcon($this->Socket);
|
|
|
|
break;
|
|
}
|
|
case SourceQuery::SOURCE:
|
|
{
|
|
$this->Rcon = new SourceRcon($this->Socket);
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
$this->Rcon->Open();
|
|
$this->Rcon->Authorize($Password);
|
|
}
|
|
|
|
public function Rcon($Command)
|
|
{
|
|
if (!$this->Connected) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->Rcon === null) {
|
|
return false;
|
|
}
|
|
|
|
return $this->Rcon->Command($Command);
|
|
}
|
|
} |