Code Newbie
News     Forums     Search     Members     Sign Up    

My Code Newbie
Username

Password

Articles/Snippets
ASP Classic
ASP.NET
C
C#
C++
HTML / CSS
Java
Javascript
Linux / BSD
Perl
PHP
Python
Ruby
SQL
VB 6
VB.NET

C.N. Friends
  Planet Rome

Link to Us!
Code Newbie
  Code Newbie
    forums
Old 06-15-2006, 11:45 PM   #1 (permalink)
Redline
PHP Student
 
Join Date: Oct 2004
Location: Forest Grove, OR
Posts: 151
Redline is on a distinguished road
Send a message via AIM to Redline Send a message via MSN to Redline
socket based CS server query

I've been pulling my hair out trying to figure out how to do info queries on counter-strike servers.

I ended up having to use Ethereal to catch UDP packets and find out exactly what I send to the counter-strike server when I do a refresh. The initial query is always the same. It's a padded string which basically says "Source Engine Query.":
Quote:
\377\377\377\377\124\123\157\165\162\143\145\40\10 5\156\147\151\156\145\40\121\165\145\162\171\0
After sending that message, I get back some basic server info. The address, name, current/max players, etc. Here's a sample response converted to ascii:

Quote:
0. ÿÿÿÿm69.90.60.125:27015
1. [euphNET] Pub #1
2. de_nuke
3. cstrike
4. Counter-Strike
5. /dl
6. www.counter-strike.net
7.
Now in order to get the list of players and scores, I have to send a response to this. It's different for each server, but every time it's a 9 byte string consisting of a static 5 byte header. Only the following 4 bytes are different per server. Here is the key for this particular server:

"\377\377\377\377\125\247\304\302\72"

So far, this 4 byte key hasn't changed the the server I've been testing on over the last few days.

The key is not calculated on the server name, map name, or number of players as far as I can tell, otherwise the key I've been using for the test server wouldn't be valid anymore. Maybe it's based on the server address? If it is, I haven't been able to figure out how it's derived.

I spent most of the day today coming up with a regular expression to extract player id's, names and scores from this mess of octal values. As soon as I figure out how to generate "callback" keys, I'll start turning this into a class, and work on rcon. This is just a project to help me learn socket programming. I'd never touched them before; there was a bit of a stigma around the concept for me.
__________________
Current Project
Redline is offline   Reply With Quote
Old 06-16-2006, 10:54 AM   #2 (permalink)
sde
Moderator
 
sde's Avatar
 
Join Date: May 2002
Location: us.ca
Posts: 4,532
sde is on a distinguished road
i spent some time a while ago on this. i had a class working for 1.6 (originally coded by madhatter) but then the implementation changed with source. lots of tweaking, trial, and error and i finally got it working. the last few lines instanciates the class and runs the query. enjoy
PHP Code:
<?
function get_float32($fourchars) {
    
$bin='';
    for(
$loop 0$loop <= 3$loop++) { 
        
$bin str_pad(decbin(ord(substr($fourchars$loop1))), 8'0'STR_PAD_LEFT).$bin
    } 
    
$exponent bindec(substr($bin18)); 
    
$exponent = ($exponent)? $exponent 127 $exponent
    if(
$exponent) { 
        
$int bindec('1'.substr($bin9$exponent)); 
        
$dec bindec(substr($bin$exponent)); 
        
$time "$int.$dec";     
        return 
number_format($time 602); 
    } else { 
        return 
0.0
    } 
}

