/** * Overload the parse players because the data coming back is different * @see GameQ_Protocols_Quake3::parsePlayers() */ protected function parsePlayers(GameQ_Result &$result, $players_info) { // Explode the arrays out $players = explode("\n", $players_info); // Remove the last array item as it is junk array_pop($players); // Add total number of players $result->add('num_players', count($players)); // Loop the players foreach ($players as $player_info) { $buf = new GameQ_Buffer($player_info); // Add player info $result->addPlayer('frags', $buf->readString(" ")); $result->addPlayer('ping', $buf->readString(" ")); // Skip first " $buf->skip(1); // Add player name $result->addPlayer('name', trim($buf->readString('"'))); // Skip space $buf->skip(1); // Add team $result->addPlayer('team', $buf->read()); } // Free some memory unset($buf, $players, $player_info); }
protected function process_all() { if (!$this->hasValidResponse(self::PACKET_ALL)) { return array(); } $data = $this->packets_response[self::PACKET_ALL][0]; $buf = new GameQ_Buffer($data); $result = new GameQ_Result(); // Grab the header $header = $buf->read(4); // Header does not match if ($header !== 'EYE1') { throw new GameQException("Exepcted header to be 'EYE1' but got '{$header}' instead."); } // Variables $result->add('gamename', $buf->readPascalString(1, true)); $result->add('port', $buf->readPascalString(1, true)); $result->add('servername', $buf->readPascalString(1, true)); $result->add('gametype', $buf->readPascalString(1, true)); $result->add('map', $buf->readPascalString(1, true)); $result->add('version', $buf->readPascalString(1, true)); $result->add('password', $buf->readPascalString(1, true)); $result->add('num_players', $buf->readPascalString(1, true)); $result->add('max_players', $buf->readPascalString(1, true)); // Key / value pairs while ($buf->getLength()) { // If we have an empty key, we've reached the end $key = $buf->readPascalString(1, true); if (empty($key)) { break; } // Otherwise, add the pair $result->add($key, $buf->readPascalString(1, true)); } // Players while ($buf->getLength()) { // Get the flags $flags = $buf->readInt8(); // Get data according to the flags if ($flags & 1) { $result->addPlayer('name', $buf->readPascalString(1, true)); } if ($flags & 2) { $result->addPlayer('team', $buf->readPascalString(1, true)); } if ($flags & 4) { $result->addPlayer('skin', $buf->readPascalString(1, true)); } if ($flags & 8) { $result->addPlayer('score', $buf->readPascalString(1, true)); } if ($flags & 16) { $result->addPlayer('ping', $buf->readPascalString(1, true)); } if ($flags & 32) { $result->addPlayer('time', $buf->readPascalString(1, true)); } } return $result->fetch(); }
/** * Pre-process the server details data that was returned. * * @param array $packets */ protected function preProcess($packets) { // Make buffer so we can check this out $buf = new GameQ_Buffer(implode('', $packets)); // Grab the header $header = $buf->read(4); // Now lets verify the header if ($header != "M2MP") { throw new GameQ_ProtocolsException('Unable to match M2MP response header. Header: ' . $header); return FALSE; } // Return the data with the header stripped, ready to go. return $buf->getBuffer(); }
/** * Handles processing the status data into a usable format * * @throws GameQ_ProtocolsException */ protected function process_status() { // Make sure we have a valid response if (!$this->hasValidResponse(self::PACKET_STATUS)) { return array(); } // Set the result to a new result instance $result = new GameQ_Result(); // Let's preprocess the rules $data = $this->preProcess_status($this->packets_response[self::PACKET_STATUS]); // Create a new buffer $buf = new GameQ_Buffer($data); // Skip the header $buf->skip(6); $result->add('mod', $buf->readPascalString()); $result->add('gametype', $buf->readPascalString()); $result->add('map', $buf->readPascalString()); // Grab the flag $flag = $buf->read(); $bit = 1; foreach (array('dedicated', 'password', 'linux', 'tournament', 'no_alias') as $var) { $value = $flag & $bit ? 1 : 0; $result->add($var, $value); $bit *= 2; } $result->add('num_players', $buf->readInt8()); $result->add('max_players', $buf->readInt8()); $result->add('num_bots', $buf->readInt8()); $result->add('cpu', $buf->readInt16()); $result->add('info', $buf->readPascalString()); $buf->skip(2); // Do teams $num_teams = $buf->read(); $result->add('num_teams', $num_teams); $buf->skip(); for ($i = 0; $i < $num_teams; $i++) { $result->addTeam('name', $buf->readString("\t")); $result->addTeam('score', $buf->readString("\n")); } // Do players // @todo: No code here to do players, no docs either, need example server with players unset($buf, $data); return $result->fetch(); }
/** * Decode words from the response * * @param GameQ_Buffer $buf */ protected function decodeWords(GameQ_Buffer &$buf) { $result = array(); $num_words = $buf->readInt32(); for ($i = 0; $i < $num_words; $i++) { $len = $buf->readInt32(); $result[] = $buf->read($len); $buf->read(1); /* 0x00 string ending */ } return $result; }
/** * Read an Unreal Engine 2 string * * Adapted from original GameQ code * * @param GameQ_Buffer $buf * @return string <string, mixed> */ private function _readUnrealString(GameQ_Buffer &$buf) { // Normal pascal string if (ord($buf->lookAhead(1)) < 129) { return $buf->readPascalString(1); } // UnrealEngine2 color-coded string $length = ($buf->readInt8() - 128) * 2 - 3; $encstr = $buf->read($length); $buf->skip(3); // Remove color-code tags $encstr = preg_replace('~\\x5e\\0\\x23\\0..~s', '', $encstr); // Remove every second character // The string is UCS-2, this approximates converting to latin-1 $str = ''; for ($i = 0, $ii = strlen($encstr); $i < $ii; $i += 2) { $str .= $encstr[$i]; } return $str; }
/** * Process the server status * * @throws GameQ_ProtocolsException */ protected function process_status() { // Make sure we have a valid response if (!$this->hasValidResponse(self::PACKET_STATUS)) { return array(); } // Set the result to a new result instance $result = new GameQ_Result(); // Always dedicated $result->add('dedicated', TRUE); // Preprocess and make buffer $buf = new GameQ_Buffer($this->preProcess($this->packets_response[self::PACKET_STATUS])); // Pull out the server information $result->add('password', (bool) $buf->readInt8()); $result->add('num_players', $buf->readInt16()); $result->add('max_players', $buf->readInt16()); // These are read differently for these last 3 $result->add('servername', $buf->read($buf->readInt32())); $result->add('gametype', $buf->read($buf->readInt32())); $result->add('map', $buf->read($buf->readInt32())); // Free some memory unset($buf); // Return the result return $result->fetch(); }
/** * Process the status result. This result is different from the parent * * @see GameQ_Protocols_Cube2::process_status() */ protected function process_status() { // Make sure we have a valid response if (!$this->hasValidResponse(self::PACKET_STATUS)) { return array(); } // Set the result to a new result instance $result = new GameQ_Result(); // Let's preprocess the rules $data = $this->preProcess_status($this->packets_response[self::PACKET_STATUS]); // Create a new buffer $buf = new GameQ_Buffer($data); // Check the header, should be the same response as the packet we sent if ($buf->read(6) != $this->packets[self::PACKET_STATUS]) { throw new GameQ_ProtocolsException("Data for " . __METHOD__ . " does not have the proper header type (should be {$this->packets[self::PACKET_STATUS]})."); return array(); } /** * Reference chart for ints by position * * 0 - Num players * 1 - Number of items to follow (i.e. 8), not used yet * 2 - Version * 3 - gamemode (dm, ctf, etc...) * 4 - mutators (sum of power of 2) * 5 - Time remaining * 6 - max players * 7 - Mastermode (open, password, etc) * 8 - variable count * 9 - modification count */ $result->add('num_players', $this->readInt($buf)); $items = $this->readInt($buf); // We dump this as we dont use it for now $result->add('version', $this->readInt($buf)); $result->add('gamemode', $this->gamemodes[$this->readInt($buf)]); // This is a sum of power's of 2 (2^1, 1 << 1) $mutators_number = $this->readInt($buf); $mutators = array(); foreach ($this->mutators as $mutator => $flag) { if ($flag & $mutators_number) { $mutators[] = $mutator; } } $result->add('mutators', $mutators); $result->add('mutators_number', $mutators_number); $result->add('time_remaining', $this->readInt($buf)); $result->add('max_players', $this->readInt($buf)); $mastermode = $this->readInt($buf); $result->add('mastermode', $this->mastermodes[$mastermode]); $result->add('password', in_array($mastermode, array(4)) ? TRUE : FALSE); // @todo: No idea what these next 2 are used for $result->add('variableCount', $this->readInt($buf)); $result->add('modificationCount', $this->readInt($buf)); $result->add('map', $buf->readString()); $result->add('servername', $buf->readString()); // The rest from here is player information, we read until we run out of strings (\x00) while ($raw = $buf->readString()) { // Items seem to be seperated by \xc $items = explode("\f", $raw); // Indexes 0, 1 & 5 seem to be junk // Indexes 2, 3, 4 seem to have something of use, not sure about #3 $result->addPlayer('guid', (int) trim($items[2], "[]")); // Index 4 has the player name with some kind int added on to the front, icon or something? // Anyway remove it for now... if (preg_match('/(\\[[0-9]+\\])(.*)/i', $items[4], $name)) { $result->addPlayer('name', $name[2]); } } unset($buf, $data); return $result->fetch(); }