mirror of https://github.com/YGGverse/hl-php.git
phpyggdrasilhalf-lifexash3dhalf-life-ipv6half-life-yggdrasilhalf-life-masterhl-phpxash3d-apihalf-life-composermaster-server-apihalf-life-phphalf-life-apimaster
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
174 lines
3.7 KiB
174 lines
3.7 KiB
<?php |
|
|
|
declare(strict_types=1); |
|
|
|
namespace Yggverse\Hl\Xash3D; |
|
|
|
class Master |
|
{ |
|
private string $_host; |
|
private int $_port; |
|
private int $_timeout; |
|
|
|
private array $_errors = []; |
|
|
|
public function __construct( |
|
string $host, |
|
int $port, |
|
int $timeout = 5 |
|
) |
|
{ |
|
$this->_host = $host; |
|
$this->_port = $port; |
|
$this->_timeout = $timeout; |
|
} |
|
|
|
private function _fclose( |
|
mixed $socket |
|
) |
|
{ |
|
if (true === is_resource($socket)) |
|
{ |
|
fclose( |
|
$socket |
|
); |
|
} |
|
} |
|
|
|
public function getServersIPv6( |
|
int $limit = 100, |
|
string $region = "\xFF", |
|
string $host = "0.0.0.0:0", |
|
int $port = 0, |
|
string $gamedir = "valve" |
|
): ?array |
|
{ |
|
// Init connection |
|
$socket = fsockopen( |
|
"udp://{$this->_host}", |
|
$this->_port, |
|
$code, |
|
$message, |
|
$this->_timeout |
|
); |
|
|
|
// Is connected |
|
if (true === is_resource($socket)) |
|
{ |
|
stream_set_timeout( |
|
$socket, |
|
$this->_timeout |
|
); |
|
} |
|
|
|
else |
|
{ |
|
$this->_errors[] = sprintf( |
|
_('Connection error: %s'), |
|
$message |
|
); |
|
|
|
$this->_fclose( |
|
$socket |
|
); |
|
|
|
return null; |
|
} |
|
|
|
// Filter query |
|
if (false === fwrite($socket, "1{$region}{$host}:{$port}\0\gamedir\t{$gamedir}\0")) |
|
{ |
|
$this->_errors[] = _('Could not send socket query'); |
|
|
|
$this->_fclose( |
|
$socket |
|
); |
|
|
|
return null; |
|
} |
|
|
|
// Skip header |
|
if (false === fread($socket, 6)) |
|
{ |
|
$this->_errors[] = _('Could not init packet header'); |
|
|
|
$this->_fclose( |
|
$socket |
|
); |
|
|
|
return null; |
|
} |
|
|
|
// Get servers |
|
$servers = []; |
|
|
|
for ($i = 0; $i < $limit; $i++) |
|
{ |
|
// Get host |
|
if (false === $host = fread($socket, 16)) |
|
{ |
|
break; |
|
} |
|
|
|
// Is end of packet |
|
if (true === str_ends_with(bin2hex($host), bin2hex("\0\0\0\0\0\0"))) |
|
{ |
|
break; |
|
} |
|
|
|
// Skip invalid host value |
|
if (false === $host = inet_ntop($host)) |
|
{ |
|
// Shift port bytes |
|
fread($socket, 2); |
|
|
|
continue; |
|
} |
|
|
|
// Decode first byte of port |
|
if (false === $byte1 = fread($socket, 1)) |
|
{ |
|
// Shift port byte |
|
fread($socket, 1); |
|
|
|
continue; |
|
} |
|
|
|
// Decode second byte of port |
|
if (false === $byte2 = fread($socket, 1)) |
|
{ |
|
continue; |
|
} |
|
|
|
// Calculate port value |
|
$port = ord($byte1) * 256 + ord($byte2); |
|
|
|
// Validate IPv6 result |
|
if ( |
|
false !== strpos($host, '.') || // filter_var not always works with mixed IPv6 |
|
false === filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) || |
|
false === filter_var($port, FILTER_VALIDATE_INT) |
|
) |
|
{ |
|
continue; |
|
} |
|
|
|
$servers["[{$host}]:{$port}"] = // keep unique |
|
[ |
|
'host' => $host, |
|
'port' => $port |
|
]; |
|
} |
|
|
|
$this->_fclose( |
|
$socket |
|
); |
|
|
|
return $servers; |
|
} |
|
|
|
public function getErrors(): array |
|
{ |
|
return $this->_errors; |
|
} |
|
} |