public function preprocess($packets) { $result = array(); // Get packet index, remove header foreach ($packets as $packet) { $p = new GameQ_Buffer($packet); $p->skip(14); $cur_packet = $p->readInt16(); $result[$cur_packet] = $p->getBuffer(); } // Sort packets, reset index ksort($result); $result = array_values($result); // Compare last var of current packet with first var of next packet // On a partial match, remove last var from current packet, // variable header from next packet for ($i = 0, $x = count($result); $i < $x - 1; $i++) { // First packet $fst = substr($result[$i], 0, -1); // Second packet $snd = $result[$i + 1]; // Get last variable from first packet $fstvar = substr($fst, strrpos($fst, "") + 1); // Get first variable from last packet $snd = substr($snd, strpos($snd, "") + 2); $sndvar = substr($snd, 0, strpos($snd, "")); // Check if fstvar is a substring of sndvar // If so, remove it from the first string if (strpos($sndvar, $fstvar) !== false) { $result[$i] = preg_replace("#(\\x00[^\\x00]+\\x00)\$#", "", $result[$i]); } } // Join packets return implode("", $result); }
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(); }
protected function process_status() { // Make sure we have a valid response if (!$this->hasValidResponse(self::PACKET_STATUS)) { return array(); } // Make buffer for data $buf = new GameQ_Buffer($this->preProcess_status($this->packets_response[self::PACKET_STATUS])); $buf->skip(8); /* skip header */ // Decode the words into an array so we can use this data $words = $this->decodeWords($buf); // Make sure we got OK if (!isset($words[0]) || $words[0] != 'OK') { throw new GameQ_ProtocolsException('Packet Response was not OK! Buffer:' . $buf->getBuffer()); } // Set the result to a new result instance $result = new GameQ_Result(); // Server is always dedicated $result->add('dedicated', TRUE); // No mods, as of yet $result->add('mod', FALSE); // These are the same no matter what mode the server is in $result->add('hostname', $words[1]); $result->add('numplayers', $words[2]); $result->add('maxplayers', $words[3]); $result->add('gametype', $words[4]); $result->add('map', $words[5]); $result->add('roundsplayed', $words[6]); $result->add('roundstotal', $words[7]); // Figure out the number of teams $num_teams = intval($words[8]); // Set the current index $index_current = 9; // Loop for the number of teams found, increment along the way for ($id = 1; $id <= $num_teams; $id++) { $result->addSub('teams', 'tickets', $words[$index_current]); $result->addSub('teams', 'id', $id); // Increment $index_current++; } // Get and set the rest of the data points. $result->add('targetscore', $words[$index_current]); $result->add('online', TRUE); // Forced TRUE, it seems $words[$index_current + 1] is always empty $result->add('ranked', $words[$index_current + 2] === 'true'); $result->add('punkbuster', $words[$index_current + 3] === 'true'); $result->add('password', $words[$index_current + 4] === 'true'); $result->add('uptime', $words[$index_current + 5]); $result->add('roundtime', $words[$index_current + 6]); // The next 3 are empty in MOHWF, kept incase they start to work some day $result->add('ip_port', $words[$index_current + 7]); $result->add('punkbuster_version', $words[$index_current + 8]); $result->add('join_queue', $words[$index_current + 9] === 'true'); $result->add('region', $words[$index_current + 10]); $result->add('pingsite', $words[$index_current + 11]); $result->add('country', $words[$index_current + 12]); unset($buf, $words); return $result->fetch(); }
protected function parsePlayerTeamInfoNew(GameQ_Buffer &$buf, GameQ_Result &$result) { // Read the buffer and replace the team_ sub-section under the players section becasue it is broke $buf_fixed = preg_replace('/team_(.*)score_/m', 'score_', $buf->getBuffer()); // Replace the buffer with the "fixed" buffer $buf = new GameQ_Buffer($buf_fixed); unset($buf_fixed); // Now we continue on with the parent return parent::parsePlayerTeamInfo($buf, $result); }
/** * 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 parsePlayers(GameQ_Buffer &$buf, GameQ_Result &$result) { while (($id = $buf->readInt8()) != 32) { $result->addPlayer('id', $id); $result->addPlayer('ping', $buf->readInt16()); $result->addPlayer('rate', $buf->readInt32()); $result->addPlayer('name', $buf->readString()); $result->addPlayer('clantag', $buf->readString()); } return true; }
/** * 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; }
/** * 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(); // Lets pre process and make sure these things are in the proper order by id $data = $this->preProcess($this->packets_response[self::PACKET_STATUS]); // Create a new buffer $buf = new GameQ_Buffer($data); // Lets peek and see if the data starts with a \ if ($buf->lookAhead(1) == '\\') { // Burn the first one $buf->skip(1); } // Explode the data $data = explode('\\', $buf->getBuffer()); // Remove the last 2 "items" as it should be final\ array_pop($data); array_pop($data); // Init some vars $num_players = 0; $num_teams = 0; // Now lets loop the array for ($x = 0; $x < count($data); $x += 2) { // Set some local vars $key = $data[$x]; $val = $data[$x + 1]; // Check for <variable>_<count> variable (i.e players) if (($suffix = strrpos($key, '_')) !== FALSE && is_numeric(substr($key, $suffix + 1))) { // See if this is a team designation if (substr($key, 0, $suffix) == 'teamname') { $result->addTeam('teamname', $val); $num_teams++; } else { if (substr($key, 0, $suffix) == 'playername') { $num_players++; } $result->addPlayer(substr($key, 0, $suffix), $val); } } else { $result->add($key, $val); } } // Add the player and team count $result->add('num_players', $num_players); $result->add('num_teams', $num_teams); unset($buf, $data, $key, $val, $suffix, $x); return $result->fetch(); }
/** * 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 details * * @throws GameQ_ProtocolsException */ protected function process_all() { // Make sure we have a valid response if (!$this->hasValidResponse(self::PACKET_ALL)) { 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_ALL])); // Pull out the server information // Note the length information is incorrect, we correct using offset options in pascal method $result->add('servername', $buf->readPascalString(1, TRUE)); $result->add('num_players', $buf->readPascalString(1, TRUE)); $result->add('max_players', $buf->readPascalString(1, TRUE)); $result->add('gamemode', $buf->readPascalString(1, TRUE)); $result->add('password', (bool) $buf->readInt8()); // Read the player info, it's in the same query response for some odd reason. while ($buf->getLength()) { // Check to see if we ran out of info if ($buf->getLength() <= 1) { break; } // Only player information is available $result->addPlayer('name', $buf->readPascalString(1, TRUE)); } unset($buf); return $result->fetch(); }
/** * Overloaded for Killing Floor servername issue, could be all unreal2 games though * * @see GameQ_Protocols_Unreal2::process_details() */ protected function process_details() { // Make sure we have a valid response if (!$this->hasValidResponse(self::PACKET_DETAILS)) { return array(); } // Set the result to a new result instance $result = new GameQ_Result(); // Let's preprocess the rules $data = $this->preProcess_details($this->packets_response[self::PACKET_DETAILS]); // Create a buffer $buf = new GameQ_Buffer($data); $result->add('steamappid', 1250); // ;) $result->add('serverid', $buf->readInt32()); // 0 $result->add('serverip', $buf->readPascalString(1)); // empty $result->add('gameport', $buf->readInt32()); $result->add('queryport', $buf->readInt32()); // 0 // We burn the first char since it is not always correct with the hostname $buf->skip(1); // Read as a regular string since the length is incorrect (what we skipped earlier) $result->add('servername', $buf->readString()); // The rest is read as normal $result->add('mapname', $buf->readPascalString(1)); $result->add('gametype', $buf->readPascalString(1)); $result->add('playercount', $buf->readInt32()); $result->add('maxplayers', $buf->readInt32()); $result->add('ping', $buf->readInt32()); // 0 // @todo: There is extra data after this point (~9 bytes), cant find any reference on what it is unset($buf); // Return the result return $result->fetch(); }
/** * Verify the header of the returned response packet * * @param GameQ_Buffer $buffer * @throws GameQ_ProtocolsException */ protected function verify_header(GameQ_Buffer &$buffer) { // Check length if ($buffer->getLength() < 6) { throw new GameQ_ProtocolsException(__METHOD__ . ": Length of buffer is not long enough"); return FALSE; } // Check to make sure the header is correct if (($type = trim($buffer->readString("\n"))) != '[TS]') { throw new GameQ_ProtocolsException(__METHOD__ . ": Header returned did not match. Returned type {$type}"); return FALSE; } // Verify the response and return return $this->verify_response(trim($buffer->readString("\n"))); }
/** * Process the players * * NOTE: There is a restriction on the SAMP server side that if there are too many players * the player return will be empty. Nothing can really be done about this unless you bug * the game developers to fix it. */ protected function process_players() { // Make sure we have a valid response if (!$this->hasValidResponse(self::PACKET_PLAYERS)) { return array(); } // Set the result to a new result instance $result = new GameQ_Result(); // Preprocess and make buffer $buf = new GameQ_Buffer($this->preProcess($this->packets_response[self::PACKET_PLAYERS])); // Number of players $result->add('num_players', $buf->readInt16()); // Run until we run out of buffer while ($buf->getLength()) { $result->addPlayer('id', $buf->readInt8()); $result->addPlayer('name', $buf->readPascalString()); $result->addPlayer('score', $buf->readInt32()); $result->addPlayer('ping', $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(); }
/** * 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(); }
protected function process_players() { // Make sure we have a valid response if (!$this->hasValidResponse(self::PACKET_PLAYERS)) { return array(); } // Set the result to a new result instance $result = new GameQ_Result(); // Make buffer for data $buf = new GameQ_Buffer($this->preProcess_players($this->packets_response[self::PACKET_PLAYERS])); $buf->skip(8); /* skip header */ $words = $this->decodeWords($buf); // Not too important if players are missing. if (!isset($words[0]) || $words[0] != 'OK') { return array(); } // Count the number of words and figure out the highest index. $words_total = count($words) - 1; // The number of player info points $num_tags = $words[1]; // Pull out the tags, they start at index=3, length of num_tags $tags = array_slice($words, 2, $num_tags); // Just incase this changed between calls. $result->add('numplayers', $words[$num_tags + 2]); // Loop until we run out of positions for ($pos = 3 + $num_tags; $pos <= $words_total; $pos += $num_tags) { // Pull out this player $player = array_slice($words, $pos, $num_tags); // Loop the tags and add the proper value for the tag. foreach ($tags as $tag_index => $tag) { $result->addPlayer($tag, $player[$tag_index]); } } // @todo: Add some team definition stuff unset($buf, $tags, $words, $player); return $result->fetch(); }
/** * Parse the buffer response into an array and return it * * @param GameQ_Buffer $buffer */ protected function parse_response(GameQ_Buffer &$buffer) { // The data is in the first block $data = explode('|', trim($buffer->readString("\n"))); // The response is the last block $this->verify_response(trim($buffer->readString("\n"))); $return = array(); foreach ($data as $part) { $variables = explode(' ', $part); $info = array(); foreach ($variables as $variable) { // Explode and make sure we always have 2 items in the array list($key, $value) = array_pad(explode('=', $variable, 2), 2, ''); $info[$key] = str_replace(array_keys($this->string_replace), array_values($this->string_replace), $value); } // Add this to the return $return[] = $info; } return $return; }