Compare commits

..

No commits in common. "main" and "1.0.4" have entirely different histories.
main ... 1.0.4

View file

@ -4,27 +4,6 @@ declare(strict_types=1);
namespace Yggverse\Hl\Xash3D; namespace Yggverse\Hl\Xash3D;
enum Family: int {
case IPv4 = 4;
case IPv6 = 16;
}
enum Region: string {
case USEastCoast = "0x00";
case USWestCoast = "0x01";
case SouthAmerica = "0x02";
case Europe = "0x03";
case Asia = "0x04";
case Australia = "0x05";
case MiddleEast = "0x06";
case Africa = "0x07";
case World = "0xff";
}
enum Game: string {
case Valve = "valve";
}
class Master class Master
{ {
private string $_host; private string $_host;
@ -35,7 +14,7 @@ class Master
public function __construct( public function __construct(
string $host, string $host,
int $port = 27010, int $port,
int $timeout = 5 int $timeout = 5
) )
{ {
@ -56,18 +35,14 @@ class Master
} }
} }
// Legacy protocol implementation does not support mixed address families public function getServersIPv6(
// in the binary master socket response, use separated method for IPv4 servers.
public function getServers(
int $limit = 100, int $limit = 100,
string $host = "0.0.0.0", string $region = "\xFF",
string $host = "0.0.0.0:0",
int $port = 0, int $port = 0,
Game $game = Game::Valve, string $gamedir = "valve"
Region $region = Region::World
): ?array ): ?array
{ {
$family = filter_var($host, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4) ? Family::IPv4 : Family::IPv6;
// Init connection // Init connection
$socket = fsockopen( $socket = fsockopen(
"udp://{$this->_host}", "udp://{$this->_host}",
@ -77,8 +52,6 @@ class Master
$this->_timeout $this->_timeout
); );
$master = "{$this->_host}:{$this->_port}";
// Is connected // Is connected
if (true === is_resource($socket)) if (true === is_resource($socket))
{ {
@ -91,7 +64,7 @@ class Master
else else
{ {
$this->_errors[] = sprintf( $this->_errors[] = sprintf(
_("Connection error for $master: %s"), _('Connection error: %s'),
$message $message
); );
@ -103,9 +76,9 @@ class Master
} }
// Filter query // Filter query
if (false === fwrite($socket, "1{$region->value}{$host}:{$port}\0\\gamedir\\{$game->value}\0")) if (false === fwrite($socket, "1{$region}{$host}:{$port}\0\gamedir\t{$gamedir}\0"))
{ {
$this->_errors[] = _("Could not send socket query for $master"); $this->_errors[] = _('Could not send socket query');
$this->_fclose( $this->_fclose(
$socket $socket
@ -117,7 +90,7 @@ class Master
// Skip header // Skip header
if (false === fread($socket, 6)) if (false === fread($socket, 6))
{ {
$this->_errors[] = _("Could not init packet header for $master"); $this->_errors[] = _('Could not init packet header');
$this->_fclose( $this->_fclose(
$socket $socket
@ -131,52 +104,59 @@ class Master
for ($i = 0; $i < $limit; $i++) for ($i = 0; $i < $limit; $i++)
{ {
// Get host bytes // Get host
if (false === $host = fread($socket, $family->value)) if (false === $host = fread($socket, 16))
{ {
$this->_errors[] = _("Invalid `host` fragment in packet at $i for $master");
break; break;
} }
// End of packet // Is end of packet
if (true === str_ends_with(bin2hex($host), bin2hex("\0\0\0\0\0\0"))) if (true === str_ends_with(bin2hex($host), bin2hex("\0\0\0\0\0\0")))
{ {
break; break;
} }
// Get host string // Skip invalid host value
if (false === $host = inet_ntop($host)) if (false === $host = inet_ntop($host))
{ {
$this->_errors[] = _("Invalid `host` value in packet at $i for $master"); // Shift port bytes
break; fread($socket, 2);
continue;
} }
// Get port bytes // Decode first byte of port
if (false === $p = fread($socket, 2)) if (false === $byte1 = fread($socket, 1))
{ {
$this->_errors[] = _("Invalid `port` fragment in packet at $i for $master"); // Shift port byte
break; fread($socket, 1);
continue;
} }
// Get port value // Decode second byte of port
if (false === $p = unpack('nport', $p)) if (false === $byte2 = fread($socket, 1))
{ {
$this->_errors[] = _("Invalid `port` value in packet at $i for $master"); continue;
break;
} }
// Validate result // Calculate port value
if (false === filter_var($host, FILTER_VALIDATE_IP, $family == Family::IPv6 ? FILTER_FLAG_IPV6 $port = ord($byte1) * 256 + ord($byte2);
: FILTER_FLAG_IPV4) || empty($p['port']))
// 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)
)
{ {
$this->_errors[] = _("Invalid socket address in packet at $i for $master"); continue;
break;
} }
$servers["{$host}{$p['port']}"] = // keep unique $servers["[{$host}]:{$port}"] = // keep unique
[ [
'host' => $host, 'host' => $host,
'port' => $p['port'] 'port' => $port
]; ];
} }