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:
19: namespace GameQ\Protocols;
20:
21: use GameQ\Exception\Protocol as Exception;
22: use GameQ\Protocol;
23: use GameQ\Buffer;
24: use GameQ\Result;
25:
26: /**
27: * GameSpy2 Protocol class
28: *
29: * Given the ability for non utf-8 characters to be used as hostnames, player names, etc... this
30: * version returns all strings utf-8 encoded (utf8_encode). To access the proper version of a
31: * string response you must use utf8_decode() on the specific response.
32: *
33: * @author Austin Bischoff <austin@codebeard.com>
34: */
35: class Gamespy2 extends Protocol
36: {
37:
38: /**
39: * Define the state of this class
40: *
41: * @type int
42: */
43: protected $state = self::STATE_BETA;
44:
45: /**
46: * Array of packets we want to look up.
47: * Each key should correspond to a defined method in this or a parent class
48: *
49: * @type array
50: */
51: protected $packets = [
52: self::PACKET_DETAILS => "\xFE\xFD\x00\x43\x4F\x52\x59\xFF\x00\x00",
53: self::PACKET_PLAYERS => "\xFE\xFD\x00\x43\x4F\x52\x58\x00\xFF\xFF",
54: ];
55:
56: /**
57: * Use the response flag to figure out what method to run
58: *
59: * @type array
60: */
61: protected $responses = [
62: "\x00\x43\x4F\x52\x59" => "processDetails",
63: "\x00\x43\x4F\x52\x58" => "processPlayers",
64: ];
65:
66: /**
67: * The query protocol used to make the call
68: *
69: * @type string
70: */
71: protected $protocol = 'gamespy2';
72:
73: /**
74: * String name of this protocol class
75: *
76: * @type string
77: */
78: protected $name = 'gamespy2';
79:
80: /**
81: * Longer string name of this protocol class
82: *
83: * @type string
84: */
85: protected $name_long = "GameSpy2 Server";
86:
87: /**
88: * The client join link
89: *
90: * @type string
91: */
92: protected $join_link = null;
93:
94: /**
95: * Normalize settings for this protocol
96: *
97: * @type array
98: */
99: protected $normalize = [
100: // General
101: 'general' => [
102: // target => source
103: 'dedicated' => 'dedicated',
104: 'gametype' => 'gametype',
105: 'hostname' => 'hostname',
106: 'mapname' => 'mapname',
107: 'maxplayers' => 'maxplayers',
108: 'mod' => 'mod',
109: 'numplayers' => 'numplayers',
110: 'password' => 'password',
111: ],
112: ];
113:
114:
115: /**
116: * Process the response
117: *
118: * @return array
119: * @throws Exception
120: */
121: public function processResponse()
122: {
123:
124: // Will hold the packets after sorting
125: $packets = [];
126:
127: // We need to pre-sort these for split packets so we can do extra work where needed
128: foreach ($this->packets_response as $response) {
129: $buffer = new Buffer($response);
130:
131: // Pull out the header
132: $header = $buffer->read(5);
133:
134: // Add the packet to the proper section, we will combine later
135: $packets[$header][] = $buffer->getBuffer();
136: }
137:
138: unset($buffer);
139:
140: $results = [];
141:
142: // Now let's iterate and process
143: foreach ($packets as $header => $packetGroup) {
144: // Figure out which packet response this is
145: if (!array_key_exists($header, $this->responses)) {
146: throw new Exception(__METHOD__ . " response type '" . bin2hex($header) . "' is not valid");
147: }
148:
149: // Now we need to call the proper method
150: $results = array_merge(
151: $results,
152: call_user_func_array([$this, $this->responses[$header]], [new Buffer(implode($packetGroup))])
153: );
154: }
155:
156: unset($packets);
157:
158: return $results;
159: }
160:
161: /*
162: * Internal methods
163: */
164:
165: /**
166: * Handles processing the details data into a usable format
167: *
168: * @param \GameQ\Buffer $buffer
169: *
170: * @return array
171: * @throws Exception
172: */
173: protected function processDetails(Buffer $buffer)
174: {
175:
176: // Set the result to a new result instance
177: $result = new Result();
178:
179: // We go until we hit an empty key
180: while ($buffer->getLength()) {
181: $key = $buffer->readString();
182: if (strlen($key) == 0) {
183: break;
184: }
185: $result->add($key, utf8_encode($buffer->readString()));
186: }
187:
188: unset($buffer);
189:
190: return $result->fetch();
191: }
192:
193: /**
194: * Handles processing the players data into a usable format
195: *
196: * @param \GameQ\Buffer $buffer
197: *
198: * @return array
199: * @throws Exception
200: */
201: protected function processPlayers(Buffer $buffer)
202: {
203:
204: // Set the result to a new result instance
205: $result = new Result();
206:
207: // Skip the header
208: $buffer->skip(1);
209:
210: // Players are first
211: $this->parsePlayerTeam('players', $buffer, $result);
212:
213: // Teams are next
214: $this->parsePlayerTeam('teams', $buffer, $result);
215:
216: unset($buffer);
217:
218: return $result->fetch();
219: }
220:
221: /**
222: * Parse the player/team info returned from the player call
223: *
224: * @param string $dataType
225: * @param \GameQ\Buffer $buffer
226: * @param \GameQ\Result $result
227: *
228: * @throws Exception
229: */
230: protected function parsePlayerTeam($dataType, Buffer &$buffer, Result &$result)
231: {
232:
233: // Do count
234: $result->add('num_' . $dataType, $buffer->readInt8());
235:
236: // Variable names
237: $varNames = [];
238:
239: // Loop until we run out of length
240: while ($buffer->getLength()) {
241: $varNames[] = str_replace('_', '', $buffer->readString());
242:
243: if ($buffer->lookAhead() === "\x00") {
244: $buffer->skip();
245: break;
246: }
247: }
248:
249: // Check if there are any value entries
250: if ($buffer->lookAhead() == "\x00") {
251: $buffer->skip();
252:
253: return;
254: }
255:
256: // Get the values
257: while ($buffer->getLength() > 4) {
258: foreach ($varNames as $varName) {
259: $result->addSub($dataType, utf8_encode($varName), utf8_encode($buffer->readString()));
260: }
261: if ($buffer->lookAhead() === "\x00") {
262: $buffer->skip();
263: break;
264: }
265: }
266:
267: return;
268: }
269: }
270: