Advertisement
Guest User

GameQ.php

a guest
Oct 30th, 2016
109
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 17.62 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 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 General Public License for more details.
  14.  *
  15.  * You should have received a copy of the GNU General Public License
  16.  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
  17.  *
  18.  * $Id: GameQ.php,v 1.13 2009/03/05 13:17:38 tombuskens Exp $  
  19.  */
  20.  
  21.  
  22. define('GAMEQ_BASE', dirname(__FILE__) . '/GameQ/');
  23.  
  24. require_once GAMEQ_BASE . 'Buffer.php';
  25. require_once GAMEQ_BASE . 'Config.php';
  26. require_once GAMEQ_BASE . 'Communicate.php';
  27. require_once GAMEQ_BASE . 'Exceptions.php';
  28. require_once GAMEQ_BASE . 'Result.php';
  29.    
  30. /**
  31.  * Retrieve gameplay data from gameservers.
  32.  *
  33.  * @author    Tom Buskens    <t.buskens@deviation.nl>
  34.  * @version   $Revision: 1.13 $
  35.  */
  36. class GameQ
  37. {
  38.     private $prot    = array();     // Cached protocol objects
  39.     private $servers = array();     // Server data
  40.     private $filters = array();     // Filter objects
  41.     private $options = array();     // Options:
  42.                                     // - (bool) debug
  43.                                     // - (bool) raw
  44.                                     // - (int)  timeout
  45.                                     // - (int)  sockets
  46.  
  47.     private $cfg;                   // Configuration object
  48.     private $comm;                  // Communication object
  49.  
  50.  
  51.     /**
  52.      * Constructor
  53.      *
  54.      * Initializes options and classes.
  55.      */
  56.     public function __construct()
  57.     {
  58.         // Default options
  59.         $this->setOption('timeout',    200);
  60.         $this->setOption('raw',        false);
  61.         $this->setOption('debug',      false);
  62.         $this->setOption('sock_count', 64);
  63.         $this->setOption('sock_start', 0);
  64.  
  65.         // Initialize objects
  66.         $this->cfg  = new GameQ_Config();
  67.         $this->comm = new GameQ_Communicate();
  68.     }
  69.  
  70.     /**
  71.      * Add a single server to the query list.
  72.      *
  73.      * @param    string    $id        A string to identify the server by
  74.      * @param    array     $server    Server data (gametype, address, port)
  75.      * @return   boolean   True if the server was added successfully, false otherwise
  76.      */
  77.     public function addServer($id, $server)
  78.     {
  79.         // We need at least two arguments
  80.         if (!is_array($server) or count($server) < 2) {
  81.             trigger_error(
  82.                 'GameQ::addServer: need an array with at least two ' .
  83.                 'elements as second argument for server [' . $id . '].',
  84.                 E_USER_NOTICE
  85.             );
  86.             return false;
  87.         }
  88.  
  89.         // Get the arguments
  90.         $game = array_shift($server);
  91.         $addr = array_shift($server);
  92.         $port = array_shift($server);
  93.  
  94.         // See if we can resolve the address
  95.         $raddr = $this->comm->getIp($addr);
  96.         if ($raddr === false) {
  97.             trigger_error(
  98.                 'GameQ::addServer: could not resolve server ' .
  99.                 'address for server [' . $id . '].',
  100.                 E_USER_NOTICE
  101.             );
  102.             return false;
  103.  
  104.         }
  105.  
  106.         // Retrieve game data and add it to the server array
  107.         $this->servers[$id] = $this->cfg->getGame($game, $raddr, $port);
  108.         return true;
  109.     }
  110.  
  111.     /**
  112.      * Add multiple servers to the query list.
  113.      *
  114.      * @param   array    $servers    A list of servers
  115.      * @return  boolean  True if all servers were added, false otherwise
  116.      */
  117.     public function addServers($servers)
  118.     {
  119.         $result = true;
  120.         foreach ($servers as $id => $server) {
  121.             $result = $result && $this->addServer($id, $server);
  122.         }
  123.  
  124.         return $result;
  125.     }
  126.  
  127.     /**
  128.      * Return the value for a specific option.
  129.      *
  130.      * @param     string    $var    Option name
  131.      * @return    mixed     Option value, or null if the option does not exist
  132.      */
  133.     public function getOption($var)
  134.     {
  135.         return isset($this->options[$var]) ? $this->options[$var] : null;
  136.     }
  137.  
  138.     /**
  139.      * Set an option.
  140.      *
  141.      * @param    string    $var      Option name
  142.      * @param    mixed     $value    Option value
  143.      */
  144.     public function setOption($var, $value)
  145.     {
  146.         $this->options[$var] = $value;
  147.     }
  148.  
  149.     /**
  150.      * Set an output filter.
  151.      *
  152.      * @param    string    $name      Filter name
  153.      * @param    array     $params    Assiociative array containing parameters
  154.      * @return   boolean   True on success, false on failure
  155.      */
  156.     public function setFilter($name, $params = array())
  157.     {
  158.         // Load it
  159.         $file  = GAMEQ_BASE . 'Filter/' . $name . '.php';
  160.         $class = 'GameQ_Filter_' . $name;
  161.  
  162.         // Initialize the protocol object
  163.         if (!is_readable($file)) {
  164.             trigger_error('GameQ::setFilter: unable to read file [' . $file . '].',
  165.                     E_USER_NOTICE);
  166.             return false;
  167.         }
  168.         include_once($file);
  169.  
  170.         // Check if the class can be loaded
  171.         if (!class_exists($class)) {
  172.             trigger_error('GameQ::setFilter: unable to load filter [' . $name . '].',
  173.                     E_USER_NOTICE);
  174.             return false;
  175.         }
  176.  
  177.         // Pass any parameters
  178.         $this->filters[$name] = new $class($params);
  179.         return true;
  180.     }
  181.  
  182.     /**
  183.      * Remove an output filter.
  184.      *
  185.      * @param    string    $name    Filter name
  186.      */
  187.     public function removeFilter($name)
  188.     {
  189.         unset($this->filters[$name]);
  190.     }
  191.  
  192.     /**
  193.      * Request data from all servers in the query list.
  194.      *
  195.      * @return    mixed    Server data, processed according to options and
  196.      *                     any filters used.
  197.      */
  198.     public function requestData()
  199.     {
  200.         // Get options
  201.         $timeout    = $this->getOption('timeout');
  202.         $raw        = $this->getOption('raw');
  203.         $sock_start = $this->getOption('sock_start');
  204.         $sock_count = $this->getOption('sock_count');
  205.  
  206.         $data = array();
  207.  
  208.         // Get a list of packets
  209.         $packs = $this->getPackets($this->servers);
  210.  
  211.         // Allow each protocol to modify their packets
  212.         // (for example, ts2 needs a fixed port, and the target port
  213.         // must be given in the request)
  214.         $packs = $this->modifyPackets($packs);
  215.  
  216.         // Send only as many packets as we have sockets available
  217.         for ($i = 0;; $i += $sock_count) {
  218.  
  219.             // Get as much packets as we have sockets available
  220.             $packets = array_slice($packs, $i, $sock_count);
  221.             if (empty($packets)) break;
  222.            
  223.             // Send all challenge packets
  224.             $packets = $this->comm->query($packets, $timeout, 'challenge', $sock_start);
  225.  
  226.             // Modify any packets using the challenge response
  227.             $packets = $this->processChallengeResponses($packets);
  228.  
  229.             // Send the regular packets
  230.             $packets = $this->comm->query($packets, $timeout, 'data', $sock_start);
  231.  
  232.             // Add packets to the result data
  233.             $data = array_merge($data, $packets);
  234.         }
  235.  
  236.         // Process data, if desired
  237.         if ($raw) {
  238.             return $this->processRaw($data, $this->servers);
  239.         }
  240.         else {
  241.             $data = $this->processResponses($data, $this->servers);
  242.             return $this->filterResponses($data);
  243.         }
  244.     }
  245.  
  246.     /**
  247.      * Removes all servers
  248.      */
  249.     public function clearServers()
  250.     {
  251.         $this->servers = array();
  252.     }
  253.  
  254.     /**
  255.      * Apply all set filters to the data returned by gameservers.
  256.      *
  257.      * @param     array    $responses    The data returned by gameservers
  258.      * @return    array    The data, filtered
  259.      */
  260.     private function filterResponses($responses)
  261.     {
  262.         foreach ($responses as $key => &$response) {
  263.             foreach ($this->filters as $filter) {
  264.                 $response = $filter->filter($response, $this->servers[$key]);
  265.             }
  266.         }
  267.  
  268.         return $responses;
  269.     }
  270.  
  271.     /**
  272.      * Allow protocols to modify their packets, before any are sent
  273.      *
  274.      * @param     array    $packets    Packets and their config
  275.      * @return    array    The modified packets
  276.      */
  277.     private function modifyPackets($packets)
  278.     {
  279.         foreach ($packets as &$packet) {
  280.             $prot   = $this->getProtocol($packet['prot']);
  281.             $packet = $prot->modifyPacket($packet);
  282.         }
  283.  
  284.         return $packets;
  285.     }
  286.  
  287.     /**
  288.      * Load a protocol object.
  289.      *
  290.      * @param     string    $name     The protocol name
  291.      * @return    object    The protocol class
  292.      */
  293.     private function getProtocol($name)
  294.     {
  295.         // It's already loaded
  296.         if (array_key_exists($name, $this->prot)) return $this->prot[$name];
  297.  
  298.         // Load it
  299.         $file  = GAMEQ_BASE . 'Protocol/' . $name . '.php';
  300.         $class = 'GameQ_Protocol_' . $name;
  301.  
  302.         // Initialize the protocol object
  303.         if (!is_readable($file)) {
  304.             trigger_error('GameQ::getProtocol: unable to read file [' . $file . '].',
  305.                     E_USER_ERROR);
  306.         }
  307.         include_once($file);
  308.  
  309.         // Check if the class can be loaded
  310.         if (!class_exists($class)) {
  311.             trigger_error('GameQ::setFilter: unable to load protocol [' . $name . '].',
  312.                     E_USER_ERROR);
  313.         }
  314.  
  315.         $this->prot[$name] = new $class;
  316.  
  317.         return $this->prot[$name];
  318.     }
  319.  
  320.     /**
  321.      * Get all packets that have to be sent to the servers.
  322.      *
  323.      * @param     array    $servers    Gameservers
  324.      * @return    array    An array of packet data; a single server may have
  325.      *                     multiple packet entries here
  326.      */
  327.     private function getPackets($servers)
  328.     {
  329.         $result = array();
  330.  
  331.         // Get packets for each server
  332.         foreach ($servers as $id => $server) {
  333.  
  334.             $packets = $this->cfg->getPackets($server['pack']);
  335.             $chall   = false;
  336.  
  337.             // Filter out challenge packets
  338.             if (isset($packets['challenge'])) {
  339.                 $chall = $packets['challenge'];
  340.                 unset($packets['challenge']);
  341.             }
  342.  
  343.             // Create an entry for each packet
  344.             foreach ($packets as $packetname => $packet) {
  345.  
  346.                 $p = array();
  347.                 $p['sid']  = $id;
  348.                 $p['name'] = $packetname;
  349.                 $p['data'] = $packet;
  350.                 $p['addr'] = $server['addr'];
  351.                 $p['port'] = $server['port'];
  352.                 $p['prot'] = $server['prot'];
  353.                 $p['transport'] = $server['transport'];
  354.  
  355.                 // Challenge, add to end of packet array
  356.                 if ($chall !== false) {
  357.                     $p['challenge'] = $chall;
  358.                     array_push($result, $p);
  359.                 }
  360.                 // Normal, add to beginning
  361.                 else {
  362.                     array_unshift($result, $p);
  363.                 }
  364.             }
  365.         }
  366.  
  367.         return $result;
  368.     }
  369.  
  370.     /**
  371.      * Recursively merge two arrays.
  372.      *
  373.      * @param    array    $arr1    An array
  374.      * @param    array    $arr2    Another array
  375.      */
  376.     private function merge($arr1, $arr2)
  377.     {
  378.         if (!is_array($arr2)) return $arr1;
  379.  
  380.         foreach ($arr2 as $key => $val2) {
  381.  
  382.             // No overlap, simply add
  383.             if (!isset($arr1[$key])) {
  384.                $arr1[$key] = $val2;
  385.                continue;
  386.             }
  387.  
  388.             $val1 = $arr1[$key];
  389.  
  390.             // Overlap, merge
  391.             if (is_array($val1)) {
  392.                 $arr1[$key] = $this->merge($val1, $val2);
  393.             }
  394.         }
  395.  
  396.         return $arr1;
  397.     }
  398.  
  399.     /**
  400.      * Modify packets using the response to the challenge packet received
  401.      * earlier for a single gameserver.
  402.      *
  403.      * @param     string    $prot        Protocol name
  404.      * @param     array     $data        Packet that need to be modified
  405.      * @param     string    $response    Challenge response packet
  406.      * @return    array     The modified packet
  407.      */
  408.     private function processChallengeResponse($prot, $data, $response)
  409.     {
  410.         $result = '';
  411.        
  412.         // Load the protocol
  413.         $prot = $this->getProtocol($prot);
  414.  
  415.         // Modify the packet using the challenge response
  416.         $prot->setData(new GameQ_Buffer($response));
  417.  
  418.         try {
  419.             $result = $prot->parseChallenge($data);
  420.         }
  421.         catch (GameQ_ParsingException $e) {
  422.             if ($this->getOption('debug')) print $e;
  423.         }
  424.  
  425.         return $result;
  426.     }
  427.  
  428.     /**
  429.      * Modify packets using the response to the challenge packet received
  430.      * earlier.
  431.      *
  432.      * @param     array    $packets      Packets that need to be modified
  433.      * @return    array    The modified packets
  434.      */
  435.     private function processChallengeResponses($packets)
  436.     {
  437.         foreach ($packets as $pid => &$packet) {
  438.  
  439.             // Not a challenge-response type server, ignore
  440.             if (!isset($packet['challenge'])) continue;
  441.  
  442.             // Challenge-response type, but no response, remove
  443.             if (!isset($packet['response'][0])) {
  444.                 unset($packet);
  445.                 continue;
  446.             }
  447.  
  448.             // We got a response, process
  449.             $prot = $packet['prot'];
  450.             $data = $packet['data'];
  451.             $resp = $packet['response'][0];
  452.  
  453.             // Process the packet
  454.             $packet['data'] = $this->processChallengeResponse($prot, $data, $resp);
  455.  
  456.             // Packet could not be parsed, remove
  457.             if (empty($packet['data'])) {
  458.                 unset($packet);
  459.                 continue;
  460.             }
  461.  
  462.             // Clear the response field
  463.             unset($packet['response']);
  464.         }
  465.  
  466.         return $packets;
  467.     }
  468.  
  469.     /**
  470.      * Process a normal server response.
  471.      *
  472.      * @param     string    $protname      Protocol name
  473.      * @param     string    $packetname    Packet name
  474.      * @param     string    $data          Packet data
  475.      * @return    array     A processed response (key => value pairs)
  476.      */
  477.     private function processResponse($protname, $packetname, $data)
  478.     {
  479.         $debug = $this->getOption('debug');
  480.        
  481.         // Nothing to process
  482.         if (!isset($data) or count($data) === 0) return array();
  483.  
  484.         // Load the protocol
  485.         $prot = $this->getProtocol($protname);
  486.         $call = array($prot, $packetname);
  487.  
  488.         // Preprocess the packet data
  489.         try {
  490.             $data = $prot->preprocess($data);
  491.             if ($data == false) return array();
  492.         }
  493.         catch (GameQ_ParsingException $e) {
  494.             if ($debug) print $e;
  495.         }
  496.  
  497.         // Check if the parsing method actually exists
  498.         if (!is_callable($call)) {
  499.             trigger_error('GameQ::processResponse: unable to call ' . $protname . '::' . $packetname . '.',
  500.                     E_USER_ERROR);
  501.         }
  502.        
  503.         // Parse the packet
  504.         $prot->setData(new GameQ_Buffer($data), new GameQ_Result());
  505.  
  506.         try {
  507.             call_user_func($call);
  508.         }
  509.         catch (GameQ_ParsingException $e) {
  510.             if ($debug) print $e;
  511.         }
  512.  
  513.         return $prot->getData();
  514.     }
  515.  
  516.     /**
  517.      * Join raw data to servers
  518.      *
  519.      * @param     array    $packets  Server responses
  520.      * @param     array    $servers  Server data
  521.      * @return    Processed server responses
  522.      */
  523.     private function processRaw($packets, $servers)
  524.     {
  525.         // Create an empty result list
  526.         $results = array();
  527.         foreach ($servers as $sid => $server) {
  528.             $results[$sid] = array();
  529.         }
  530.  
  531.         // Add packets to server
  532.         foreach ($packets as &$packet) {
  533.             if (!isset($packet['response'])) $packet['response'] = null;
  534.             $results[$packet['sid']][$packet['name']] = $packet['response'];
  535.         }
  536.  
  537.         return $results;
  538.     }
  539.  
  540.     /**
  541.      * Batch process server responses
  542.      *
  543.      * @param     array    $packets  Server responses
  544.      * @param     array    $servers  Server data
  545.      * @return    Processed server responses
  546.      */
  547.     private function processResponses($packets, $servers)
  548.     {
  549.         // Create an empty result list
  550.         $results = array();
  551.         foreach ($servers as $sid => $server) {
  552.             $results[$sid] = array();
  553.         }
  554.  
  555.         // Process each packet and add it to the proper server
  556.         foreach ($packets as $packet) {
  557.  
  558.             if (!isset($packet['response'])) continue;
  559.  
  560.             $name = $packet['name'];
  561.             $prot = $packet['prot'];
  562.             $sid  = $packet['sid'];
  563.  
  564.             $result = $this->processResponse($prot, $name, $packet['response']);
  565.             $results[$sid] = $this->merge($results[$sid], $result);
  566.         }
  567.  
  568.         // Add some default variables
  569.         foreach ($results as $sid => &$result) {
  570.  
  571.             $sv = $servers[$sid];
  572.            
  573.             $result['gq_online']  = !empty($result);
  574.             $result['gq_address'] = $sv['addr'];
  575.             $result['gq_port']    = $sv['port'];
  576.             $result['gq_prot']    = $sv['prot'];
  577.             $result['gq_type']    = $sv['type'];
  578.         }
  579.  
  580.         return $results;
  581.     }
  582. }
  583. ?>
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement