4 # Copyright (c) 1993 - 2002 Tim Riker
6 # This package is free software; you can redistribute it and/or
7 # modify it under the terms of the license found in the file
8 # named LICENSE that should have accompanied this file.
10 # THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
11 # IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
12 # WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
16 use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
23 eval "use LWP::UserAgent";
31 &::status("BZFlag module requires Socket.");
32 return 'BZFlag module not active';
34 if ( $message =~ /^bzfquery\s+([^:]*)(?::([0-9]*))?$/xi ) {
35 $retval = &query( $1, $2 );
37 elsif ( $message =~ /^bzflist$/xi ) {
41 $retval = "BZFlag: unhandled command \"$message\"";
43 &::performStrictReply($retval);
48 my $ua = new LWP::UserAgent;
49 $ua->proxy( 'http', $::param{'httpProxy'} ) if ( &::IsParam('httpProxy') );
54 HTTP::Request->new( 'GET', 'http://my.bzflag.org/db/?action=LIST' );
55 my $res = $ua->request($req);
58 for my $line ( split( "\n", $res->content ) ) {
59 my ( $serverport, $version, $flags, $ip, $comments ) =
60 split( " ", $line, 5 );
62 # not "(A4)18" to handle old dumb perl
63 my ($style, $type, $maxShots, $shakeWins, $shakeTimeout, $maxPlayerScore, $maxTeamScore, $maxTime,
64 $maxPlayers, $rogueSize, $rogueMax, $redSize, $redMax, $greenSize, $greenMax,
65 $blueSize, $blueMax, $purpleSize, $purpleMax, $observerSize, $observerMax);
66 if (length($flags) == 54) {
67 ($style, $maxShots, $shakeWins, $shakeTimeout, $maxPlayerScore, $maxTeamScore, $maxTime,
68 $maxPlayers, $rogueSize, $rogueMax, $redSize, $redMax, $greenSize, $greenMax,
69 $blueSize, $blueMax, $purpleSize, $purpleMax, $observerSize, $observerMax) =
70 unpack("A4A4A4A4A4A4A4A2A2A2A2A2A2A2A2A2A2A2A2A2", $flags);
71 } elsif (length($flags) == 58) {
72 ($style, $type, $maxShots, $shakeWins, $shakeTimeout, $maxPlayerScore, $maxTeamScore, $maxTime,
73 $maxPlayers, $rogueSize, $rogueMax, $redSize, $redMax, $greenSize, $greenMax,
74 $blueSize, $blueMax, $purpleSize, $purpleMax, $observerSize, $observerMax) =
75 unpack("A4A4A4A4A4A4A4A4A2A2A2A2A2A2A2A2A2A2A2A2A2", $flags);
80 hex($rogueSize) + hex($redSize) + hex($greenSize) + hex($blueSize) +
81 hex($purpleSize) + hex($observerSize);
82 $servers{$serverport} = $playerSize;
83 $servers{$version} += $playerSize;
84 $servers{'PLAYERS'} += $playerSize;
87 $response .= "s=$totalServers";
89 my $key ( sort { $servers{$b} <=> $servers{$a} } ( keys(%servers) ) )
91 if ( $servers{$key} > 0 ) {
92 $response .= " $key($servers{$key})";
95 &::performStrictReply($response);
101 my $ua = new LWP::UserAgent;
102 $ua->proxy( 'http', $::param{'httpProxy'} ) if ( &::IsParam('httpProxy') );
106 my $req = HTTP::Request->new( 'GET', 'http://list.bzflag.org:5156/' );
107 my $res = $ua->request($req);
109 my $totalServers = 0;
110 my $totalPlayers = 0;
111 for my $line ( split( "\n", $res->content ) ) {
112 my ( $serverport, $version, $flags, $ip, $comments ) =
113 split( " ", $line, 5 );
115 # not "(A4)18" to handle old dumb perl
117 $style, $maxPlayers, $maxShots, $rogueSize,
118 $redSize, $greenSize, $blueSize, $purpleSize,
119 $rogueMax, $redMax, $greenMax, $blueMax,
120 $purpleMax, $shakeWins, $shakeTimeout, $maxPlayerScore,
121 $maxTeamScore, $maxTime
122 ) = unpack( 'A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4A4', $flags );
124 hex($rogueSize) + hex($redSize) + hex($greenSize) + hex($blueSize) +
126 $servers{$serverport} = $playerSize;
128 $totalPlayers += $playerSize;
130 $response .= "s=$totalServers p=$totalPlayers";
132 my $key ( sort { $servers{$b} <=> $servers{$a} } ( keys(%servers) ) )
134 if ( $servers{$key} > 0 ) {
135 $response .= " $key($servers{$key})";
138 &::performStrictReply($response);
143 my ($servernameport) = @_;
144 my ( $servername, $port ) = split( ":", $servernameport );
146 &::status("BZFlag module requires Socket.");
147 return 'BZFlag module not active';
150 #my @teamName = ('Rogue', 'Red', 'Green', 'Blue', 'Purple', 'Observer', 'Rabbit');
151 my @teamName = ( 'X', 'R', 'G', 'B', 'P', 'O', 'K' );
152 my ( $message, $server, $response );
153 $port = 5154 unless $port;
156 my $sockaddr = 'S n a4 x8';
158 # port to port number
159 my ( $name, $aliases, $proto ) = getprotobyname('tcp');
160 ( $name, $aliases, $port ) = getservbyname( $port, 'tcp' )
161 unless $port =~ /^\d+$/;
164 my ( $type, $len, $serveraddr );
165 ( $name, $aliases, $type, $len, $serveraddr ) = gethostbyname($servername);
166 $server = pack( $sockaddr, AF_INET, $port, $serveraddr );
169 # TODO wrap this with a 5 second alarm()
170 return 'socket() error' unless socket( S1, AF_INET, SOCK_STREAM, $proto );
171 return "could not connect to $servername:$port"
172 unless connect( S1, $server );
181 return 'read error' unless read( S1, $buffer, 8 ) == 8;
184 my ( $magic, $major, $minor, $something, $revision ) =
185 unpack( "a4 a1 a1 a1 a1", $buffer );
186 my ($version) = $magic . $major . $minor . $something . $revision;
188 # quit if version isn't valid
189 return 'not a bzflag server' if ( $magic ne 'BZFS' );
190 $response .= "$major$minor$something$revision ";
193 if ( $version eq 'BZFS0026' ) {
195 # 1.11.x handled here
196 return 'read error' unless read( S1, $buffer, 1 ) == 1;
197 my ($id) = unpack( 'C', $buffer );
198 return "rejected by server" if ( $id == 255 );
201 print S1 pack( 'n2', 0, 0x7167 );
204 my $nbytes = read( S1, $buffer, 4 );
205 my ( $infolen, $infocode ) = unpack( 'n2', $buffer );
206 if ( $infocode == 0x6774 ) {
208 # read and ignore MsgGameTime from new servers
209 $nbytes = read( S1, $buffer, 8 );
210 $nbytes = read( S1, $buffer, 4 );
211 ( $infolen, $infocode ) = unpack( 'n2', $buffer );
213 $nbytes = read( S1, $buffer, 42 );
214 if ( $nbytes != 42 ) {
215 return "Error: read $nbytes bytes, expecting 46: $^E\n";
219 $style, $maxPlayers, $maxShots, $rogueSize,
220 $redSize, $greenSize, $blueSize, $purpleSize,
221 $observerSize, $rogueMax, $redMax, $greenMax,
222 $blueMax, $purpleMax, $observerMax, $shakeWins,
223 $shakeTimeout, $maxPlayerScore, $maxTeamScore, $maxTime,
225 ) = unpack( 'n23', $buffer );
226 return "bad server data $infocode" unless $infocode == 0x7167;
228 # send players request
229 print S1 pack( 'n2', 0, 0x7170 );
231 # get number of teams and players we'll be receiving
232 return 'count read error' unless read( S1, $buffer, 8 ) == 8;
233 my ( $countlen, $countcode, $numTeams, $numPlayers ) =
234 unpack( 'n4', $buffer );
237 return 'bad count data' unless $countcode == 0x7170;
238 return 'count read error' unless read( S1, $buffer, 5 ) == 5;
239 ( $countlen, $countcode, $numTeams ) = unpack( "n n C", $buffer );
240 for ( 1 .. $numTeams ) {
241 return 'team read error' unless read( S1, $buffer, 8 ) == 8;
242 my ( $team, $size, $won, $lost ) = unpack( 'n4', $buffer );
244 my $score = $won - $lost;
245 $response .= "$teamName[$team]:$score($won-$lost) ";
250 for ( 1 .. $numPlayers ) {
251 last unless read( S1, $buffer, 175 ) == 175;
253 $playerlen, $playercode, $pID, $type, $team,
254 $won, $lost, $tks, $sign, $email
255 ) = unpack( 'n2Cn5A32A128', $buffer );
257 #my ($playerlen,$playercode,$pAddr,$pPort,$pNum,$type,$team,$won,$lost,$sign,$email) =
258 # unpack("n2Nn2 n4A32A128", $buffer);
259 return 'bad player data' unless $playercode == 0x6170;
260 my $score = $won - $lost;
261 $response .= " $sign($teamName[$team]";
262 $response .= ":$email" if ($email);
263 $response .= ")$score($won-$lost)";
265 $response .= "No Players" if ( $numPlayers < 1 );
269 elsif ( $major == 1 && $minor == 9 ) {
271 # 1.10.x handled here
272 $revision = $something * 10 + $revision;
273 return 'read error' unless read( S1, $buffer, 1 ) == 1;
274 my ($id) = unpack( 'C', $buffer );
277 print S1 pack( 'n2', 0, 0x7167 );
279 # FIXME the packets are wrong from here down
281 return 'server read error' unless read( S1, $buffer, 40 ) == 40;
283 $infolen, $infocode, $style, $maxPlayers,
284 $maxShots, $rogueSize, $redSize, $greenSize,
285 $blueSize, $purpleSize, $rogueMax, $redMax,
286 $greenMax, $blueMax, $purpleMax, $shakeWins,
287 $shakeTimeout, $maxPlayerScore, $maxTeamScore, $maxTime
288 ) = unpack( 'n20', $buffer );
289 return 'bad server data' unless $infocode == 0x7167;
291 # send players request
292 print S1 pack( 'n2', 0, 0x7170 );
294 # get number of teams and players we'll be receiving
295 return 'count read error' unless read( S1, $buffer, 8 ) == 8;
296 my ( $countlen, $countcode, $numTeams, $numPlayers ) =
297 unpack( 'n4', $buffer );
300 return 'bad count data' unless $countcode == 0x7170;
301 return 'count read error' unless read( S1, $buffer, 5 ) == 5;
302 ( $countlen, $countcode, $numTeams ) = unpack( "n n C", $buffer );
303 for ( 1 .. $numTeams ) {
304 return 'team read error' unless read( S1, $buffer, 8 ) == 8;
305 my ( $team, $size, $won, $lost ) = unpack( 'n4', $buffer );
307 my $score = $won - $lost;
308 $response .= "$teamName[$team]:$score($won-$lost) ";
313 for ( 1 .. $numPlayers ) {
314 last unless read( S1, $buffer, 175 ) == 175;
316 $playerlen, $playercode, $pID, $type, $team,
317 $won, $lost, $tks, $sign, $email
318 ) = unpack( 'n2Cn5A32A128', $buffer );
320 #my ($playerlen,$playercode,$pAddr,$pPort,$pNum,$type,$team,$won,$lost,$sign,$email) =
321 # unpack("n2Nn2 n4A32A128", $buffer);
322 return 'bad player data' unless $playercode == 0x6170;
323 my $score = $won - $lost;
324 $response .= " $sign($teamName[$team]";
325 $response .= ":$email" if ($email);
326 $response .= ")$score($won-$lost)";
328 $response .= "No Players" if ( $numPlayers < 1 );
333 elsif ( $major == 1 && $minor == 0 && $something == 7 ) {
335 # 1.7* versions handled here
336 # old servers send a reconnect port number
337 return 'read error' unless read( S1, $buffer, 2 ) == 2;
338 my ($reconnect) = unpack( 'n', $buffer );
339 $minor = $minor * 10 + $something;
342 return 'rejected by server' if ( $reconnect == 0 );
344 # reconnect on new port
345 $server = pack( $sockaddr, AF_INET, $reconnect, $serveraddr );
346 return 'socket() error on reconnect'
347 unless socket( S, AF_INET, SOCK_STREAM, $proto );
348 return "could not reconnect to $servername:$reconnect"
349 unless connect( S, $server );
358 print S pack( 'n2', 0, 0x7167 );
361 return 'server read error' unless read( S, $buffer, 40 ) == 40;
363 $infolen, $infocode, $style, $maxPlayers,
364 $maxShots, $rogueSize, $redSize, $greenSize,
365 $blueSize, $purpleSize, $rogueMax, $redMax,
366 $greenMax, $blueMax, $purpleMax, $shakeWins,
367 $shakeTimeout, $maxPlayerScore, $maxTeamScore, $maxTime
368 ) = unpack( 'n20', $buffer );
369 return 'bad server data' unless $infocode == 0x7167;
371 # send players request
372 print S pack( 'n2', 0, 0x7170 );
374 # get number of teams and players we'll be receiving
375 return 'count read error' unless read( S, $buffer, 8 ) == 8;
376 my ( $countlen, $countcode, $numTeams, $numPlayers ) =
377 unpack( 'n4', $buffer );
378 return 'bad count data' unless $countcode == 0x7170;
381 for ( 1 .. $numTeams ) {
382 return 'team read error' unless read( S, $buffer, 14 ) == 14;
383 my ( $teamlen, $teamcode, $team, $size, $aSize, $won, $lost ) =
384 unpack( 'n7', $buffer );
385 return 'bad team data' unless $teamcode == 0x7475;
387 my $score = $won - $lost;
388 $response .= "$teamName[$team]:$score($won-$lost) ";
393 for ( 1 .. $numPlayers ) {
394 last unless read( S, $buffer, 180 ) == 180;
396 $playerlen, $playercode, $pAddr, $pPort,
397 $pNum, $type, $team, $won,
399 ) = unpack( "n2Nn2 n4A32A128", $buffer );
400 return 'bad player data' unless $playercode == 0x6170;
401 my $score = $won - $lost;
402 $response .= " $sign($teamName[$team]";
403 $response .= ":$email" if ($email);
404 $response .= ")$score($won-$lost)";
406 $response .= "No Players" if ( $numPlayers <= 1 );
412 $response = "incompatible version: $version";
419 my ($servernameport) = @_;
420 &::performStrictReply( &querytext($servernameport) );
426 # vim:ts=4:sw=4:expandtab:tw=80