Official Soldat Forums

Soldat Fans => Developers Corner => Topic started by: FliesLikeABrick on March 25, 2007, 09:57:35 pm

Title: PHP ASE parser
Post by: FliesLikeABrick on March 25, 2007, 09:57:35 pm
here's another PHP one I just wrote, I think it is a bit more efficient, and a bit more featureful than Frenchie's is.  It provides some "fixed" versions of things like time remaining, time limit, and other values (strips text so they're int-only and ready for use)

The section that "fixes" the output to make it more readily usable is labelled with a comment and can be removed.


Code: [Select]
<?php
// PHP ASE parser for Soldat by FliesLikeABrick
// May be used for other games with a few slight modifications
// (mainly removing the part that pretties up the output)
// You are free to use this code for anything you want and redistribute/modify at will

function readASEString($sock,$length false) {
    
$length $length $length ord(fread($sock,1));
    return 
fread($sock,$length-1);
}

function 
ASE($host,$port) {
    
$info = Array();
    
$s fsockopen("udp://$host",$port+123);
    
fwrite($s,"s");
    
fread($s,4);
    
readASEString($s);
    
$info['port'] = readASEString($s);
    
$info['servername'] = readASEString($s);
    
$info['ip'] = gethostbyname($host);
    
$info['mode'] = readASEString($s);
    
$info['map'] = readASEString($s);
    
$info['version'] = readASEString($s);
    
$info['passworded'] = readASEString($s);
    
$info['numplayers'] = readASEString($s);
    
$info['maxplayers'] = readASEString($s);

    
// prime the loop conditional
    
$lengthstring fread($s,1);
    
$length ord($lengthstring);

    
// loop through the key/value pairs and set them
    
    
while($length != 1) {
        
$info[readASEString($s,$length)] = readASEString($s);
        
$lengthstring fread($s,1);
        
$length ord($lengthstring);
    }

    
// this entire section can be removed if you don't use them
    
$info['survival'] = $info['Survival'] == 'No' false:true;
    
$info['realistic'] = $info['Realistic'] == 'No' false:true;
    
$info['remaining'] = explode(' ',$info['Time Left']);
    
$info['remaining'] = $info['remaining'][0];
    
$info['minutes'] = explode(':',$info['remaining']);
    
$info['seconds'] = $info['minutes'][1];
    
$info['minutes'] = $info['minutes'][0];
    
$info['timelimit'] = explode(' ',$info['Time Limit']);
    
$info['timelimit'] = $info['timelimit'][0];
    
$info['protected'] = $info['Protected'] == 'No' false:true;
    
// end removable section

    
$info['players'] = Array();
    for(
$i=0;$i<$info['numplayers'];$i++) {
        
$info['players'][$i] = Array();
        
fread($s,1);
        
$info['players'][$i]['name'] = readASEString($s);
        
$info['players'][$i]['team'] = readASEString($s);
        
$info['players'][$i]['skin'] = readASEString($s);
        
$info['players'][$i]['score'] = readASEString($s);
        
$info['players'][$i]['ping'] = readASEString($s);
        
$info['players'][$i]['time'] = readASEString($s);
    }    
    return 
$info;
}

?>




It just returns an array with all of the information, as follows when used as ASE('u13.net',23073):
Code: [Select]
Array
(
    [port] => 23073
    [servername] => U13 CTF Public [Chicago]
    [ip] => 67.106.77.193
    [mode] => Capture the Flag
    [map] => ctf_Equinox
    [version] => 1.3.1
    [passworded] => 0
    [numplayers] => 12
    [maxplayers] => 12
    [Respawn Time] => 5 Secs
    [Bonus Frequency] => None
    [Survival] => No
    [Realistic] => No
    [System] => Linux
    [Time Left] => 16:32 Minutes
    [Time Limit] => 20 Minutes
    [Next Map] => ctf_Kampf
    [Protected] => No
    [respawntime] => 5 Secs
    [bonusfreq] => None
    [survival] =>
    [realistic] =>
    [os] => Linux
    [remaining] => 16:32
    [minutes] => 16
    [seconds] => 32
    [timelimit] => 20
    [nextmap] => ctf_Kampf
    [protected] =>
    [players] => Array
        (
            [0] => Array
                (
                    [name] => mr.ming
                    [team] => Bravo
                    [skin] => Bravo
                    [score] => 9
                    [ping] => 116
                    [time] => 3m
                )

            [1] => Array
                (
                    [name] => Kastor
                    [team] => Alpha
                    [skin] => Alpha
                    [score] => 7
                    [ping] => 16
                    [time] => 36m
                )

            [2] => Array
                (
                    [name] => El Padre
                    [team] => Bravo
                    [skin] => Bravo
                    [score] => 5
                    [ping] => 33
                    [time] => 55m
                )

            [3] => Array
                (
                    [name] => DI
                    [team] => Bravo
                    [skin] => Bravo
                    [score] => 7
                    [ping] => 83
                    [time] => 12m
                )

            [4] => Array
                (
                    [name] => Playing Soldat on my PSP
                    [team] => Alpha
                    [skin] => Alpha
                    [score] => 1
                    [ping] => 50
                    [time] => 3m
                )

            [5] => Array
                (
                    [name] => Sniper(1)
                    [team] => Bravo
                    [skin] => Bravo
                    [score] => 6
                    [ping] => 83
                    [time] => 17m
                )

            [6] => Array
                (
                    [name] => Diss
                    [team] => Alpha
                    [skin] => Alpha
                    [score] => 6
                    [ping] => 16
                    [time] => 79m
                )

            [7] => Array
                (
                    [name] => Milf.Hunter
                    [team] => Alpha
                    [skin] => Alpha
                    [score] => 2
                    [ping] => 50
                    [time] => 6m
                )

            [8] => Array
                (
                    [name] => Corsair
                    [team] => Bravo
                    [skin] => Bravo
                    [score] => 22
                    [ping] => 33
                    [time] => 10m
                )

            [9] => Array
                (
                    [name] => K-Vader
                    [team] => Alpha
                    [skin] => Alpha
                    [score] => 4
                    [ping] => 16
                    [time] => 9m
                )

            [10] => Array
                (
                    [name] => ==Gordon Freeman==
                    [team] => Alpha
                    [skin] => Alpha
                    [score] => 26
                    [ping] => 183
                    [time] => 16m
                )

            [11] => Array
                (
                    [name] => [Legends]--->Eclipse
                    [team] => Bravo
                    [skin] => Bravo
                    [score] => 26
                    [ping] => 16
                    [time] => 9m
                )

        )

)

Title: Re: PHP ASE parser
Post by: mikembm on March 25, 2007, 10:06:54 pm
Nice. A lot better than what I'm using currently. :D
Title: Re: PHP ASE parser
Post by: jrgp on March 25, 2007, 10:18:00 pm
Thank you Flies!

This will be perfect for building a server status panel on my soldat site.
Title: Re: PHP ASE parser
Post by: FliesLikeABrick on March 25, 2007, 10:35:49 pm
You should note that this code will just hang if a server is offline, I don't think it'll ever give an error.  If you want to check for a server to be online first, use some other method.
Title: Re: PHP ASE parser
Post by: Frenchie on March 26, 2007, 01:10:39 am
Wow cool just when I login I see this thread.
Good work looks good ;D

Except
Code: [Select]
Array
(
    [Survival] => No
    [Realistic] => No
    [Protected] => No
    [survival] =>
    [realistic] =>
    [protected] =>
The lowercase versions are empty..

When I get the time I'll try it out. :)
Title: Re: PHP ASE parser
Post by: FliesLikeABrick on March 26, 2007, 01:16:03 am
Wow cool just when I login I see this thread.
Good work looks good ;D

Except
Code: [Select]
Array
(
    [Survival] => No
    [Realistic] => No
    [Protected] => No
    [survival] =>
    [realistic] =>
    [protected] =>
The lowercase versions are empty..

When I get the time I'll try it out. :)

They aren't empty, they actually contain the boolean "false" value (since for the lowercase versions I translated Yes/No to true/false), that is just how print_r shows false.
Title: Re: PHP ASE parser
Post by: mikembm on March 26, 2007, 02:05:37 am
Too make it sort the players by score add this code right before the return $info line:

Code: [Select]
$sort = array();
foreach($info['players'] as $res)
$sort[] = $res['score'];
array_multisort($sort, SORT_DESC, $info['players']);
Title: Re: PHP ASE parser
Post by: mar77a on March 26, 2007, 03:55:11 pm
love the compact conditional

great work
Title: Re: PHP ASE parser
Post by: Laser Guy on March 26, 2007, 04:02:25 pm
and how do u use that?? (sry, but i never really was interested in that)
Title: Re: PHP ASE parser
Post by: FliesLikeABrick on March 26, 2007, 04:37:42 pm
and how do u use that?? (sry, but i never really was interested in that)

the compact conditional or the whole parser?
Title: Re: PHP ASE parser
Post by: Laser Guy on March 26, 2007, 04:55:21 pm
both
Title: Re: PHP ASE parser
Post by: Trututu on March 29, 2007, 07:31:01 am
Where do I have to change port and ip?
Title: Re: PHP ASE parser
Post by: mikembm on March 29, 2007, 12:04:15 pm
Can somebody make this using socket_create instead of fsockopen?

I ask this because most hosts have fsockopen disabled for security reasons.
Title: Re: PHP ASE parser
Post by: Thomas on May 05, 2007, 04:01:32 pm
With socket-functions.
The server sends the whole packet in one and you cannot read parts from it with socket functions.
So i connect with socket functions, get the whole thing, and continue with "virtual socket functions".
Works fine :)

Code is some posts below (http://forums.soldat.pl/index.php?topic=12200.0#msg159898)
Title: Re: PHP ASE parser
Post by: chrisgbk on May 05, 2007, 11:20:28 pm
With socket-functions.
The server sends the whole packet in one and you cannot read parts from it with socket functions.
So i connect with socket functions, get the whole thing, and continue with "virtual socket functions".
Works fine :)
Code: [Select]
<?php
// PHP ASE parser for Soldat by FliesLikeABrick
// changed from fsockopen to socket_* functions by minus
// May be used for other games with a few slight modifications
// (mainly removing the part that pretties up the output)
// You are free to use this code for anything you want and redistribute/modify at will

function readASEString($sock,$length false) {
    $length $length $length ord(vsocket_read($sock,1));
    return vsocket_read($sock,$length-1);
}

function 
ASE($host,$port) {
    $info = Array();
    // Using Socketfunctions
    $s socket_create(AF_INETSOCK_DGRAMSOL_UDP);
    socket_connect($s$host$port+123);
    socket_write($s,"s");
    $vsock socket_read($s,512); // read to "virtual socket"
    $vpos 0// virtual socket "pointer"
    socket_close($s);
    $s = &$vsock;
    vsocket_read($s,4);
    
    readASEString
($s);
    $info['port'] = readASEString($s);
    $info['servername'] = readASEString($s);
    $info['ip'] = gethostbyname($host);
    $info['mode'] = readASEString($s);
    $info['map'] = readASEString($s);
    $info['version'] = readASEString($s);
    $info['passworded'] = readASEString($s);
    $info['numplayers'] = readASEString($s);
    $info['maxplayers'] = readASEString($s);

    // prime the loop conditional
    $lengthstring vsocket_read($s,1);
    $length ord($lengthstring);

    // loop through the key/value pairs and set them
    
    
while($length != 1) {
        $info[readASEString($s,$length)] = readASEString($s);
        $lengthstring vsocket_read($s,1);
        $length ord($lengthstring);
    }

    // this entire section can be removed if you don't use them
    $info['survival'] = $info['Survival'] == 'No' false:true;
    $info['realistic'] = $info['Realistic'] == 'No' false:true;
    $info['remaining'] = explode(' ',$info['Time Left']);
    $info['remaining'] = $info['remaining'][0];
    $info['minutes'] = explode(':',$info['remaining']);
    $info['seconds'] = $info['minutes'][1];
    $info['minutes'] = $info['minutes'][0];
    $info['timelimit'] = explode(' ',$info['Time Limit']);
    $info['timelimit'] = $info['timelimit'][0];
    $info['protected'] = $info['Protected'] == 'No' false:true;
    // end removable section

    $info['players'] = Array();
    for($i=0;$i<$info['numplayers'];$i++) {
        $info['players'][$i] = Array();
        vsocket_read($s,1);
        $info['players'][$i]['name'] = readASEString($s);
        $info['players'][$i]['team'] = readASEString($s);
        $info['players'][$i]['skin'] = readASEString($s);
        $info['players'][$i]['score'] = readASEString($s);
        $info['players'][$i]['ping'] = readASEString($s);
        $info['players'][$i]['time'] = readASEString($s);
    }    
    
return $info;
}

function 
vsocket_read($vsock$length)
{
    global $vpos;
    $str substr($vsock$vpos$length);
    $vpos += $length;
    return $str;
}

?>

You read a maximum of 512 bytes; wouldn't that cause problems given the size of a packet is 1188?
Title: Re: PHP ASE parser
Post by: Thomas on May 06, 2007, 03:24:44 am
1188 is the size of the REFRESH packet, not of the ASE packet.
Anyways, i changed it to 2048 to avoid errors.
Title: Re: PHP ASE parser
Post by: chrisgbk on May 06, 2007, 01:40:37 pm
1188 is the size of the REFRESH packet, not of the ASE packet.
Anyways, i changed it to 2048 to avoid errors.

Whoops, don't know where my head was heh :-[

Anyhow, what you should do is loop until the socket doesn't return any more data (either it will return "" or FALSE) to ensure you read it all.
Title: Re: PHP ASE parser
Post by: mikembm on May 06, 2007, 07:32:08 pm
Thomas, can you also make it timeout if the server doesn't exist?
Title: Re: PHP ASE parser
Post by: Thomas on May 07, 2007, 07:32:51 am
Anyhow, what you should do is loop until the socket doesn't return any more data (either it will return "" or FALSE) to ensure you read it all.
Nope, if i don't read everything at once i get an error that wheather the packet is too big or what i want to read is too less.
I'll go check how big it is on a full server.

mikembm: yeah

--------------
I tried to use socket_get_option($s, getprotobyname('UDP'), SO_RCVBUF); to get the size of the buffer but it didn't work.

Now tested with 25 Players - about 900 Byte.
I set it to 4096 which should be fine.
--------------
Here it is: (with "server down" message (also returns false))
Code: [Select]
<?php
// PHP ASE parser for Soldat by FliesLikeABrick
// changed from fsockopen to socket_* functions by minus
// May be used for other games with a few slight modifications
// (mainly removing the part that pretties up the output)
// You are free to use this code for anything you want and redistribute/modify at will

function readASEString($sock,$length false) {
    
$length $length $length ord(vsocket_read($sock,1));
    return 
vsocket_read($sock,$length-1);
}

function 
ASE($host,$port) {
    
$info = Array();
    
// Using Socketfunctions
    
$s socket_create(AF_INETSOCK_DGRAMSOL_UDP);
    
socket_connect($s$host$port+123);
    if(
socket_last_error() == 10054)
    {
echo 'Server not running';
return false;
    }
    
socket_write($s,"s");
    
$vsock socket_read($s4096); // read to "virtual socket"
    
$vpos 0// virtual socket "pointer"
    
socket_close($s);
    
$s $vsock;
    
vsocket_read($s,4);
    
    
readASEString($s);
    
$info['port'] = readASEString($s);
    
$info['servername'] = readASEString($s);
    
$info['ip'] = gethostbyname($host);
    
$info['mode'] = readASEString($s);
    
$info['map'] = readASEString($s);
    
$info['version'] = readASEString($s);
    
$info['passworded'] = readASEString($s);
    
$info['numplayers'] = readASEString($s);
    
$info['maxplayers'] = readASEString($s);

    
// prime the loop conditional
    
$lengthstring vsocket_read($s,1);
    
$length ord($lengthstring);

    
// loop through the key/value pairs and set them
    
    
while($length != 1) {
        
$info[readASEString($s,$length)] = readASEString($s);
        
$lengthstring vsocket_read($s,1);
        
$length ord($lengthstring);
    }

    
// this entire section can be removed if you don't use them
    
$info['survival'] = $info['Survival'] == 'No' false:true;
    
$info['realistic'] = $info['Realistic'] == 'No' false:true;
    
$info['remaining'] = explode(' ',$info['Time Left']);
    
$info['remaining'] = $info['remaining'][0];
    
$info['minutes'] = explode(':',$info['remaining']);
    
$info['seconds'] = $info['minutes'][1];
    
$info['minutes'] = $info['minutes'][0];
    
$info['timelimit'] = explode(' ',$info['Time Limit']);
    
$info['timelimit'] = $info['timelimit'][0];
    
$info['protected'] = $info['Protected'] == 'No' false:true;
    
// end removable section

    
$info['players'] = Array();
    for(
$i=0;$i<$info['numplayers'];$i++) {
        
$info['players'][$i] = Array();
        
vsocket_read($s,1);
        
$info['players'][$i]['name'] = readASEString($s);
        
$info['players'][$i]['team'] = readASEString($s);
        
$info['players'][$i]['skin'] = readASEString($s);
        
$info['players'][$i]['score'] = readASEString($s);
        
$info['players'][$i]['ping'] = readASEString($s);
        
$info['players'][$i]['time'] = readASEString($s);
    }    
    return 
$info;
}

function 
vsocket_read($vsock$length)
{
    global 
$vpos;
    
$str substr($vsock$vpos$length);
    
$vpos += $length;
    return 
$str;
}

?>
Title: Re: PHP ASE parser
Post by: mikembm on May 07, 2007, 11:41:23 am
The Server not running part doesn't seem to be working for me.. it just stays on loading.
Title: Re: PHP ASE parser
Post by: Thomas on May 08, 2007, 12:02:37 pm
It worked for me yesterday (PHP 5.2.2 + Apache 2.0.59)
Edit: wtf, yesterday it worked, now i get errors but the request works

Code: [Select]
<?php
    
// Using Socketfunctions
    
$s socket_create(AF_INETSOCK_DGRAMSOL_UDP);
    
socket_clear_error($s);
    
socket_connect($s$host$port+123);
    if(
socket_last_error())
    {
    echo 'Error'.socket_last_error().': '.socket_strerror(socket_last_error());
    socket_clear_error($s);
    #return false;
    
}
    
socket_write($s,"s");
    if(
socket_last_error())
    {
    echo 'Error'.socket_last_error().': '.socket_strerror(socket_last_error());
    socket_clear_error($s);
    #return false;
    
}
    
$vsock socket_read($s4096); // read to "virtual socket"
    
$vpos 0// virtual socket "pointer"
    
socket_close($s);
    
$s $vsock;
    
vsocket_read($s,4);
?>
Title: Re: PHP ASE parser
Post by: DorkeyDear on February 18, 2008, 05:40:36 pm
(sry for bringing up an old topic)
I'm having issues trying to get this to work all the time.
I've tried the code in the first post and the code in Thomas's most recent post..
It seems to work sometimes, but some servers I try seems to freeze it up and the browser keeps loading and loading and loading...
There are like a list of like 10 servers that I'm trying to loop through to get server information about..
Title: Re: PHP ASE parser
Post by: Slazenger on February 19, 2008, 10:32:09 am
Where do I need to put the servers IP and PORT?
Title: Re: PHP ASE parser
Post by: rayanaga on June 05, 2008, 05:48:58 pm
Add this right below the other code

It should ping your server and timeout if its not found. ;D
Code: [Select]
<?
//Settings.
$ipaddress = '127.0.0.1';
$port = '20000';

//Ping your server and then retrieve info
if(fsockopen ($ipaddress, $port, $errno, $errstr, 15))
print_r(ASE($ipaddress,$port));
else
echo 'The requested server is offline';
?>

Hope it helps!

Title: Re: PHP ASE parser
Post by: Frenchie on June 22, 2008, 04:25:14 am
Ok. I really needed a bulletproof ASE parser which wouldn't stuck in loops. I tried modifying FLAB's code and got somewhere but realised I wasn't get to the perfection I needed and my knowledge of this ASE protocol is lower than imagineable I decided to stick to some old code I had.

Because I see there are some in this topic who want a ASE parser who doesn't get stuck in loops I'll post my code. It has been used in my scripts which are run by cron jobs for quite a while.. so I know it is very reliable
(I found it on the internet somewhere, was modified by ChrisGBK to fully support the protocol and modified by Me to include the protection label (anti-hack))

I warn you it's extremely MESSY CODE!
some of you might die just looking at it

Code: [Select]
SNIP

Please ddon't bug too clean it up, I didn't post it here for those of you to bug me. I just wanted to help those who need another option as an ASE parser.

There is huge potential for this code to be tidied up. And if anyone does please upload for the rest to benefit.

Date Posted: June 22, 2008, 05:00:03 pm
EDIT-------------------------------
I decided to fix up the code / tidy it up. As I will be needing it in my next script. :)

I have tested both the ASE parsers on quite alot of servers and each one that FLAB's looped on, this one didn't (Not talking down FLAB's script, as I would like to have used it instead on using up my time re-writing this whole script)
This ASE Parser is more designed to be used in cron jobs / behind the scene scripts as it does not get caught on servers, has some error checking in it and Returns 'Error' if anything goes wrong. (This makes it easy to implement script checking into your script) And it works well

And one more thing  ;D
It's fully interchangeable / compatible with FLAB's script, I made it use the same variable names (and used his player for snippet) And it's compatible with that extra formatting snippet in FLAB's script.

Hope you enjoy... Had to modify / re-write the whole thing from 225 lines to just 65  ;D
Code: [Select]
<?php
//Original by: Christian Celler
//Player Details by: ChrisBGK
//Modified/Re-written by: Frenchie
function parse_ase (&$string) {
    
$size   intval (ord (substr ($string01)));
    if (
$size 0)
      
$value  substr ($string1$size-1);
    else
      
$value NULL;
    
$string substr ($string$size);
    return (
$value);
}

function 
ASE($host$port) {
$socket socket_create (AF_INETSOCK_DGRAMSOL_UDP);
    if (!(@
socket_connect ($socket$host$port+123))) 
return('Error'); //Could not connect
      
socket_set_option ($socketSOL_SOCKETSO_RCVTIMEO, array ('sec' => 0'usec' => 500000));
      
socket_send ($socket's'10);
  
  //Read all Data
      
while ($data = @socket_read ($socket16384PHP_BINARY_READ))
        
$result $output $data;

      
socket_close ($socket);

      if (empty (
$result))
   return('Error'); //No output
        
$result   substr ($result4);

        
parse_ase ($result);
        
$info['port'] = parse_ase ($result);
        
$info['servername'] = parse_ase ($result);
$info['ip'] = gethostbyname($host);
        
$info['mode'] = parse_ase ($result);
        
$info['map'] = parse_ase ($result);
        
$info['version'] = parse_ase ($result);
        
$info['passworded'] = parse_ase ($result);
        
$info['numplayers'] = parse_ase ($result);
        
$info['maxplayers'] = parse_ase ($result);        
      
//Loop through and retrieve Data 
        
while (ord (substr ($result01)) > 1)
        
$info[parse_ase($result)]= parse_ase ($result);

        
$result =substr($result1);

$info['players'] = Array();
         if (
strlen ($result) > 0) { //Check if there is still data to parse
   
for($i=0;$i<$info['numplayers'];$i++) {
       
  $info['players'][$i] = Array();
       
  $result =substr($result1);
       
  $info['players'][$i]['name'] = parse_ase($result);
        
$info['players'][$i]['team'] = parse_ase($result);
        
$info['players'][$i]['skin'] = parse_ase($result);
        
$info['players'][$i]['score'] = parse_ase($result);
        
$info['players'][$i]['ping'] = parse_ase($result);
        
$info['players'][$i]['time'] = parse_ase($result);
    
}    
     }
     return 
$info;
}
?>


Any feedback on this script appreciated
Title: Re: PHP ASE parser
Post by: chrisgbk on July 17, 2008, 05:18:13 pm
As a late followup to the discussion in here regarding reading data, and strange errors:

Thomas' code throws errors because he forgot to pass the socket to socket_last_error() resulting in the global socket error to be checked instead of the specific socket. Error checking should have been socket_last_error($s). He also forgot to clear the global socket error via socket_clear_error() without arguments before attempting to create the socket.

In order to guarantee the correct amount of data is read, use something like this:

Code: [Select]
while (($size = socket_recvfrom($socket, $data, ($last = $size) + 1024, MSG_PEEK, $host, $port)) && ($last != $size))
; // deliberately empty
socket_recvfrom($socket, $data, $size, 0, $host, $port);