Advertisement
Guest User

Untitled

a guest
Jan 12th, 2017
95
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 19.12 KB | None | 0 0
  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. namespace GameQ;
  19.  
  20. use GameQ\Exception\Protocol as ProtocolException;
  21. use GameQ\Exception\Query as QueryException;
  22.  
  23. /**
  24.  * Base GameQ Class
  25.  *
  26.  * This class should be the only one that is included when you use GameQ to query
  27.  * any games servers.
  28.  *
  29.  * Requirements: See wiki or README for more information on the requirements
  30.  *  - PHP 5.4.14+
  31.  *    * Bzip2 - http://www.php.net/manual/en/book.bzip2.php
  32.  *
  33.  * @author Austin Bischoff <austin@codebeard.com>
  34.  *
  35.  * @property bool   $debug
  36.  * @property string $capture_packets_file
  37.  * @property int    $stream_timeout
  38.  * @property int    $timeout
  39.  * @property int    $write_wait
  40.  */
  41. class GameQ
  42. {
  43.     /*
  44.      * Constants
  45.      */
  46.  
  47.     /* Static Section */
  48.  
  49.     /**
  50.      * Holds the instance of itself
  51.      *
  52.      * @type self
  53.      */
  54.     protected static $instance = null;
  55.  
  56.     /**
  57.      * Create a new instance of this class
  58.      *
  59.      * @return \GameQ\GameQ
  60.      */
  61.     public static function factory()
  62.     {
  63.  
  64.         // Create a new instance
  65.         self::$instance = new self();
  66.  
  67.         // Return this new instance
  68.         return self::$instance;
  69.     }
  70.  
  71.     /* Dynamic Section */
  72.  
  73.     /**
  74.      * Default options
  75.      *
  76.      * @type array
  77.      */
  78.     protected $options = [
  79.         'debug'                => false,
  80.         'timeout'              => 3, // Seconds
  81.         'filters'              => [
  82.             // Default normalize
  83.             'normalize_d751713988987e9331980363e24189ce' => [
  84.                 'filter'  => 'normalize',
  85.                 'options' => [],
  86.             ],
  87.         ],
  88.         // Advanced settings
  89.         'stream_timeout'       => 200000, // See http://www.php.net/manual/en/function.stream-select.php for more info
  90.         'write_wait'           => 500,
  91.         // How long (in micro-seconds) to pause between writing to server sockets, helps cpu usage
  92.  
  93.         // Used for generating protocol test data
  94.         'capture_packets_file' => null,
  95.     ];
  96.  
  97.     /**
  98.      * Array of servers being queried
  99.      *
  100.      * @type array
  101.      */
  102.     protected $servers = [];
  103.  
  104.     /**
  105.      * The query library to use.  Default is Native
  106.      *
  107.      * @type string
  108.      */
  109.     protected $queryLibrary = 'GameQ\\Query\\Native';
  110.  
  111.     /**
  112.      * Holds the instance of the queryLibrary
  113.      *
  114.      * @type \GameQ\Query\Core|null
  115.      */
  116.     protected $query = null;
  117.  
  118.     /**
  119.      * GameQ constructor.
  120.      *
  121.      * Do some checks as needed so this will operate
  122.      */
  123.     public function __construct()
  124.     {
  125.         // Check for missing utf8_encode function
  126.         if (!function_exists('utf8_encode')) {
  127.             throw new \Exception("PHP's utf8_encode() function is required - "
  128.                 . "http://php.net/manual/en/function.utf8-encode.php.  Check your php installation.");
  129.         }
  130.     }
  131.  
  132.     /**
  133.      * Get an option's value
  134.      *
  135.      * @param mixed $option
  136.      *
  137.      * @return mixed|null
  138.      */
  139.     public function __get($option)
  140.     {
  141.  
  142.         return isset($this->options[$option]) ? $this->options[$option] : null;
  143.     }
  144.  
  145.     /**
  146.      * Set an option's value
  147.      *
  148.      * @param mixed $option
  149.      * @param mixed $value
  150.      *
  151.      * @return bool
  152.      */
  153.     public function __set($option, $value)
  154.     {
  155.  
  156.         $this->options[$option] = $value;
  157.  
  158.         return true;
  159.     }
  160.  
  161.     /**
  162.      * Chainable call to __set, uses set as the actual setter
  163.      *
  164.      * @param mixed $var
  165.      * @param mixed $value
  166.      *
  167.      * @return $this
  168.      */
  169.     public function setOption($var, $value)
  170.     {
  171.  
  172.         // Use magic
  173.         $this->{$var} = $value;
  174.  
  175.         return $this; // Make chainable
  176.     }
  177.  
  178.     /**
  179.      * Add a single server
  180.      *
  181.      * @param array $server_info
  182.      *
  183.      * @return $this
  184.      */
  185.     public function addServer(array $server_info = [])
  186.     {
  187.  
  188.         // Add and validate the server
  189.         $this->servers[uniqid()] = new Server($server_info);
  190.  
  191.         return $this; // Make calls chainable
  192.     }
  193.  
  194.     /**
  195.      * Add multiple servers in a single call
  196.      *
  197.      * @param array $servers
  198.      *
  199.      * @return $this
  200.      */
  201.     public function addServers(array $servers = [])
  202.     {
  203.  
  204.         // Loop through all the servers and add them
  205.         foreach ($servers as $server_info) {
  206.             $this->addServer($server_info);
  207.         }
  208.  
  209.         return $this; // Make calls chainable
  210.     }
  211.  
  212.     /**
  213.      * Add a set of servers from a file or an array of files.
  214.      * Supported formats:
  215.      * JSON
  216.      *
  217.      * @param array $files
  218.      *
  219.      * @return $this
  220.      * @throws \Exception
  221.      */
  222.     public function addServersFromFiles($files = [])
  223.     {
  224.  
  225.         // Since we expect an array let us turn a string (i.e. single file) into an array
  226.         if (!is_array($files)) {
  227.             $files = [$files];
  228.         }
  229.  
  230.         // Iterate over the file(s) and add them
  231.         foreach ($files as $file) {
  232.             // Check to make sure the file exists and we can read it
  233.             if (!file_exists($file) || !is_readable($file)) {
  234.                 continue;
  235.             }
  236.  
  237.             // See if this file is JSON
  238.             if (($servers = json_decode(file_get_contents($file), true)) === null
  239.                 && json_last_error() !== JSON_ERROR_NONE
  240.             ) {
  241.                 // Type not supported
  242.                 continue;
  243.             }
  244.  
  245.             // Add this list of servers
  246.             $this->addServers($servers);
  247.         }
  248.  
  249.         return $this;
  250.     }
  251.  
  252.     /**
  253.      * Clear all of the defined servers
  254.      *
  255.      * @return $this
  256.      */
  257.     public function clearServers()
  258.     {
  259.  
  260.         // Reset all the servers
  261.         $this->servers = [];
  262.  
  263.         return $this; // Make Chainable
  264.     }
  265.  
  266.     /**
  267.      * Add a filter to the processing list
  268.      *
  269.      * @param string $filterName
  270.      * @param array  $options
  271.      *
  272.      * @return $this
  273.      */
  274.     public function addFilter($filterName, $options = [])
  275.     {
  276.         // Create the filter hash so we can run multiple versions of the same filter
  277.         $filterHash = sprintf('%s_%s', strtolower($filterName), md5(json_encode($options)));
  278.  
  279.         // Add the filter
  280.         $this->options['filters'][$filterHash] = [
  281.             'filter'  => strtolower($filterName),
  282.             'options' => $options,
  283.         ];
  284.  
  285.         unset($filterHash);
  286.  
  287.         return $this;
  288.     }
  289.  
  290.     /**
  291.      * Remove an added filter
  292.      *
  293.      * @param string $filterHash
  294.      *
  295.      * @return $this
  296.      */
  297.     public function removeFilter($filterHash)
  298.     {
  299.         // Make lower case
  300.         $filterHash = strtolower($filterHash);
  301.  
  302.         // Remove this filter if it has been defined
  303.         if (array_key_exists($filterHash, $this->options['filters'])) {
  304.             unset($this->options['filters'][$filterHash]);
  305.         }
  306.  
  307.         unset($filterHash);
  308.  
  309.         return $this;
  310.     }
  311.  
  312.     /**
  313.      * Return the list of applied filters
  314.      *
  315.      * @return array
  316.      */
  317.     public function listFilters()
  318.     {
  319.         return $this->options['filters'];
  320.     }
  321.  
  322.     /**
  323.      * Main method used to actually process all of the added servers and return the information
  324.      *
  325.      * @return array
  326.      * @throws \Exception
  327.      */
  328.     public function process()
  329.     {
  330.  
  331.         // Initialize the query library we are using
  332.         $class = new \ReflectionClass($this->queryLibrary);
  333.  
  334.         // Set the query pointer to the new instance of the library
  335.         $this->query = $class->newInstance();
  336.  
  337.         unset($class);
  338.  
  339.         // Define the return
  340.         $results = [];
  341.         // @todo: Add break up into loop to split large arrays into smaller chunks
  342.  
  343.         // Do server challenge(s) first, if any
  344.         $this->doChallenges();
  345.  
  346.         // Do packets for server(s) and get query responses
  347.         $this->doQueries();
  348.  
  349.         // Now we should have some information to process for each server
  350.         foreach ($this->servers as $server) {
  351.             /* @var $server \GameQ\Server */
  352.  
  353.             // Parse the responses for this server
  354.             $result = $this->doParseResponse($server);
  355.  
  356.             // Apply the filters
  357.             $result = array_merge($result, $this->doApplyFilters($result, $server));
  358.  
  359.             // Sort the keys so they are alphabetical and nicer to look at
  360.             ksort($result);
  361.  
  362.             // Add the result to the results array
  363.             $results[$server->id()] = $result;
  364.         }
  365.  
  366.         return $results;
  367.     }
  368.  
  369.    
  370.     public function getServers($gq_type = "")
  371.     {
  372.         $return = [];
  373.         global $results;
  374.         foreach($results as $result){
  375.             if($result['gq_type'] == $gq_type || $gq_type == ""){
  376.                 $return[$result['gq_address'].$result['gq_port_query']] = $result;
  377.             }
  378.         }
  379.         //sort podla hier (asi funguje :D)
  380.         usort($return, function($a, $b) {
  381.           return strcmp($a["gq_type"], $b["gq_type"]);
  382.         });
  383.        
  384.         return $return;
  385.     }
  386.    
  387.    
  388.     /**
  389.      * Do server challenges, where required
  390.      */
  391.     protected function doChallenges()
  392.     {
  393.  
  394.         // Initialize the sockets for reading
  395.         $sockets = [];
  396.  
  397.         // By default we don't have any challenges to process
  398.         $server_challenge = false;
  399.  
  400.         // Do challenge packets
  401.         foreach ($this->servers as $server_id => $server) {
  402.             /* @var $server \GameQ\Server */
  403.  
  404.             // This protocol has a challenge packet that needs to be sent
  405.             if ($server->protocol()->hasChallenge()) {
  406.                 // We have a challenge, set the flag
  407.                 $server_challenge = true;
  408.  
  409.                 // Let's make a clone of the query class
  410.                 $socket = clone $this->query;
  411.  
  412.                 // Set the information for this query socket
  413.                 $socket->set(
  414.                     $server->protocol()->transport(),
  415.                     $server->ip,
  416.                     $server->port_query,
  417.                     $this->timeout
  418.                 );
  419.  
  420.                 try {
  421.                     // Now write the challenge packet to the socket.
  422.                     $socket->write($server->protocol()->getPacket(Protocol::PACKET_CHALLENGE));
  423.  
  424.                     // Add the socket information so we can reference it easily
  425.                     $sockets[(int)$socket->get()] = [
  426.                         'server_id' => $server_id,
  427.                         'socket'    => $socket,
  428.                     ];
  429.                 } catch (QueryException $e) {
  430.                     // Check to see if we are in debug, if so bubble up the exception
  431.                     if ($this->debug) {
  432.                         throw new \Exception($e->getMessage(), $e->getCode(), $e);
  433.                     }
  434.                 }
  435.  
  436.                 unset($socket);
  437.  
  438.                 // Let's sleep shortly so we are not hammering out calls rapid fire style hogging cpu
  439.                 usleep($this->write_wait);
  440.             }
  441.         }
  442.  
  443.         // We have at least one server with a challenge, we need to listen for responses
  444.         if ($server_challenge) {
  445.             // Now we need to listen for and grab challenge response(s)
  446.             $responses = call_user_func_array(
  447.                 [$this->query, 'getResponses'],
  448.                 [$sockets, $this->timeout, $this->stream_timeout]
  449.             );
  450.  
  451.             // Iterate over the challenge responses
  452.             foreach ($responses as $socket_id => $response) {
  453.                 // Back out the server_id we need to update the challenge response for
  454.                 $server_id = $sockets[$socket_id]['server_id'];
  455.  
  456.                 // Make this into a buffer so it is easier to manipulate
  457.                 $challenge = new Buffer(implode('', $response));
  458.  
  459.                 // Grab the server instance
  460.                 /* @var $server \GameQ\Server */
  461.                 $server = $this->servers[$server_id];
  462.  
  463.                 // Apply the challenge
  464.                 $server->protocol()->challengeParseAndApply($challenge);
  465.  
  466.                 // Add this socket to be reused, has to be reused in GameSpy3 for example
  467.                 $server->socketAdd($sockets[$socket_id]['socket']);
  468.  
  469.                 // Clear
  470.                 unset($server);
  471.             }
  472.         }
  473.     }
  474.  
  475.     /**
  476.      * Run the actual queries and get the response(s)
  477.      */
  478.     protected function doQueries()
  479.     {
  480.  
  481.         // Initialize the array of sockets
  482.         $sockets = [];
  483.  
  484.         // Iterate over the server list
  485.         foreach ($this->servers as $server_id => $server) {
  486.             /* @var $server \GameQ\Server */
  487.  
  488.             // Invoke the beforeSend method
  489.             $server->protocol()->beforeSend($server);
  490.  
  491.             // Get all the non-challenge packets we need to send
  492.             $packets = $server->protocol()->getPacket('!' . Protocol::PACKET_CHALLENGE);
  493.  
  494.             if (count($packets) == 0) {
  495.                 // Skip nothing else to do for some reason.
  496.                 continue;
  497.             }
  498.  
  499.             // Try to use an existing socket
  500.             if (($socket = $server->socketGet()) === null) {
  501.                 // Let's make a clone of the query class
  502.                 $socket = clone $this->query;
  503.  
  504.                 // Set the information for this query socket
  505.                 $socket->set(
  506.                     $server->protocol()->transport(),
  507.                     $server->ip,
  508.                     $server->port_query,
  509.                     $this->timeout
  510.                 );
  511.             }
  512.  
  513.             try {
  514.                 // Iterate over all the packets we need to send
  515.                 foreach ($packets as $packet_data) {
  516.                     // Now write the packet to the socket.
  517.                     $socket->write($packet_data);
  518.  
  519.                     // Let's sleep shortly so we are not hammering out calls rapid fire style
  520.                     usleep($this->write_wait);
  521.                 }
  522.  
  523.                 unset($packets);
  524.  
  525.                 // Add the socket information so we can reference it easily
  526.                 $sockets[(int)$socket->get()] = [
  527.                     'server_id' => $server_id,
  528.                     'socket'    => $socket,
  529.                 ];
  530.             } catch (QueryException $e) {
  531.                 // Check to see if we are in debug, if so bubble up the exception
  532.                 if ($this->debug) {
  533.                     throw new \Exception($e->getMessage(), $e->getCode(), $e);
  534.                 }
  535.  
  536.                 break;
  537.             }
  538.  
  539.             // Clean up the sockets, if any left over
  540.             $server->socketCleanse();
  541.         }
  542.  
  543.         // Now we need to listen for and grab response(s)
  544.         $responses = call_user_func_array(
  545.             [$this->query, 'getResponses'],
  546.             [$sockets, $this->timeout, $this->stream_timeout]
  547.         );
  548.  
  549.         // Iterate over the responses
  550.         foreach ($responses as $socket_id => $response) {
  551.             // Back out the server_id
  552.             $server_id = $sockets[$socket_id]['server_id'];
  553.  
  554.             // Grab the server instance
  555.             /* @var $server \GameQ\Server */
  556.             $server = $this->servers[$server_id];
  557.  
  558.             // Save the response from this packet
  559.             $server->protocol()->packetResponse($response);
  560.  
  561.             unset($server);
  562.         }
  563.  
  564.         // Now we need to close all of the sockets
  565.         foreach ($sockets as $socketInfo) {
  566.             /* @var $socket \GameQ\Query\Core */
  567.             $socket = $socketInfo['socket'];
  568.  
  569.             // Close the socket
  570.             $socket->close();
  571.  
  572.             unset($socket);
  573.         }
  574.  
  575.         unset($sockets);
  576.     }
  577.  
  578.     /**
  579.      * Parse the response for a specific server
  580.      *
  581.      * @param \GameQ\Server $server
  582.      *
  583.      * @return array
  584.      * @throws \Exception
  585.      */
  586.     protected function doParseResponse(Server $server)
  587.     {
  588.  
  589.         try {
  590.             // @codeCoverageIgnoreStart
  591.             // We want to save this server's response to a file (useful for unit testing)
  592.             if (!is_null($this->capture_packets_file)) {
  593.                 file_put_contents(
  594.                     $this->capture_packets_file,
  595.                     implode(PHP_EOL . '||' . PHP_EOL, $server->protocol()->packetResponse())
  596.                 );
  597.             }
  598.             // @codeCoverageIgnoreEnd
  599.  
  600.             // Get the server response
  601.             $results = $server->protocol()->processResponse();
  602.  
  603.             // Check for online before we do anything else
  604.             $results['gq_online'] = (count($results) > 0);
  605.         } catch (ProtocolException $e) {
  606.             // Check to see if we are in debug, if so bubble up the exception
  607.             if ($this->debug) {
  608.                 throw new \Exception($e->getMessage(), $e->getCode(), $e);
  609.             }
  610.  
  611.             // We ignore this server
  612.             $results = [
  613.                 'gq_online' => false,
  614.             ];
  615.         }
  616.  
  617.         // Now add some default stuff
  618.         $results['gq_address'] = $server->ip();
  619.         $results['gq_port_client'] = $server->portClient();
  620.         $results['gq_port_query'] = $server->portQuery();
  621.         $results['gq_protocol'] = $server->protocol()->getProtocol();
  622.         $results['gq_type'] = (string)$server->protocol();
  623.         $results['gq_name'] = $server->protocol()->nameLong();
  624.         $results['gq_transport'] = $server->protocol()->transport();
  625.  
  626.         // Process the join link
  627.         if (!isset($results['gq_joinlink']) || empty($results['gq_joinlink'])) {
  628.             $results['gq_joinlink'] = $server->getJoinLink();
  629.         }
  630.  
  631.         return $results;
  632.     }
  633.  
  634.     /**
  635.      * Apply any filters to the results
  636.      *
  637.      * @param array         $results
  638.      * @param \GameQ\Server $server
  639.      *
  640.      * @return array
  641.      */
  642.     protected function doApplyFilters(array $results, Server $server)
  643.     {
  644.  
  645.         // Loop over the filters
  646.         foreach ($this->options['filters'] as $filterOptions) {
  647.             // Try to do this filter
  648.             try {
  649.                 // Make a new reflection class
  650.                 $class = new \ReflectionClass(sprintf('GameQ\\Filters\\%s', ucfirst($filterOptions['filter'])));
  651.  
  652.                 // Create a new instance of the filter class specified
  653.                 $filter = $class->newInstanceArgs([$filterOptions['options']]);
  654.  
  655.                 // Apply the filter to the data
  656.                 $results = $filter->apply($results, $server);
  657.             } catch (\ReflectionException $e) {
  658.                 // Invalid, skip it
  659.                 continue;
  660.             }
  661.         }
  662.  
  663.         return $results;
  664.     }
  665. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement