public function Read($Length = 1400) { $this->Buffer->Set(FRead($this->Socket, $Length)); if ($this->Buffer->Remaining() === 0) { // TODO: Should we throw an exception here? return; } $Header = $this->Buffer->GetLong(); if ($Header === -1) { // We don't have to do anything } else { if ($Header === -2) { $Packets = array(); $IsCompressed = false; $ReadMore = false; do { $RequestID = $this->Buffer->GetLong(); switch ($this->Engine) { case SourceQuery::GOLDSOURCE: $PacketCountAndNumber = $this->Buffer->GetByte(); $PacketCount = $PacketCountAndNumber & 0xf; $PacketNumber = $PacketCountAndNumber >> 4; break; case SourceQuery::SOURCE: $IsCompressed = ($RequestID & 0x80000000) !== 0; $PacketCount = $this->Buffer->GetByte(); $PacketNumber = $this->Buffer->GetByte() + 1; if ($IsCompressed) { $this->Buffer->GetLong(); // Split size $PacketChecksum = $this->Buffer->GetUnsignedLong(); } else { $this->Buffer->GetShort(); // Split size } break; } $Packets[$PacketNumber] = $this->Buffer->Get(); $ReadMore = $PacketCount > sizeof($Packets); } while ($ReadMore && $this->Sherlock($Length)); $Buffer = Implode($Packets); // TODO: Test this if ($IsCompressed) { // Let's make sure this function exists, it's not included in PHP by default if (!Function_Exists('bzdecompress')) { throw new RuntimeException('Received compressed packet, PHP doesn\'t have Bzip2 library installed, can\'t decompress.'); } $Buffer = bzdecompress($Buffer); if (CRC32($Buffer) !== $PacketChecksum) { throw new InvalidPacketException('CRC32 checksum mismatch of uncompressed packet data.', InvalidPacketException::CHECKSUM_MISMATCH); } } $this->Buffer->Set(SubStr($Buffer, 4)); } else { throw new InvalidPacketException('Socket read: Raw packet header mismatch. (0x' . DecHex($Header) . ')', InvalidPacketException::PACKET_HEADER_MISMATCH); } } }
public function Read($Length = 1400) { $this->Buffer->Set(FRead($this->Socket, $Length)); if ($this->Buffer->Remaining() > 0 && $this->Buffer->GetLong() == -2) { $Packets = array(); $IsCompressed = false; $ReadMore = false; do { $RequestID = $this->Buffer->GetLong(); switch ($this->Engine) { case SourceQuery::GOLDSOURCE: $PacketCountAndNumber = $this->Buffer->GetByte(); $PacketCount = $PacketCountAndNumber & 0xf; $PacketNumber = $PacketCountAndNumber >> 4; break; case SourceQuery::SOURCE: $IsCompressed = ($RequestID & 0x80000000) != 0; $PacketCount = $this->Buffer->GetByte(); $PacketNumber = $this->Buffer->GetByte() + 1; if ($IsCompressed) { $this->Buffer->GetLong(); // Split size $PacketChecksum = $this->Buffer->GetUnsignedLong(); } else { $this->Buffer->GetShort(); // Split size } break; } $Packets[$PacketNumber] = $this->Buffer->Get(); $ReadMore = $PacketCount > sizeof($Packets); } while ($ReadMore && $this->Sherlock($Length)); $Buffer = Implode($Packets); // TODO: Test this if ($IsCompressed) { // Let's make sure this function exists, it's not included in PHP by default if (!Function_Exists('bzdecompress')) { throw new RuntimeException('Received compressed packet, PHP doesn\'t have Bzip2 library installed, can\'t decompress.'); } $Data = bzdecompress($Data); if (CRC32($Data) != $PacketChecksum) { throw new SourceQueryException('CRC32 checksum mismatch of uncompressed packet data.'); } } $this->Buffer->Set(SubStr($Buffer, 4)); } }
/** * Get server information * * @throws SourceQueryException * * @return bool|array Returns array with information on success, false on failure */ public function GetInfo() { if (!$this->Connected) { return false; } $this->Socket->Write(self::A2S_INFO, "Source Engine Query"); $this->Socket->Read(); $Type = $this->Buffer->GetByte(); if ($Type == 0) { return false; } // Old GoldSource protocol, HLTV still uses it if ($Type == self::S2A_INFO_OLD && $this->Socket->Engine == self::GOLDSOURCE) { /** * If we try to read data again, and we get the result with type S2A_INFO (0x49) * That means this server is running dproto, * Because it sends answer for both protocols */ $Server['Address'] = $this->Buffer->GetString(); $Server['HostName'] = $this->Buffer->GetString(); $Server['Map'] = $this->Buffer->GetString(); $Server['ModDir'] = $this->Buffer->GetString(); $Server['ModDesc'] = $this->Buffer->GetString(); $Server['Players'] = $this->Buffer->GetByte(); $Server['MaxPlayers'] = $this->Buffer->GetByte(); $Server['Protocol'] = $this->Buffer->GetByte(); $Server['Dedicated'] = Chr($this->Buffer->GetByte()); $Server['Os'] = Chr($this->Buffer->GetByte()); $Server['Password'] = $this->Buffer->GetByte() == 1; $Server['IsMod'] = $this->Buffer->GetByte() == 1; if ($Server['IsMod']) { $Mod['Url'] = $this->Buffer->GetString(); $Mod['Download'] = $this->Buffer->GetString(); $this->Buffer->Get(1); // NULL byte $Mod['Version'] = $this->Buffer->GetLong(); $Mod['Size'] = $this->Buffer->GetLong(); $Mod['ServerSide'] = $this->Buffer->GetByte() == 1; $Mod['CustomDLL'] = $this->Buffer->GetByte() == 1; } $Server['Secure'] = $this->Buffer->GetByte() == 1; $Server['Bots'] = $this->Buffer->GetByte(); if (isset($Mod)) { $Server['Mod'] = $Mod; } return $Server; } if ($Type != self::S2A_INFO) { throw new SourceQueryException('GetInfo: Packet header mismatch. (0x' . DecHex($Type) . ')'); } $Server['Protocol'] = $this->Buffer->GetByte(); $Server['HostName'] = $this->Buffer->GetString(); $Server['Map'] = $this->Buffer->GetString(); $Server['ModDir'] = $this->Buffer->GetString(); $Server['ModDesc'] = $this->Buffer->GetString(); $Server['AppID'] = $this->Buffer->GetShort(); $Server['Players'] = $this->Buffer->GetByte(); $Server['MaxPlayers'] = $this->Buffer->GetByte(); $Server['Bots'] = $this->Buffer->GetByte(); $Server['Dedicated'] = Chr($this->Buffer->GetByte()); $Server['Os'] = Chr($this->Buffer->GetByte()); $Server['Password'] = $this->Buffer->GetByte() == 1; $Server['Secure'] = $this->Buffer->GetByte() == 1; // The Ship if ($Server['AppID'] == 2400) { $Server['GameMode'] = $this->Buffer->GetByte(); $Server['WitnessCount'] = $this->Buffer->GetByte(); $Server['WitnessTime'] = $this->Buffer->GetByte(); } $Server['Version'] = $this->Buffer->GetString(); // Extra Data Flags if ($this->Buffer->Remaining() > 0) { $Flags = $this->Buffer->GetByte(); // The server's game port if ($Flags & 0x80) { $Server['GamePort'] = $this->Buffer->GetShort(); } // The server's SteamID - does this serve any purpose? if ($Flags & 0x10) { $Server['ServerID'] = $this->Buffer->GetUnsignedLong() | $this->Buffer->GetUnsignedLong() << 32; } // The spectator port and then the spectator server name if ($Flags & 0x40) { $Server['SpecPort'] = $this->Buffer->GetShort(); $Server['SpecName'] = $this->Buffer->GetString(); } // The game tag data string for the server if ($Flags & 0x20) { $Server['GameTags'] = $this->Buffer->GetString(); } // 0x01 - The server's 64-bit GameID } return $Server; }
/** * Get server information * * @throws InvalidPacketException * * @return bool|array Returns array with information on success, false on failure */ public function GetInfo() { if (!$this->Connected) { return false; } $this->Socket->Write(self::A2S_INFO, "Source Engine Query"); $this->Socket->Read(); $Type = $this->Buffer->GetByte(); if ($Type === 0) { return false; } // Old GoldSource protocol, HLTV still uses it if ($Type === self::S2A_INFO_OLD && $this->Socket->Engine === self::GOLDSOURCE) { /** * If we try to read data again, and we get the result with type S2A_INFO (0x49) * That means this server is running dproto, * Because it sends answer for both protocols */ $Server['Address'] = $this->Buffer->GetString(); $Server['HostName'] = $this->Buffer->GetString(); $Server['Map'] = $this->Buffer->GetString(); $Server['ModDir'] = $this->Buffer->GetString(); $Server['ModDesc'] = $this->Buffer->GetString(); $Server['Players'] = $this->Buffer->GetByte(); $Server['MaxPlayers'] = $this->Buffer->GetByte(); $Server['Protocol'] = $this->Buffer->GetByte(); $Server['Dedicated'] = Chr($this->Buffer->GetByte()); $Server['Os'] = Chr($this->Buffer->GetByte()); $Server['Password'] = $this->Buffer->GetByte() === 1; $Server['IsMod'] = $this->Buffer->GetByte() === 1; if ($Server['IsMod']) { $Mod['Url'] = $this->Buffer->GetString(); $Mod['Download'] = $this->Buffer->GetString(); $this->Buffer->Get(1); // NULL byte $Mod['Version'] = $this->Buffer->GetLong(); $Mod['Size'] = $this->Buffer->GetLong(); $Mod['ServerSide'] = $this->Buffer->GetByte() === 1; $Mod['CustomDLL'] = $this->Buffer->GetByte() === 1; } $Server['Secure'] = $this->Buffer->GetByte() === 1; $Server['Bots'] = $this->Buffer->GetByte(); if (isset($Mod)) { $Server['Mod'] = $Mod; } return $Server; } if ($Type !== self::S2A_INFO) { throw new InvalidPacketException('GetInfo: Packet header mismatch. (0x' . DecHex($Type) . ')', InvalidPacketException::PACKET_HEADER_MISMATCH); } $Server['Protocol'] = $this->Buffer->GetByte(); $Server['HostName'] = $this->Buffer->GetString(); $Server['Map'] = $this->Buffer->GetString(); $Server['ModDir'] = $this->Buffer->GetString(); $Server['ModDesc'] = $this->Buffer->GetString(); $Server['AppID'] = $this->Buffer->GetShort(); $Server['Players'] = $this->Buffer->GetByte(); $Server['MaxPlayers'] = $this->Buffer->GetByte(); $Server['Bots'] = $this->Buffer->GetByte(); $Server['Dedicated'] = Chr($this->Buffer->GetByte()); $Server['Os'] = Chr($this->Buffer->GetByte()); $Server['Password'] = $this->Buffer->GetByte() === 1; $Server['Secure'] = $this->Buffer->GetByte() === 1; // The Ship (they violate query protocol spec by modifying the response) if ($Server['AppID'] === 2400) { $Server['GameMode'] = $this->Buffer->GetByte(); $Server['WitnessCount'] = $this->Buffer->GetByte(); $Server['WitnessTime'] = $this->Buffer->GetByte(); } $Server['Version'] = $this->Buffer->GetString(); // Extra Data Flags if ($this->Buffer->Remaining() > 0) { $Server['ExtraDataFlags'] = $Flags = $this->Buffer->GetByte(); // The server's game port if ($Flags & 0x80) { $Server['GamePort'] = $this->Buffer->GetShort(); } // The server's SteamID - does this serve any purpose? if ($Flags & 0x10) { $Server['ServerID'] = $this->Buffer->GetUnsignedLong() | $this->Buffer->GetUnsignedLong() << 32; // TODO: verify this } // The spectator port and then the spectator server name if ($Flags & 0x40) { $Server['SpecPort'] = $this->Buffer->GetShort(); $Server['SpecName'] = $this->Buffer->GetString(); } // The game tag data string for the server if ($Flags & 0x20) { $Server['GameTags'] = $this->Buffer->GetString(); } // GameID -- alternative to AppID? if ($Flags & 0x1) { $Server['GameID'] = $this->Buffer->GetUnsignedLong() | $this->Buffer->GetUnsignedLong() << 32; } if ($this->Buffer->Remaining() > 0) { throw new InvalidPacketException('GetInfo: unread data? ' . $this->Buffer->Remaining() . ' bytes remaining in the buffer. Please report it to the library developer.', InvalidPacketException::BUFFER_NOT_EMPTY); } } return $Server; }