Quake3.php 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  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. * Quake3 Protocol Class
  9. *
  10. * Handles processing Quake 3 servers
  11. *
  12. * @package GameQ\Protocols
  13. */
  14. class Quake3 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\xFF\x67\x65\x74\x73\x74\x61\x74\x75\x73\x0A",
  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\xFFstatusResponse" => 'processStatus',
  32. ];
  33. /**
  34. * The query protocol used to make the call
  35. *
  36. * @type string
  37. */
  38. protected $protocol = 'quake3';
  39. /**
  40. * String name of this protocol class
  41. *
  42. * @type string
  43. */
  44. protected $name = 'quake3';
  45. /**
  46. * Longer string name of this protocol class
  47. *
  48. * @type string
  49. */
  50. protected $name_long = "Quake 3 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' => 'sv_hostname',
  68. 'mapname' => 'mapname',
  69. 'maxplayers' => 'sv_maxclients',
  70. 'mod' => 'g_gametype',
  71. 'numplayers' => 'clients',
  72. 'password' => ['g_needpass', 'pswrd'],
  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. protected function processStatus(Buffer $buffer)
  100. {
  101. // We need to split the data and offload
  102. $results = $this->processServerInfo(new Buffer($buffer->readString("\x0A")));
  103. $results = array_merge_recursive(
  104. $results,
  105. $this->processPlayers(new Buffer($buffer->getBuffer()))
  106. );
  107. unset($buffer);
  108. // Return results
  109. return $results;
  110. }
  111. /**
  112. * Handle processing the server information
  113. *
  114. * @param Buffer $buffer
  115. *
  116. * @return array
  117. */
  118. protected function processServerInfo(Buffer $buffer)
  119. {
  120. // Set the result to a new result instance
  121. $result = new Result();
  122. // Burn leading \ if one exists
  123. $buffer->readString('\\');
  124. // Key / value pairs
  125. while ($buffer->getLength()) {
  126. // Add result
  127. $result->add(
  128. trim($buffer->readString('\\')),
  129. utf8_encode(trim($buffer->readStringMulti(['\\', "\x0a"])))
  130. );
  131. }
  132. unset($buffer);
  133. return $result->fetch();
  134. }
  135. /**
  136. * Handle processing of player data
  137. *
  138. * @param Buffer $buffer
  139. *
  140. * @return array
  141. * @throws Exception
  142. */
  143. protected function processPlayers(Buffer $buffer)
  144. {
  145. // Some games do not have a number of current players
  146. $playerCount = 0;
  147. // Set the result to a new result instance
  148. $result = new Result();
  149. // Loop until we are out of data
  150. while ($buffer->getLength()) {
  151. // Add player info
  152. $result->addPlayer('frags', $buffer->readString("\x20"));
  153. $result->addPlayer('ping', $buffer->readString("\x20"));
  154. // Look ahead to see if we have a name or team
  155. $checkTeam = $buffer->lookAhead(1);
  156. // We have team info
  157. if ($checkTeam != '' and $checkTeam != '"') {
  158. $result->addPlayer('team', $buffer->readString("\x20"));
  159. }
  160. // Check to make sure we have player name
  161. $checkPlayerName = $buffer->read();
  162. // Bad response
  163. if ($checkPlayerName !== '"') {
  164. throw new Exception('Expected " but got ' . $checkPlayerName . ' for beginning of player name string!');
  165. }
  166. // Add player name, encoded
  167. $result->addPlayer('name', utf8_encode(trim($buffer->readString('"'))));
  168. // Burn ending delimiter
  169. $buffer->read();
  170. // Increment
  171. $playerCount++;
  172. }
  173. $result->add('clients', $playerCount);
  174. // Clear
  175. unset($buffer, $playerCount);
  176. return $result->fetch();
  177. }
  178. }