class 
sourceQueryCS{

  function 
sourceQueryCS($ip,$port){

    
$this->ip=$ip;
    
$this->port=$port;
    
$this->address=$ip.":".$port;
    
$this->hostname "";
    
$this->map "";
    
$this->mod "";
    
$this->modname "";
    
$this->active "";
    
$this->max "";
    
$this->cvars = array();
    
$this->players = array();
    
$this->excluded_cvars = array();
    
    
/*
    // you can define cvars you wish to exclude
    // this may be useful if you are looping through
    // the cvar array instead of just calling individual cvars
    
    $this->excluded_cvars = array(
      "mp_falldamage",
      "mp_weaponstay",
      "mp_forcerespawn",
      "mp_autocrosshair",
      "decalfrequency",
      "coop",
      "mp_teamlist",
      "mp_allowNPCs",
      "sv_stopspeed",
      "sv_noclipaccelerate",
      "sv_noclipspeed",
      "sv_specaccelerate",
      "sv_specspeed",
      "sv_specnoclip",
      "sv_maxspeed",
      "sv_accelerate",
      "sv_airaccelerate",
      "sv_wateraccelerate",
      "sv_waterfriction",
      "sv_rollspeed",
      "sv_rollangle",
      "sv_friction",
      "sv_bounce",
      "sv_stepsize",
      "r_VehicleViewDampen",
      "r_JeepViewDampenFreq",
      "r_JeepViewDampenDamp",
      "r_JeepViewZHeight",
      "r_AirboatViewDampenFreq",
      "r_AirboatViewDampenDamp",
      "r_AirboatViewZHeight",
      "sv_pausable"
    );
    */
    
    
$this->_sock fsockopen("udp://".$this->ip,$this->port$errno$errstr3);

    if (!
$this->_sock) {
      echo 
"unable to connect to ".$this->ip.":".$this->port;
      exit;
    }
    
    
$this->getInfo();
    
$this->getRules();
    
$this->getPlayers();

    
fclose($this->_sock);
  }
  
  function 
getInfo(){
    
$array = array();
    
$query=chr(0xFF).chr(0xFF).chr(0xFF).chr(0xFF).chr(0x54);
    
fwrite($this->_sock$query);
    
socket_set_timeout($this->_sock2,0);
      
$buffer=fread($this->_sock,1);
      
$stat=socket_get_status($this->_sock);
      
$buffer.=fread($this->_sock$stat["unread_bytes"]);
      
    
$buffer=substr($buffer,6);
    
$text="";
        
$count=0
        
$arr=array(0);
        do { 
            
$tmp=substr($buffer,0,1);$buffer=substr($buffer,1); 
            if (!
ord($tmp)) { $array[$count++]=$text$text=""; }
            else { 
$text.=$tmp; }
        } while (
$count<5);
        for(
$i=0;$i<=6;$i++, $count++) {
            
$tmp=substr($buffer,0,1);$buffer=substr($buffer,1);
            if(
$count==|| $count==9)
                
$array[$count]=$tmp;
            else
                
$array[$count]=ord($tmp);
        } 
//count = 12
        
if($array[$count-1]) { //if ismod
            
do {
                
$tmp=substr($buffer,0,1);$buffer=substr($buffer,1); 
                if (
ord($tmp)!=0)
                    
$array[$count].=$tmp// mod website [12]
            
} while(ord($tmp)!=0);
            
$count++;
            do {
                
$tmp=substr($buffer,0,1);$buffer=substr($buffer,1); 
                if (
ord($tmp)!=0)
                    
$array[$count].=$tmp// mod FTP [13]
            
} while(ord($tmp)!=0);
            
$count++;
            
$array[$count++]=ord(substr($buffer,0,1)); $buffer=substr($buffer,1); //Dummy bit? [14] o_0 -- SHOULD be server-only bit... ^_^
            
$tmp=substr($buffer,0,4);$buffer=substr($buffer,4); 
            for(
$j=0;$j<4;$j++) {
                
$array[$count]+=(pow(256,$j) * ord(substr($tmp,$j,1))); //Ver [15]
            
$count++;                
            
$tmp=substr($buffer,0,4);$buffer=substr($buffer,4); 
            for(
$j=0;$j<4;$j++) {
                
$array[$count]+=(pow(256,$j) * ord(substr($tmp,$j,1))); //Size [16]
            
$count++;
            
$array[$count++]=ord(substr($buffer,0,1));$buffer=substr($buffer,1); //server-only [17]
            
$array[$count++]=ord(substr($buffer,0,1));$buffer=substr($buffer,1); //custom client.dll [18]
            
$array[$count++]=ord(substr($buffer,0,1));$buffer=substr($buffer,1); //Secure! [19]
        
} else {
            for(
$i=0;$i<8;$i++)
                
$array[$count++]='\0';
        }
        
        
$this->hostname $array[0];
        
$this->map      $array[1];
        
$this->mod      $array[2];
        
$this->modname  $array[3];
        
$this->active   $array[6];
        
$this->max      $array[7];
  }
  
  function 
getplayers(){
    
$query=chr(0xFF).chr(0xFF).chr(0xFF).chr(0xFF).chr(0x55);
    
fwrite($this->_sock$query);
    
socket_set_timeout($this->_sock2,0);
      
$buffer=fread($this->_sock,1);
      
$stat=socket_get_status($this->_sock);
      
$buffer.=fread($this->_sock$stat["unread_bytes"]);
  
    
$buffer=substr($buffer,5);
        
$count=ord(substr($buffer,0,1)); //Num active players
        
$buffer=substr($buffer,1);
        
$tfrags="";
        
$ttime=0;
        
$array=array(0);
        for(
$i=0;$i<$count;$i++){
            
$rfrags=0.0;
            
$rtime=0;
            
$stime=0;
            
$tind=ord(substr($buffer,0,1));
            
$buffer=substr($buffer,1);
            
$tname="";
            do {
                
$tmp=substr($buffer,0,1);
                
$buffer=substr($buffer,1);
                
$tname.=$tmp;
            }while(
ord($tmp)!=0);
            
            
$tfrags=substr($buffer,0,4);
            
$buffer=substr($buffer,4);
            for(
$j=0;$j<4;$j++) {
                
$rfrags+=(pow(256,$j) * ord(substr($tfrags,$j,1)));
            }
            if(
$rfrags 2147483648) {
                
$rfrags-=4294967296;
            }
            
$tmp=substr($buffer,0,4);
            
$buffer=substr($buffer,4);
            
$rtime=get_float32($tmp);
            
$array[$i]=array("index" => $tind,"name" => $tname,"frags" => $rfrags"time" => $rtime);
      }
      
      
$this->players $array;
  }
  
  function 
getRules(){
    
$array = array();
    
$rules = array();
    
$query=chr(0xFF).chr(0xFF).chr(0xFF).chr(0xFF).chr(0x56);
    
fwrite($this->_sock$query);
    
socket_set_timeout($this->_sock2,0);
      
$buffer=fread($this->_sock,1);
      
$stat=socket_get_status($this->_sock);
      
$buffer.=fread($this->_sock$stat["unread_bytes"]);

      
$array explode(chr(0),$buffer);
      
$count = (count($array)-1);
      
      for(
$i=1;$i<$count;$i++){
        if(
in_array($array[$i],$this->excluded_cvars)){
          
$i++;
          continue;
        }
        
        
$rules[$array[$i]]=$array[++$i];
      }
      
      
$this->cvars $rules;
  }
}

header("content-type: text/plain");
$sq = new sourceQueryCS("192.18.1.250",27016);
print_r($sq);
?>
__________________
Mike
sde is offline   Reply With Quote
Old 06-16-2006, 12:05 PM   #3 (permalink)
Redline
PHP Student
 
Join Date: Oct 2004
Location: Forest Grove, OR
Posts: 151
Redline is on a distinguished road
Send a message via AIM to Redline Send a message via MSN to Redline
Awesome, there're definitely a few things in there I could use. Specifically, the function you're using to turn those arbitrary looking numbers at the end of each player info row into a time. Unfortunately I think steam hacked up the socket commands and responses.

Short queries like the getInfo() query

Quote:
chr(0xFF).chr(0xFF).chr(0xFF).chr(0xFF).chr(0x54)
don't work anymore. The server won't send back a response. The longer info query I'm sending has the same 5th character though. Same with the player list query.

I think steam made some changes to make it harder for third-parties to communicate with their servers. I'm still stumped about how this "key" is being generated
__________________
Current Project
Redline is offline   Reply With Quote
Old 06-16-2006, 04:11 PM   #4 (permalink)
sde
Moderator
 
sde's Avatar
 
Join Date: May 2002
Location: us.ca
Posts: 4,532
sde is on a distinguished road
ah that sucks that they changed it. =/
__________________
Mike
sde is offline   Reply With Quote
Old 06-16-2006, 04:59 PM   #5 (permalink)
Redline
PHP Student
 
Join Date: Oct 2004
Location: Forest Grove, OR
Posts: 151
Redline is on a distinguished road
Send a message via AIM to Redline Send a message via MSN to Redline
I know =/ Any idea how to figure out what string to send with the player list command? It has the same 5th character as the one you used in your class, but now it's followed by 4 practically arbitrary chars. *arg*
__________________
Current Project
Redline is offline   Reply With Quote
Old 06-16-2006, 06:38 PM   #6 (permalink)
Redline
PHP Student
 
Join Date: Oct 2004
Location: Forest Grove, OR
Posts: 151
Redline is on a distinguished road
Send a message via AIM to Redline Send a message via MSN to Redline
Okay, I found the answer. In order to get the "key" to retrieve the player list, and server settings, you need to issue a getchallenge request to the server.

Quote:
FF FF FF FF 57
It then replies with "FF FF FF FF 41" followed by the four character challenge key.

This was found here, on the wiki valve developer site: http://developer.valvesoftware.com/w...Server_Queries
__________________
Current Project
Redline is offline   Reply With Quote
Old 06-17-2006, 04:57 AM   #7 (permalink)
Redline
PHP Student
 
Join Date: Oct 2004
Location: Forest Grove, OR
Posts: 151
Redline is on a distinguished road
Send a message via AIM to Redline Send a message via MSN to Redline
Okay, I have a working class now. It's modeled after yours Mike. I hate modeling my code after anyone elses work; I feel like I'm cheating myself. But I liked the overall layout of your class. I certainly didn't copy it though. I more or less just took the framework. Except for the get_float32 function. I stole that. I barely understand what it's doing, even after writing my own function to build the 32bit score. I didn't realize the score was 32 bit until I found my class didn't work on servers where player scores were more than 255. Even then, it wasn't until I found that the score was considered as "long" instead of "byte", in the valve development wiki site.

Okay. 5am. Time for bed.
__________________
Current Project
Redline is offline   Reply With Quote
Old 06-17-2006, 07:26 AM   #8 (permalink)
redhead
Newbie
 
redhead's Avatar
 
Join Date: Jun 2002
Location: Denmark
Posts: 1,726
redhead is on a distinguished road
Quote:
I hate modeling my code after anyone elses work; I feel like I'm cheating myself.
Half the work as a programmer, is borrowing code from others, why invent the wheel twice for something.
__________________
Don't worry Ma'am, We're university students, We know what We're doing.
-----
If you pull the pin, Mr.Grenade would no longer be your friend.
-----
01000111 01101111 00100000 01000011 00100000 00100001
redhead is offline   Reply With Quote
Old 06-17-2006, 12:34 PM   #9 (permalink)
Redline
PHP Student
 
Join Date: Oct 2004
Location: Forest Grove, OR
Posts: 151
Redline is on a distinguished road
Send a message via AIM to Redline Send a message via MSN to Redline
Oh, believe me, I know. The difference is I'm still learning quite a bit. It's generally better for me to do things on my own the first few times to get the swing of it
__________________
Current Project
Redline is offline   Reply With Quote
Old 06-18-2006, 01:18 AM   #10 (permalink)
Redline
PHP Student
 
Join Date: Oct 2004
Location: Forest Grove, OR
Posts: 151
Redline is on a distinguished road
Send a message via AIM to Redline Send a message via MSN to Redline
This is still a little over my head

Hey, mike, or anyone really, would you be able to change this function so that it will work with negative numbers? I'm still not sure how to convert 32 bits into negative values. It works fine for converting positive frags, but if someone has a negative score, it spits out insanely large numbers.

PHP Code:
function get_long32($num) {
  
$num explode(','$num);
  
$binary_num "";
  for (
$i 3$i >= 0$i--) {
    
$binary_num .= str_pad(decbin($num[$i]), 8'0'STR_PAD_LEFT);
  }
  return 
bindec($binary_num);

Right now it takes four comma delimited dec values, explodes it, reassembles the string in reverse order with each individual number converted to binary format, then returns that compiled number in dec form.

If you have the time to rebuild the function that would be fantastic. If you have the time to explain how the proper conversion works, that'd be doubly fantastic, 'cause I realize I took a shortcut on this one.
__________________
Current Project
Redline is offline   Reply With Quote