Quake2.php 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. <?php
  2. namespace GameQ\Protocols;
  3. use GameQ\Protocol;
  4. use GameQ\Buffer;
  5. use GameQ\Result;
  6. use GameQ\Exception\Protocol as Exception;
  7. /**
  8. * Quake2 Protocol Class
  9. *
  10. * Handles processing Quake 3 servers
  11. *
  12. * @package GameQ\Protocols
  13. */
  14. class Quake2 extends Protocol
  15. {
  16. /**
  17. * Array of packets we want to look up.
  18. * Each key should correspond to a defined method in this or a parent class
  19. *
  20. * @type array
  21. */
  22. protected $packets = [
  23. self::PACKET_STATUS => "\xFF\xFF\xFF\xFFstatus\x00",
  24. ];
  25. /**
  26. * Use the response flag to figure out what method to run
  27. *
  28. * @type array
  29. */
  30. protected $responses = [
  31. "\xFF\xFF\xFF\xFF\x70\x72\x69\x6e\x74" => 'processStatus',
  32. ];
  33. /**
  34. * The query protocol used to make the call
  35. *
  36. * @type string
  37. */
  38. protected $protocol = 'quake2';
  39. /**
  40. * String name of this protocol class
  41. *
  42. * @type string
  43. */
  44. protected $name = 'quake2';
  45. /**
  46. * Longer string name of this protocol class
  47. *
  48. * @type string
  49. */
  50. protected $name_long = "Quake 2 Server";
  51. /**
  52. * The client join link
  53. *
  54. * @type string
  55. */
  56. protected $join_link = null;
  57. /**
  58. * Normalize settings for this protocol
  59. *
  60. * @type array
  61. */
  62. protected $normalize = [
  63. // General
  64. 'general' => [
  65. // target => source
  66. 'gametype' => 'gamename',
  67. 'hostname' => 'hostname',
  68. 'mapname' => 'mapname',
  69. 'maxplayers' => 'maxclients',
  70. 'mod' => 'g_gametype',
  71. 'numplayers' => 'clients',
  72. 'password' => 'password',
  73. ],
  74. // Individual
  75. 'player' => [
  76. 'name' => 'name',
  77. 'ping' => 'ping',
  78. 'score' => 'frags',
  79. ],
  80. ];
  81. /**
  82. * Handle response from the server
  83. *
  84. * @return mixed
  85. * @throws Exception
  86. */
  87. public function processResponse()
  88. {
  89. // Make a buffer
  90. $buffer = new Buffer(implode('', $this->packets_response));
  91. // Grab the header
  92. $header = $buffer->readString("\x0A");
  93. // Figure out which packet response this is
  94. if (empty($header) || !array_key_exists($header, $this->responses)) {
  95. throw new Exception(__METHOD__ . " response type '" . bin2hex($header) . "' is not valid");
  96. }
  97. return call_user_func_array([$this, $this->responses[$header]], [$buffer]);
  98. }
  99. /**
  100. * Process the status response
  101. *
  102. * @param Buffer $buffer
  103. *
  104. * @return array
  105. */
  106. protected function processStatus(Buffer $buffer)
  107. {
  108. // We need to split the data and offload
  109. $results = $this->processServerInfo(new Buffer($buffer->readString("\x0A")));
  110. $results = array_merge_recursive(
  111. $results,
  112. $this->processPlayers(new Buffer($buffer->getBuffer()))
  113. );
  114. unset($buffer);
  115. // Return results
  116. return $results;
  117. }
  118. /**
  119. * Handle processing the server information
  120. *
  121. * @param Buffer $buffer
  122. *
  123. * @return array
  124. */
  125. protected function processServerInfo(Buffer $buffer)
  126. {
  127. // Set the result to a new result instance
  128. $result = new Result();
  129. // Burn leading \ if one exists
  130. $buffer->readString('\\');
  131. // Key / value pairs
  132. while ($buffer->getLength()) {
  133. // Add result
  134. $result->add(
  135. trim($buffer->readString('\\')),
  136. utf8_encode(trim($buffer->readStringMulti(['\\', "\x0a"])))
  137. );
  138. }
  139. $result->add('password', 0);
  140. $result->add('mod', 0);
  141. unset($buffer);
  142. return $result->fetch();
  143. }
  144. /**
  145. * Handle processing of player data
  146. *
  147. * @param Buffer $buffer
  148. *
  149. * @return array
  150. */
  151. protected function processPlayers(Buffer $buffer)
  152. {
  153. // Some games do not have a number of current players
  154. $playerCount = 0;
  155. // Set the result to a new result instance
  156. $result = new Result();
  157. // Loop until we are out of data
  158. while ($buffer->getLength()) {
  159. // Make a new buffer with this block
  160. $playerInfo = new Buffer($buffer->readString("\x0A"));
  161. // Add player info
  162. $result->addPlayer('frags', $playerInfo->readString("\x20"));
  163. $result->addPlayer('ping', $playerInfo->readString("\x20"));
  164. // Skip first "
  165. $playerInfo->skip(1);
  166. // Add player name, encoded
  167. $result->addPlayer('name', utf8_encode(trim(($playerInfo->readString('"')))));
  168. // Skip first "
  169. $playerInfo->skip(2);
  170. // Add address
  171. $result->addPlayer('address', trim($playerInfo->readString('"')));
  172. // Increment
  173. $playerCount++;
  174. // Clear
  175. unset($playerInfo);
  176. }
  177. $result->add('clients', $playerCount);
  178. // Clear
  179. unset($buffer, $playerCount);
  180. return $result->fetch();
  181. }
  182. }