Raknet.php 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. <?php
  2. /**
  3. * This file is part of GameQ.
  4. *
  5. * GameQ is free software; you can redistribute it and/or modify
  6. * it under the terms of the GNU Lesser General Public License as published by
  7. * the Free Software Foundation; either version 3 of the License, or
  8. * (at your option) any later version.
  9. *
  10. * GameQ is distributed in the hope that it will be useful,
  11. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. * GNU Lesser General Public License for more details.
  14. *
  15. * You should have received a copy of the GNU Lesser General Public License
  16. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  17. */
  18. namespace GameQ\Protocols;
  19. use GameQ\Buffer;
  20. use GameQ\Exception\Protocol as Exception;
  21. use GameQ\Protocol;
  22. use GameQ\Result;
  23. use GameQ\Server;
  24. /**
  25. * Raknet Protocol Class
  26. *
  27. * See https://wiki.vg/Raknet_Protocol for more techinal information
  28. *
  29. * @author Austin Bischoff <[email protected]>
  30. */
  31. class Raknet extends Protocol
  32. {
  33. /**
  34. * The magic string that is sent to get access to the server information
  35. */
  36. const OFFLINE_MESSAGE_DATA_ID = "\x00\xFF\xFF\x00\xFE\xFE\xFE\xFE\xFD\xFD\xFD\xFD\x12\x34\x56\x78";
  37. /**
  38. * Expected first part of the response from the server after query
  39. */
  40. const ID_UNCONNECTED_PONG = "\x1C";
  41. /**
  42. * Array of packets we want to look up.
  43. * Each key should correspond to a defined method in this or a parent class
  44. *
  45. * @type array
  46. */
  47. protected $packets = [
  48. self::PACKET_STATUS => "\x01%s%s\x02\x00\x00\x00\x00\x00\x00\x00", // Format time, magic,
  49. ];
  50. /**
  51. * The query protocol used to make the call
  52. *
  53. * @type string
  54. */
  55. protected $protocol = 'raknet';
  56. /**
  57. * String name of this protocol class
  58. *
  59. * @type string
  60. */
  61. protected $name = 'raknet';
  62. /**
  63. * Longer string name of this protocol class
  64. *
  65. * @type string
  66. */
  67. protected $name_long = "Raknet Server";
  68. /**
  69. * Do some work to build the packet we need to send out to query
  70. *
  71. * @param Server $server
  72. *
  73. * @return void
  74. */
  75. public function beforeSend(Server $server)
  76. {
  77. // Update the server status packet before it is sent
  78. $this->packets[self::PACKET_STATUS] = sprintf(
  79. $this->packets[self::PACKET_STATUS],
  80. pack('Q', time()),
  81. self::OFFLINE_MESSAGE_DATA_ID
  82. );
  83. }
  84. /**
  85. * Process the response
  86. *
  87. * @return array
  88. * @throws \GameQ\Exception\Protocol
  89. */
  90. public function processResponse()
  91. {
  92. // Merge the response array into a buffer. Unknown if this protocol does split packets or not
  93. $buffer = new Buffer(implode($this->packets_response));
  94. // Read first character from response. It should match below
  95. $header = $buffer->read(1);
  96. // Check first character to make sure the header matches
  97. if ($header !== self::ID_UNCONNECTED_PONG) {
  98. throw new Exception(sprintf(
  99. '%s The header returned "%s" does not match the expected header of "%s"',
  100. __METHOD__,
  101. bin2hex($header),
  102. bin2hex(self::ID_UNCONNECTED_PONG)
  103. ));
  104. }
  105. // Burn the time section
  106. $buffer->skip(8);
  107. // Server GUID is next
  108. $serverGUID = $buffer->readInt64();
  109. // Read the next set to check to make sure the "magic" matches
  110. $magicCheck = $buffer->read(16);
  111. // Magic check fails
  112. if ($magicCheck !== self::OFFLINE_MESSAGE_DATA_ID) {
  113. throw new Exception(sprintf(
  114. '%s The magic value returned "%s" does not match the expected value of "%s"',
  115. __METHOD__,
  116. bin2hex($magicCheck),
  117. bin2hex(self::OFFLINE_MESSAGE_DATA_ID)
  118. ));
  119. }
  120. // According to docs the next character is supposed to be used for a length and string for the following
  121. // character for the MOTD but it appears to be implemented incorrectly
  122. // Burn the next two characters instead of trying to do anything useful with them
  123. $buffer->skip(2);
  124. // Set the result to a new result instance
  125. $result = new Result();
  126. // Here on is server information delimited by semicolons (;)
  127. $info = explode(';', $buffer->getBuffer());
  128. $result->add('edition', $info[0]);
  129. $result->add('motd_line_1', $info[1]);
  130. $result->add('protocol_version', (int)$info[2]);
  131. $result->add('version', $info[3]);
  132. $result->add('num_players', (int)$info[4]);
  133. $result->add('max_players', (int)$info[5]);
  134. $result->add('server_uid', $info[6]);
  135. $result->add('motd_line_2', $info[7]);
  136. $result->add('gamemode', $info[8]);
  137. $result->add('gamemode_numeric', (int)$info[9]);
  138. $result->add('port_ipv4', (isset($info[10])) ? (int)$info[10] : null);
  139. $result->add('port_ipv6', (isset($info[11])) ? (int)$info[11] : null);
  140. $result->add('dedicated', 1);
  141. unset($header, $serverGUID, $magicCheck, $info);
  142. return $result->fetch();
  143. }
  144. }