Cs2d.php 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  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\Protocol;
  20. use GameQ\Buffer;
  21. use GameQ\Result;
  22. use GameQ\Exception\Protocol as Exception;
  23. /**
  24. * Counter-Strike 2d Protocol Class
  25. *
  26. * Note:
  27. * Unable to make player information calls work as the protocol does not like parallel requests
  28. *
  29. * @author Austin Bischoff <[email protected]>
  30. */
  31. class Cs2d extends Protocol
  32. {
  33. /**
  34. * Array of packets we want to query.
  35. *
  36. * @type array
  37. */
  38. protected $packets = [
  39. self::PACKET_STATUS => "\x01\x00\xFB\x01",
  40. //self::PACKET_STATUS => "\x01\x00\x03\x10\x21\xFB\x01\x75\x00",
  41. self::PACKET_PLAYERS => "\x01\x00\xFB\x05",
  42. ];
  43. /**
  44. * Use the response flag to figure out what method to run
  45. *
  46. * @type array
  47. */
  48. protected $responses = [
  49. "\x01\x00\xFB\x01" => "processDetails",
  50. "\x01\x00\xFB\x05" => "processPlayers",
  51. ];
  52. /**
  53. * The query protocol used to make the call
  54. *
  55. * @type string
  56. */
  57. protected $protocol = 'cs2d';
  58. /**
  59. * String name of this protocol class
  60. *
  61. * @type string
  62. */
  63. protected $name = 'cs2d';
  64. /**
  65. * Longer string name of this protocol class
  66. *
  67. * @type string
  68. */
  69. protected $name_long = "Counter-Strike 2d";
  70. /**
  71. * The client join link
  72. *
  73. * @type string
  74. */
  75. protected $join_link = "cs2d://%s:%d/";
  76. /**
  77. * Normalize settings for this protocol
  78. *
  79. * @type array
  80. */
  81. protected $normalize = [
  82. // General
  83. 'general' => [
  84. // target => source
  85. 'dedicated' => 'dedicated',
  86. 'gametype' => 'game_mode',
  87. 'hostname' => 'hostname',
  88. 'mapname' => 'mapname',
  89. 'maxplayers' => 'max_players',
  90. 'mod' => 'game_dir',
  91. 'numplayers' => 'num_players',
  92. 'password' => 'password',
  93. ],
  94. // Individual
  95. 'player' => [
  96. 'name' => 'name',
  97. 'deaths' => 'deaths',
  98. 'score' => 'score',
  99. ],
  100. ];
  101. /**
  102. * Process the response for the Tibia server
  103. *
  104. * @return array
  105. * @throws \GameQ\Exception\Protocol
  106. */
  107. public function processResponse()
  108. {
  109. // We have a merged packet, try to split it back up
  110. if (count($this->packets_response) == 1) {
  111. // Temp buffer to make string manipulation easier
  112. $buffer = new Buffer($this->packets_response[0]);
  113. // Grab the header and set the packet we need to split with
  114. $packet = (($buffer->lookAhead(4) === $this->packets[self::PACKET_PLAYERS]) ?
  115. self::PACKET_STATUS : self::PACKET_PLAYERS);
  116. // Explode the merged packet as the response
  117. $responses = explode(substr($this->packets[$packet], 2), $buffer->getData());
  118. // Try to rebuild the second packet to the same as if it was sent as two separate responses
  119. $responses[1] = $this->packets[$packet] . ((count($responses) === 2) ? $responses[1] : "");
  120. unset($buffer);
  121. } else {
  122. $responses = $this->packets_response;
  123. }
  124. // Will hold the packets after sorting
  125. $packets = [];
  126. // We need to pre-sort these for split packets so we can do extra work where needed
  127. foreach ($responses as $response) {
  128. $buffer = new Buffer($response);
  129. // Pull out the header
  130. $header = $buffer->read(4);
  131. // Add the packet to the proper section, we will combine later
  132. $packets[$header][] = $buffer->getBuffer();
  133. }
  134. unset($buffer);
  135. $results = [];
  136. // Now let's iterate and process
  137. foreach ($packets as $header => $packetGroup) {
  138. // Figure out which packet response this is
  139. if (!array_key_exists($header, $this->responses)) {
  140. throw new Exception(__METHOD__ . " response type '" . bin2hex($header) . "' is not valid");
  141. }
  142. // Now we need to call the proper method
  143. $results = array_merge(
  144. $results,
  145. call_user_func_array([$this, $this->responses[$header]], [new Buffer(implode($packetGroup))])
  146. );
  147. }
  148. unset($packets);
  149. return $results;
  150. }
  151. /**
  152. * Handles processing the details data into a usable format
  153. *
  154. * @param Buffer $buffer
  155. *
  156. * @return array
  157. * @throws Exception
  158. */
  159. protected function processDetails(Buffer $buffer)
  160. {
  161. // Set the result to a new result instance
  162. $result = new Result();
  163. // First int is the server flags
  164. $serverFlags = $buffer->readInt8();
  165. // Read server flags
  166. $result->add('password', (int)$this->readFlag($serverFlags, 0));
  167. $result->add('registered_only', (int)$this->readFlag($serverFlags, 1));
  168. $result->add('fog_of_war', (int)$this->readFlag($serverFlags, 2));
  169. $result->add('friendly_fire', (int)$this->readFlag($serverFlags, 3));
  170. $result->add('bots_enabled', (int)$this->readFlag($serverFlags, 5));
  171. $result->add('lua_scripts', (int)$this->readFlag($serverFlags, 6));
  172. // Read the rest of the buffer data
  173. $result->add('servername', utf8_encode($buffer->readPascalString(0)));
  174. $result->add('mapname', utf8_encode($buffer->readPascalString(0)));
  175. $result->add('num_players', $buffer->readInt8());
  176. $result->add('max_players', $buffer->readInt8());
  177. $result->add('game_mode', $buffer->readInt8());
  178. $result->add('num_bots', (($this->readFlag($serverFlags, 5)) ? $buffer->readInt8() : 0));
  179. $result->add('dedicated', 1);
  180. unset($buffer);
  181. return $result->fetch();
  182. }
  183. /**
  184. * Handles processing the player data into a usable format
  185. *
  186. * @param Buffer $buffer
  187. *
  188. * @return array
  189. * @throws Exception
  190. */
  191. protected function processPlayers(Buffer $buffer)
  192. {
  193. // Set the result to a new result instance
  194. $result = new Result();
  195. // First entry is the number of players in this list. Don't care
  196. $buffer->read();
  197. // Parse players
  198. while ($buffer->getLength()) {
  199. // Player id
  200. if (($id = $buffer->readInt8()) !== 0) {
  201. // Add the results
  202. $result->addPlayer('id', $id);
  203. $result->addPlayer('name', utf8_encode($buffer->readPascalString(0)));
  204. $result->addPlayer('team', $buffer->readInt8());
  205. $result->addPlayer('score', $buffer->readInt32());
  206. $result->addPlayer('deaths', $buffer->readInt32());
  207. }
  208. }
  209. unset($buffer, $id);
  210. return $result->fetch();
  211. }
  212. /**
  213. * Read flags from stored value
  214. *
  215. * @param $flags
  216. * @param $offset
  217. *
  218. * @return bool
  219. */
  220. protected function readFlag($flags, $offset)
  221. {
  222. return !!($flags & (1 << $offset));
  223. }
  224. }