Last active
January 27, 2021 01:14
-
-
Save segg21/a3c79d69877c27fafabccf427e55e226 to your computer and use it in GitHub Desktop.
Steam Source UDP PHP Query Script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace Query { | |
use Query\Buffer\BinaryReader; | |
class Socket { | |
private $socket; | |
private static $timeout = array('sec'=>4, 'usec' => 0); | |
function __construct($host, $port) | |
{ | |
if(!($this->socket = @\socket_create(AF_INET, SOCK_DGRAM, 0))) | |
throw new \Exception('Could not create socket.'); | |
if(!@\socket_connect($this->socket, $host, intval($port))) | |
throw new \Exception('Could not connect to socket.'); | |
socket_set_option($this->socket, SOL_SOCKET, SO_RCVTIMEO, self::$timeout); | |
socket_set_option($this->socket, SOL_SOCKET, SO_SNDTIMEO, self::$timeout); | |
} | |
function send($data) { | |
$size = ((is_array($data)) ? count($data) : strlen($data)); | |
if(is_array($data)) $data = implode('', $data); | |
return ((\socket_send($this->socket, $data, $size, 0)) ? true : false); | |
} | |
function read($l = 1024 * 4) { | |
return array_map('ord', str_split(@\socket_read($this->socket, $l, PHP_BINARY_READ))); | |
} | |
function dispose(){ | |
if($this->socket instanceof \Socket) { | |
socket_close($this->socket); | |
} | |
unset($this->socket); | |
} | |
} | |
class Utils { | |
public static function Ord2Chr($a) { | |
foreach($a as $k => &$v) { | |
$a[$k] = chr($v); | |
} | |
return $a; | |
} | |
} | |
class Steam { | |
// Todo, support split protocols | |
public static function A2S_INFO($host, $port){ | |
$socket = new Socket($host, $port); | |
$socket->send(Utils::Ord2Chr(array(0xFF, 0xFF, 0xFF, 0xFF, 0x54, 0x46, 0x4b, 0x0))); | |
$reader = new Buffer\BinaryReader($socket->read()); | |
$socket->dispose(); | |
if($reader->Length() > 5 && $reader->readInt() == -1 && $reader->readByte() == 0x49) { | |
$query = (object) array( | |
'Protocol' => $reader->readByte(), | |
'Name' => $reader->readString(), | |
'Map' => $reader->readString(), | |
'Folder' => $reader->readString(), | |
'Game' => $reader->readString(), | |
'ID' => $reader->readShort(), | |
'Players' => $reader->readByte(), | |
'Max Players' => $reader->readByte(), | |
'Bots' => $reader->readByte(), | |
'Server Type' => chr($reader->readByte()), | |
'Environment' => chr($reader->readByte()), | |
'Visibility' => $reader->readByte(), | |
'Vac' => $reader->readByte(), | |
'Version' => $reader->readString(), | |
'EDF' => $reader->readByte(), | |
); | |
unset($socket, $reader); | |
return $query; | |
} | |
unset($socket, $reader); | |
return null; | |
} | |
public static function A2S_PLAYER($host, $port) { | |
$socket = new Socket($host, $port); | |
$packet = array(0xFF, 0xFF, 0xFF, 0xFF, 0x55, 0xFF, 0xFF, 0xFF, 0xFF); | |
$socket->send(Utils::Ord2Chr($packet)); | |
$reader = new Buffer\BinaryReader($socket->read(9)); | |
if($reader->Length() > 5 && $reader->readInt() == -1 && $reader->readByte() == 0x41) { | |
$packet[5] = $reader->readByte(); | |
$packet[6] = $reader->readByte(); | |
$packet[7] = $reader->readByte(); | |
$packet[8] = $reader->readByte(); | |
$socket->send(Utils::Ord2Chr($packet)); | |
$reader->set($socket->read()); | |
$socket->dispose(); | |
if($reader->Length() > 5 && $reader->readInt() == -1 && $reader->readByte() == 0x44) { | |
$players = $reader->readByte(); | |
$data = array(); | |
while(--$players > 0) { | |
array_push($data, array( | |
'Id' => $reader->readByte(), | |
'Name' => $reader->readString(), | |
'Score' => $reader->readInt64(), | |
//'Duration' => $reader->readFloat() | |
)); | |
} | |
unset($packet, $socket, $reader); | |
return $data; | |
} | |
} | |
unset($packet, $socket, $reader); | |
return null; | |
} | |
public static function A2S_RULES($host, $port) { | |
$socket = new Socket($host, $port); | |
$packet = array(0xFF, 0xFF, 0xFF, 0xFF, 0x56, 0xFF, 0xFF, 0xFF, 0xFF); | |
$socket->send(Utils::Ord2Chr($packet)); | |
$reader = new Buffer\BinaryReader($socket->read(9)); | |
if($reader->Length() > 5 && $reader->readInt() == -1 && $reader->readByte() == 0x41) { | |
$packet[5] = $reader->readByte(); | |
$packet[6] = $reader->readByte(); | |
$packet[7] = $reader->readByte(); | |
$packet[8] = $reader->readByte(); | |
$socket->send(Utils::Ord2Chr($packet)); | |
$reader->set($socket->read()); | |
$socket->dispose(); | |
if($reader->Length() > 5 && $reader->readInt() == -1 && $reader->readByte() == 0x45) { | |
$rules = $reader->readShort(); | |
$data = array(); | |
while(--$rules > 0) { | |
$data[$reader->readString()] = $reader->readString(); | |
} | |
unset($packet, $socket, $reader); | |
return $data; | |
} | |
} | |
unset($packet, $socket, $reader); | |
return null; | |
} | |
public static function A2S_PING($host, $port) { | |
$socket = new Socket($host, $port); | |
$socket->send(Utils::Ord2Chr(array(0xFF, 0xFF, 0xFF, 0xFF, 0x69))); | |
$reader = new Buffer\BinaryReader($socket->read()); | |
$socket->dispose(); | |
if($reader->Length() > 5 && $reader->readInt() == -1 && $reader->readByte() == 0x6A) { | |
$payload = $reader->readString(); | |
unset($socket, $reader); | |
return $payload; | |
} | |
unset($socket, $reader); | |
return null; | |
} | |
} | |
$ip = null; | |
$port = null; | |
$method = 'A2S_INFO'; | |
$class = '\Query\Steam'; | |
if(isset($_GET['ip']) && isset($_GET['port'])) { | |
$ip = $_GET['ip']; | |
$port = $_GET['port']; | |
if(isset($_GET['method'])) { | |
$method = $_GET['method']; | |
} | |
}else { | |
$path = explode('/', substr($_SERVER['PATH_INFO'], 1)); | |
switch(count($path)) { | |
case 1: | |
$path = explode(':', $path[0]); | |
if(count($path) == 2) { | |
$ip = $path[0]; | |
$port = intval($path[1]); | |
} | |
break; | |
case 2: | |
$method = $path[0]; | |
$path = explode(':', $path[1]); | |
if(count($path) == 2) { | |
$ip = $path[0]; | |
$port = intval($path[1]); | |
} | |
break; | |
default: throw new \Exception('Invalid Response'); | |
} | |
} | |
if($ip == null || $port == null) { | |
throw new \Exception('IP or PORT not set'); | |
} | |
if(!method_exists($class, $method)){ | |
throw new \Exception('Method `'.$method.'` does not exists!'); | |
} | |
header('Content-Type: application/json'); | |
die(json_encode($class::$method($ip, intval($port)) ?? [])); | |
} | |
namespace Query\Buffer { | |
class BinaryReader { | |
private $buffer, $length, $index; | |
function __construct($buffer) | |
{ | |
$this->buffer = $buffer; | |
$this->length = count($this->buffer); | |
$this->index = 0; | |
} | |
private function _read($bytes = 1) { | |
if(($this->index + $bytes) <= $this->length) { | |
$this->index += $bytes; | |
return array_splice($this->buffer, 0, $bytes); | |
} | |
throw new \Exception('Buffer overflow'); | |
} | |
private static function unpack($format, ...$data) { | |
array_unshift($data, 'C*'); | |
return unpack($format, call_user_func_array('pack', $data))[1]; | |
} | |
function set($array) { | |
if(is_array($array)){ | |
$this->buffer = $array; | |
$this->index = 0; | |
$this->length = count($this->buffer); | |
} | |
} | |
function Size(){ | |
return $this->length; | |
} | |
function Length(){ | |
return $this->length - $this->index; | |
} | |
function next(){ | |
$this->_read(); | |
return $this; | |
} | |
function readByte(){ | |
return self::unpack('C', $this->_read()[0]); | |
} | |
function readShort(){ | |
return self::unpack('s', $this->readByte(), $this->readByte()); | |
} | |
function readUShort(){ | |
return self::unpack('S', $this->readByte(), $this->readByte()); | |
} | |
function readInt(){ | |
return self::unpack('i', $this->readByte(), $this->readByte(), $this->readByte(), $this->readByte()); | |
} | |
function readUInt(){ | |
return self::unpack('I', $this->readByte(), $this->readByte(), $this->readByte(), $this->readByte()); | |
} | |
function readInt64(){ | |
return self::unpack('l', $this->readByte(), $this->readByte(), $this->readByte(), $this->readByte(), $this->readByte(),$this->readByte(), $this->readByte(),$this->readByte()); | |
} | |
function readUInt64(){ | |
return self::unpack('L', $this->readByte(), $this->readByte(), $this->readByte(), $this->readByte(), $this->readByte(),$this->readByte(), $this->readByte(),$this->readByte()); | |
} | |
function readFloat(){ | |
return self::unpack('f', $this->readByte(), $this->readByte(), $this->readByte(), $this->readByte(), $this->readByte(),$this->readByte(), $this->readByte(),$this->readByte()); | |
} | |
function readDouble(){ | |
return self::unpack('d', $this->readByte(), $this->readByte(), $this->readByte(), $this->readByte(), $this->readByte(),$this->readByte(), $this->readByte(),$this->readByte()); | |
} | |
function readString(){ | |
$string = ""; | |
while(($char = $this->readByte()) > 0) { | |
$string .= chr($char); | |
} | |
return $string; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment