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