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\Buffer;
22: use GameQ\Exception\Protocol as Exception;
23: use GameQ\Helpers\Str;
24: use GameQ\Protocol;
25: use GameQ\Result;
26:
27: /**
28: * Mafia 2 Multiplayer Protocol Class
29: *
30: * Loosely based on SAMP protocol
31: *
32: * Query port = server port + 1
33: *
34: * Handles processing Mafia 2 Multiplayer servers
35: *
36: * @package GameQ\Protocols
37: * @author Wilson Jesus <>
38: */
39: class M2mp extends Protocol
40: {
41: /**
42: * Array of packets we want to look up.
43: * Each key should correspond to a defined method in this or a parent class
44: *
45: * @var array
46: */
47: protected $packets = [
48: self::PACKET_ALL => "M2MP",
49: ];
50:
51: /**
52: * Use the response flag to figure out what method to run
53: *
54: * @var array
55: */
56: protected $responses = [
57: "M2MP" => 'processStatus',
58: ];
59:
60: /**
61: * The query protocol used to make the call
62: *
63: * @var string
64: */
65: protected $protocol = 'm2mp';
66:
67: /**
68: * String name of this protocol class
69: *
70: * @var string
71: */
72: protected $name = 'm2mp';
73:
74: /**
75: * Longer string name of this protocol class
76: *
77: * @var string
78: */
79: protected $name_long = "Mafia 2 Multiplayer";
80:
81: /**
82: * The difference between the client port and query port
83: *
84: * @var int
85: */
86: protected $port_diff = 1;
87:
88: /**
89: * Normalize settings for this protocol
90: *
91: * @var array
92: */
93: protected $normalize = [
94: // General
95: 'general' => [
96: // target => source
97: 'hostname' => 'servername',
98: 'gametype' => 'gamemode',
99: 'maxplayers' => 'max_players',
100: 'numplayers' => 'num_players',
101: 'password' => 'password',
102: ],
103: // Individual
104: 'player' => [
105: 'name' => 'name',
106: ],
107: ];
108:
109: /**
110: * Handle response from the server
111: *
112: * @return mixed
113: * @throws Exception
114: * @throws \GameQ\Exception\Protocol
115: */
116: public function processResponse()
117: {
118: // Make a buffer
119: $buffer = new Buffer(implode('', $this->packets_response));
120:
121: // Grab the header
122: $header = $buffer->read(4);
123:
124: // Header
125: // Figure out which packet response this is
126: if ($header != "M2MP") {
127: throw new Exception(__METHOD__ . " response type '" . bin2hex($header) . "' is not valid");
128: }
129:
130: return call_user_func_array([$this, $this->responses[$header]], [$buffer]);
131: }
132:
133: /**
134: * Process the status response
135: *
136: * @param Buffer $buffer
137: *
138: * @return array
139: * @throws \GameQ\Exception\Protocol
140: */
141: protected function processStatus(Buffer $buffer)
142: {
143: // We need to split the data and offload
144: $results = $this->processServerInfo($buffer);
145:
146: $results = array_merge_recursive(
147: $results,
148: $this->processPlayers($buffer)
149: );
150:
151: unset($buffer);
152:
153: // Return results
154: return $results;
155: }
156:
157: /**
158: * Handle processing the server information
159: *
160: * @param Buffer $buffer
161: *
162: * @return array
163: * @throws \GameQ\Exception\Protocol
164: */
165: protected function processServerInfo(Buffer $buffer)
166: {
167: // Set the result to a new result instance
168: $result = new Result();
169:
170: // Always dedicated
171: $result->add('dedicated', 1);
172:
173: // Pull out the server information
174: // Note the length information is incorrect, we correct using offset options in pascal method
175: $result->add('servername', $buffer->readPascalString(1, true));
176: $result->add('num_players', $buffer->readPascalString(1, true));
177: $result->add('max_players', $buffer->readPascalString(1, true));
178: $result->add('gamemode', $buffer->readPascalString(1, true));
179: $result->add('password', (bool) $buffer->readInt8());
180:
181: unset($buffer);
182:
183: return $result->fetch();
184: }
185:
186: /**
187: * Handle processing of player data
188: *
189: * @param Buffer $buffer
190: *
191: * @return array
192: * @throws \GameQ\Exception\Protocol
193: */
194: protected function processPlayers(Buffer $buffer)
195: {
196: // Set the result to a new result instance
197: $result = new Result();
198:
199: // Parse players
200: // Read the player info, it's in the same query response for some odd reason.
201: while ($buffer->getLength()) {
202: // Check to see if we ran out of info, length bug from response
203: if ($buffer->getLength() <= 1) {
204: break;
205: }
206:
207: // Only player name information is available
208: // Add player name, encoded
209: $result->addPlayer('name', Str::isoToUtf8(trim($buffer->readPascalString(1, true))));
210: }
211:
212: // Clear
213: unset($buffer);
214:
215: return $result->fetch();
216: }
217: }
218: