Official Soldat Forums
Soldat Fans => Developers Corner => Topic started 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.
<?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):
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
)
)
)
-
Nice. A lot better than what I'm using currently. :D
-
Thank you Flies!
This will be perfect for building a server status panel on my soldat site.
-
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.
-
Wow cool just when I login I see this thread.
Good work looks good ;D
Except
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. :)
-
Wow cool just when I login I see this thread.
Good work looks good ;D
Except
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.
-
Too make it sort the players by score add this code right before the return $info line:
$sort = array();
foreach($info['players'] as $res)
$sort[] = $res['score'];
array_multisort($sort, SORT_DESC, $info['players']);
-
love the compact conditional
great work
-
and how do u use that?? (sry, but i never really was interested in that)
-
and how do u use that?? (sry, but i never really was interested in that)
the compact conditional or the whole parser?
-
both
-
Where do I have to change port and ip?
-
Can somebody make this using socket_create instead of fsockopen?
I ask this because most hosts have fsockopen disabled for security reasons.
-
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)
-
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 :)
<?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_INET, SOCK_DGRAM, SOL_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?
-
1188 is the size of the REFRESH packet, not of the ASE packet.
Anyways, i changed it to 2048 to avoid errors.
-
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.
-
Thomas, can you also make it timeout if the server doesn't exist?
-
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))
<?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_INET, SOCK_DGRAM, SOL_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($s, 4096); // 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;
}
?>
-
The Server not running part doesn't seem to be working for me.. it just stays on loading.
-
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
<?php
// Using Socketfunctions
$s = socket_create(AF_INET, SOCK_DGRAM, SOL_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($s, 4096); // read to "virtual socket"
$vpos = 0; // virtual socket "pointer"
socket_close($s);
$s = $vsock;
vsocket_read($s,4);
?>
-
(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..
-
Where do I need to put the servers IP and PORT?
-
Add this right below the other code
It should ping your server and timeout if its not found. ;D
<?
//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!
-
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
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
<?php
//Original by: Christian Celler
//Player Details by: ChrisBGK
//Modified/Re-written by: Frenchie
function parse_ase (&$string) {
$size = intval (ord (substr ($string, 0, 1)));
if ($size > 0)
$value = substr ($string, 1, $size-1);
else
$value = NULL;
$string = substr ($string, $size);
return ($value);
}
function ASE($host, $port) {
$socket = socket_create (AF_INET, SOCK_DGRAM, SOL_UDP);
if (!(@socket_connect ($socket, $host, $port+123)))
return('Error'); //Could not connect
socket_set_option ($socket, SOL_SOCKET, SO_RCVTIMEO, array ('sec' => 0, 'usec' => 500000));
socket_send ($socket, 's', 1, 0);
//Read all Data
while ($data = @socket_read ($socket, 16384, PHP_BINARY_READ))
$result = $output . $data;
socket_close ($socket);
if (empty ($result))
return('Error'); //No output
$result = substr ($result, 4);
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 ($result, 0, 1)) > 1)
$info[parse_ase($result)]= parse_ase ($result);
$result =substr($result, 1);
$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($result, 1);
$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
-
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:
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);