aboutsummaryrefslogtreecommitdiff
path: root/lib/MasterServer
diff options
context:
space:
mode:
Diffstat (limited to 'lib/MasterServer')
-rwxr-xr-xlib/MasterServer/Core/Secure.pm40
-rwxr-xr-xlib/MasterServer/Core/Version.pm4
-rwxr-xr-xlib/MasterServer/Database/Pg/dbCiphers.pm54
-rwxr-xr-xlib/MasterServer/Database/Pg/dbGetServers.pm16
-rwxr-xr-xlib/MasterServer/Database/SQLite/dbCiphers.pm54
-rwxr-xr-xlib/MasterServer/Database/SQLite/dbGetServers.pm16
-rwxr-xr-xlib/MasterServer/TCP/Handler.pm72
-rwxr-xr-xlib/MasterServer/TCP/ListCompiler.pm2
-rwxr-xr-xlib/MasterServer/UDP/BeaconCatcher.pm9
-rwxr-xr-xlib/MasterServer/UDP/DatagramProcessor.pm8
-rwxr-xr-xlib/MasterServer/UDP/UpLink.pm140
11 files changed, 215 insertions, 200 deletions
diff --git a/lib/MasterServer/Core/Secure.pm b/lib/MasterServer/Core/Secure.pm
index 6d05f82..125e276 100755
--- a/lib/MasterServer/Core/Secure.pm
+++ b/lib/MasterServer/Core/Secure.pm
@@ -39,7 +39,6 @@ sub load_ciphers {
# insert the game/cipher in the db or halt on error
if ($self->insert_cipher(%opt) < 0) {
- # failure causes a fatal error and exits
$self->{dbh}->rollback;
$self->halt();
}
@@ -52,15 +51,12 @@ sub load_ciphers {
################################################################################
# generate a random string of 6 characters long for the \secure\ challenge
-# returns string
+# returns a random string, only uppercase characters
################################################################################
sub secure_string {
- # generate a random string, only uppercase characters
my @c = ('A'..'Z');
my $s = "";
$s .= $c[rand @c] for 1..6;
-
- # return random string
return $s;
}
@@ -73,28 +69,22 @@ sub compare_challenge {
# debugging enabled? Then don't care about validation
return 1 if ($self->{debug_validate});
-
- # secure string too long? (because vulnerable in UE)
- return 0 if (length $o{secure} > 16);
-
+
# ignore this game if asked to do so
if ($self->{ignore_browser_key} =~ m/$o{gamename}/i){
$self->log("ignore", "ignored beacon validation for $o{gamename}");
return 1;
}
- # enctype given?
- $o{enctype} = 0 unless $o{enctype};
-
# calculate validate string
my $val = get_validate_string(
- $self->get_game_props($o{gamename})->{cipher},
+ $self->get_game_props(gamename => $o{gamename})->[0]->{cipher},
$o{secure},
- $o{enctype}
+ $o{enctype} || 0
);
- # return whether or not they match
- return ($val eq $o{validate});
+ # return match or no match
+ return ($val eq ($o{validate} || ""));
}
################################################################################
@@ -103,17 +93,15 @@ sub compare_challenge {
sub validate_string {
my ($self, %o) = @_;
- # secure string too long? (because vulnerable in UE)
- return 0 if (length $o{secure} > 16);
-
- # get cipher from gamename
- my $cip = $self->get_game_props(lc $o{gamename})->{cipher};
-
- # enctype given?
- $o{enctype} = 0 unless $o{enctype};
-
+ # secure string too long? discard as hack.
+ return 0 if (length $o{secure} > 6);
+
# calculate and return validate string
- return get_validate_string($cip, $o{secure}, $o{enctype});
+ return get_validate_string(
+ $self->get_game_props(gamename => $o{gamename})->[0]->{cipher},
+ $o{secure},
+ $o{enctype} || 0
+ );
}
################################################################################
diff --git a/lib/MasterServer/Core/Version.pm b/lib/MasterServer/Core/Version.pm
index f87ea8d..4a49392 100755
--- a/lib/MasterServer/Core/Version.pm
+++ b/lib/MasterServer/Core/Version.pm
@@ -29,13 +29,13 @@ sub version {
$self->{build_type} = "333networks Masterserver-Perl Multidb";
# version
- $self->{build_version} = "2.3.0";
+ $self->{build_version} = "2.3.1";
# short version for uplinks
$self->{short_version} = "MS-perl $self->{build_version}";
# date yyyy-mm-dd
- $self->{build_date} = "2017-05-13";
+ $self->{build_date} = "2017-07-06";
#author, email
$self->{build_author} = "Darkelarious, darkelarious\@333networks.com";
diff --git a/lib/MasterServer/Database/Pg/dbCiphers.pm b/lib/MasterServer/Database/Pg/dbCiphers.pm
index e099f88..5343065 100755
--- a/lib/MasterServer/Database/Pg/dbCiphers.pm
+++ b/lib/MasterServer/Database/Pg/dbCiphers.pm
@@ -7,7 +7,8 @@ use Exporter 'import';
our @EXPORT = qw| check_cipher_count
clear_ciphers
insert_cipher
- get_game_props |;
+ get_game_props
+ get_gamenames |;
################################################################################
## Check if ciphers exist
@@ -51,13 +52,56 @@ sub insert_cipher {
################################################################################
## get the cipher, description and default port that goes with given gamename
-## returns only the first item if multiple items
+##
################################################################################
sub get_game_props {
- my ($self, $gn) = @_;
+ my $s = shift;
+ my %o = (
+ sort => '',
+ @_
+ );
- # get cipher from db if gamename exists
- return $self->db_all('SELECT * FROM games WHERE gamename = ?', lc $gn)->[0];
+ my %where = (
+ $o{gamename} ? ('gamename = ?' => lc $o{gamename}) : (),
+ $o{cipher} ? ('cipher = ?' => $o{cipher}) : (),
+ $o{description} ? ('description = ?' => $o{description}) : (),
+ $o{default_qport} ? ('default_qport = ?' => $o{default_qport}) : (),
+ $o{num_uplink} ? ('num_uplink = ?' => $o{num_uplink}) : (),
+ $o{num_total} ? ('num_total = ?' => $o{num_total}) : (),
+ $o{num_gt} ? ('num_total >= ?' => $o{num_gt}) : (),
+ );
+
+ my @select = ( qw| gamename cipher description default_qport num_uplink num_total|,);
+ my $order = sprintf {
+ gamename => 'gamename %s',
+ cipher => 'cipher %s',
+ description => 'description %s',
+ default_qport => 'default_qport %s',
+ num_uplink => 'num_uplink %s',
+ num_total => 'num_total %s',
+ }->{ $o{sort}||'gamename' }, $o{reverse} ? 'DESC' : 'ASC';
+
+ return $s->db_all( q|
+ SELECT !s FROM games
+ !W
+ ORDER BY !s|
+ .($o{limit} ? " LIMIT ?" : ""),
+ join(', ', @select), \%where, $order, ($o{limit} ? $o{limit} : ()),
+ );
+}
+
+
+################################################################################
+## get a list of distinct gamenames currently in the database. it does not
+## matter whether they are recent or old, as long as the game is currently in
+## the database.
+################################################################################
+sub get_gamenames {
+ my $self = shift;
+
+ return $self->{dbh}->selectall_arrayref(
+ "SELECT distinct gamename
+ FROM serverlist");
}
1;
diff --git a/lib/MasterServer/Database/Pg/dbGetServers.pm b/lib/MasterServer/Database/Pg/dbGetServers.pm
index a33582a..e9bfaec 100755
--- a/lib/MasterServer/Database/Pg/dbGetServers.pm
+++ b/lib/MasterServer/Database/Pg/dbGetServers.pm
@@ -5,8 +5,7 @@ use warnings;
use Exporter 'import';
our @EXPORT = qw| get_server
- get_pending
- get_gamenames |;
+ get_pending |;
################################################################################
## get server details for one or multiple servers
@@ -123,17 +122,4 @@ sub get_pending {
);
}
-################################################################################
-## get a list of distinct gamenames currently in the database. it does not
-## matter whether they are recent or old, as long as the game is currently in
-## the database.
-################################################################################
-sub get_gamenames {
- my $self = shift;
-
- return $self->{dbh}->selectall_arrayref(
- "SELECT distinct gamename
- FROM serverlist");
-}
-
1;
diff --git a/lib/MasterServer/Database/SQLite/dbCiphers.pm b/lib/MasterServer/Database/SQLite/dbCiphers.pm
index 5b88944..f257b7b 100755
--- a/lib/MasterServer/Database/SQLite/dbCiphers.pm
+++ b/lib/MasterServer/Database/SQLite/dbCiphers.pm
@@ -7,7 +7,8 @@ use Exporter 'import';
our @EXPORT = qw| check_cipher_count
clear_ciphers
insert_cipher
- get_game_props |;
+ get_game_props
+ get_gamenames |;
################################################################################
## Check if ciphers exist
@@ -51,13 +52,56 @@ sub insert_cipher {
################################################################################
## get the cipher, description and default port that goes with given gamename
-## returns only the first item if multiple items
+##
################################################################################
sub get_game_props {
- my ($self, $gn) = @_;
+ my $s = shift;
+ my %o = (
+ sort => '',
+ @_
+ );
- # get cipher from db if gamename exists
- return $self->db_all('SELECT * FROM games WHERE gamename = ?', lc $gn)->[0];
+ my %where = (
+ $o{gamename} ? ('gamename = ?' => lc $o{gamename}) : (),
+ $o{cipher} ? ('cipher = ?' => $o{cipher}) : (),
+ $o{description} ? ('description = ?' => $o{description}) : (),
+ $o{default_qport} ? ('default_qport = ?' => $o{default_qport}) : (),
+ $o{num_uplink} ? ('num_uplink = ?' => $o{num_uplink}) : (),
+ $o{num_total} ? ('num_total = ?' => $o{num_total}) : (),
+ $o{num_gt} ? ('num_total >= ?' => $o{num_gt}) : (),
+ );
+
+ my @select = ( qw| gamename cipher description default_qport num_uplink num_total|,);
+ my $order = sprintf {
+ gamename => 'gamename %s',
+ cipher => 'cipher %s',
+ description => 'description %s',
+ default_qport => 'default_qport %s',
+ num_uplink => 'num_uplink %s',
+ num_total => 'num_total %s',
+ }->{ $o{sort}||'gamename' }, $o{reverse} ? 'DESC' : 'ASC';
+
+ return $s->db_all( q|
+ SELECT !s FROM games
+ !W
+ ORDER BY !s|
+ .($o{limit} ? " LIMIT ?" : ""),
+ join(', ', @select), \%where, $order, ($o{limit} ? $o{limit} : ()),
+ );
+}
+
+
+################################################################################
+## get a list of distinct gamenames currently in the database. it does not
+## matter whether they are recent or old, as long as the game is currently in
+## the database.
+################################################################################
+sub get_gamenames {
+ my $self = shift;
+
+ return $self->{dbh}->selectall_arrayref(
+ "SELECT distinct gamename
+ FROM serverlist");
}
1;
diff --git a/lib/MasterServer/Database/SQLite/dbGetServers.pm b/lib/MasterServer/Database/SQLite/dbGetServers.pm
index 1884601..719e00a 100755
--- a/lib/MasterServer/Database/SQLite/dbGetServers.pm
+++ b/lib/MasterServer/Database/SQLite/dbGetServers.pm
@@ -5,8 +5,7 @@ use warnings;
use Exporter 'import';
our @EXPORT = qw| get_server
- get_pending
- get_gamenames |;
+ get_pending |;
################################################################################
## get server details for one or multiple servers
@@ -123,17 +122,4 @@ sub get_pending {
);
}
-################################################################################
-## get a list of distinct gamenames currently in the database. it does not
-## matter whether they are recent or old, as long as the game is currently in
-## the database.
-################################################################################
-sub get_gamenames {
- my $self = shift;
-
- return $self->{dbh}->selectall_arrayref(
- "SELECT distinct gamename
- FROM serverlist");
-}
-
1;
diff --git a/lib/MasterServer/TCP/Handler.pm b/lib/MasterServer/TCP/Handler.pm
index 4e174c2..eb0094d 100755
--- a/lib/MasterServer/TCP/Handler.pm
+++ b/lib/MasterServer/TCP/Handler.pm
@@ -8,7 +8,6 @@ use Exporter 'import';
our @EXPORT = qw| read_tcp_handle
handle_validate
- handle_about
handle_list
handle_sync |;
@@ -45,9 +44,6 @@ sub read_tcp_handle {
# part 2: receive \gamename\ut\location\0\validate\$validate\final\
$val = $self->handle_validate(\%r, $h, $secure, $a, $p)
if (exists $r{validate} && !$val);
-
- # about query
- $response .= $self->handle_about($r{about}, $a, $p) if (exists $r{about});
# return address list
# part 3: wait for the requested action: \list\\gamename\ut\
@@ -65,7 +61,7 @@ sub read_tcp_handle {
# improper syntax/protocol -- no valid commands found
# respond with an error.
# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
- if ("about sync validate list" =~ m/\Q$response\E/i) {
+ if ("sync validate list" =~ m/\Q$response\E/i) {
# error message to client
$c->push_write("\\echo\\333networks did not understand your request. ".
@@ -91,7 +87,7 @@ sub handle_validate {
my $val = 0;
# pass or fail the secure challenge
- if (exists $r->{gamename} && $self->get_game_props($r->{gamename})) {
+ if (exists $r->{gamename} && $self->get_game_props(gamename => $r->{gamename})) {
# game exists and we have the key to verify the response
$val = $self->compare_challenge(
@@ -117,73 +113,9 @@ sub handle_validate {
return $val;
}
-
-################################################################################
-## \about\ query.
-## Can contain the values "contact", "build", "Address" or "support", where
-## - contact: return contact information; see config.pl
-## - build: build info, also version.pm
-## - address: address and ports
-## - support: return a list of games currently in the database
-## - undef: all of the above
-##
-## NOTE: client does not need to validate to be allowed to perform this
-## query.
-## TODO: deprecate about query -- info will now be in udp query!
-################################################################################
-sub handle_about {
- my ($self, $about, $a, $p) = @_;
- my $response = "";
-
- # contact info
- if ($about =~ /^contact$/i or $about =~ /^undef$/i) {
- $response .= "\\about\\$self->{masterserver_hostname}, contact: $self->{masterserver_contact}";
- $self->log("about","communicating to $a:$p my contact information.");
- }
-
- # build/version info
- if ($about =~ /^build$/i or $about =~ /^version$/i or $about =~ /^undef$/i) {
- $response .= "\\build\\$self->{build_type} $self->{build_version} written "
- . "by $self->{build_author}, released $self->{build_date}";
- $self->log("about","telling $a:$p my build info.");
- }
-
- # address info
- if ($about =~ /^address$/i or $about =~ /^undef$/i) {
- $response .= "\\address\\$self->{masterserver_address}"
- . "\\listen_port\\$self->{listen_port}"
- . "\\beacon_port\\$self->{beacon_port}";
- $self->log("about","telling $a:$p my address/config info.");
- }
-
- # support info
- if ($about =~ /^support$/i or $about =~ /^undef$/i) {
- # string games in database
- my $sg = $self->get_gamenames();
- my $sgs = "";
- for (@{$sg}) {
- $sgs .= " " if (length $sgs > 0);
- $sgs .= $_->[0];
- }
- $response .= "\\support\\$sgs";
- $self->log("about","telling $a:$p which games are supported.");
- }
-
- # unsupported query
- if ("contact build address support version undef" !~ m/$about/i) {
- $response .= "\\echo\\incorrect query usage, supported queries are: contact build version address support.";
- $self->log("about","incorrect query \"$about\", telling $a:$p the supported \"about\" queries.");
- }
-
- # return response string
- return $response;
-}
-
################################################################################
## At this point, the client should be validated and ready to request with
## the \secure\ command and is allowed to ask for the list.
-## The \list\ cmd is NOT compatible with combined queries
-## (like \about\contact\list\)
################################################################################
sub handle_list {
my ($self, $val, $r, $c, $a, $p) = @_;
diff --git a/lib/MasterServer/TCP/ListCompiler.pm b/lib/MasterServer/TCP/ListCompiler.pm
index 97ef541..c035008 100755
--- a/lib/MasterServer/TCP/ListCompiler.pm
+++ b/lib/MasterServer/TCP/ListCompiler.pm
@@ -98,7 +98,7 @@ sub compile_sync {
for my $g (keys %games) {
# $g is now a gamename -- check if it's supported. Else ignore.
- if ($self->get_game_props($g)) {
+ if ($self->get_game_props(gamename => $g)) {
# get list from database
my $list = $self->get_server(
diff --git a/lib/MasterServer/UDP/BeaconCatcher.pm b/lib/MasterServer/UDP/BeaconCatcher.pm
index 5c4ff5f..7c98f57 100755
--- a/lib/MasterServer/UDP/BeaconCatcher.pm
+++ b/lib/MasterServer/UDP/BeaconCatcher.pm
@@ -65,8 +65,13 @@ sub on_beacon_receive {
if ($b =~ m/\\heartbeat\\/ && $b =~ m/\\gamename\\/);
# if other masterservers check if we're still alive
- $self->process_udp_secure($udp, $pa, $b, $peer_addr)
- if ($b =~ m/\\secure\\/ || $b =~ m/\\basic\\/ || $b =~ m/\\status\\/ || $b =~ m/\\info\\/);
+ $self->handle_status_query($udp, $pa, $b, $peer_addr)
+ if ($b =~ m/\\secure\\/ ||
+ $b =~ m/\\basic\\/ ||
+ $b =~ m/\\info\\/ ||
+ $b =~ m/\\rules\\/ ||
+ $b =~ m/\\players\\/||
+ $b =~ m/\\status\\/);
}
1;
diff --git a/lib/MasterServer/UDP/DatagramProcessor.pm b/lib/MasterServer/UDP/DatagramProcessor.pm
index 2a26763..006871d 100755
--- a/lib/MasterServer/UDP/DatagramProcessor.pm
+++ b/lib/MasterServer/UDP/DatagramProcessor.pm
@@ -34,13 +34,13 @@ sub process_udp_beacon {
$self->log("beacon", "$peer_addr:$r{heartbeat} for $r{gamename}");
# check if game is actually supported in our db
- my $game_props = $self->get_game_props($r{gamename});
+ my $game_props = $self->get_game_props(gamename => $r{gamename})->[0];
# if no entry exists, report error.
if (defined $game_props) {
# validate heartbeat data
- my $heartbeat = ($r{heartbeat} || $game_props->{default_qport});
+ my $heartbeat = ($r{heartbeat} || ($game_props->{default_qport} || 0));
#
# verify valid server address (ip+port)
@@ -140,7 +140,7 @@ sub process_udp_validate {
# verify challenge
my $val = $self->compare_challenge(
- gamename => $pending->{gamename},
+ gamename => lc $pending->{gamename},
secure => $pending->{secure},
enctype => $r{enctype},
validate => $r{validate},
@@ -221,7 +221,7 @@ sub process_query_response {
# check whether the gamename is supported in our db
- if (exists $s{gamename} && $self->get_game_props($s{gamename})) {
+ if (exists $s{gamename} && $self->get_game_props(gamename => $s{gamename})) {
# parse variables
my %nfo = ();
diff --git a/lib/MasterServer/UDP/UpLink.pm b/lib/MasterServer/UDP/UpLink.pm
index 63cbefb..01e806e 100755
--- a/lib/MasterServer/UDP/UpLink.pm
+++ b/lib/MasterServer/UDP/UpLink.pm
@@ -9,8 +9,11 @@ use Exporter 'import';
our @EXPORT = qw| send_heartbeats
do_uplink
- process_uplink_response
- process_udp_secure |;
+ process_udp_secure
+ handle_status_query |;
+
+# for compliance, query ID
+my $query_id = 0;
################################################################################
## Broadcast heartbeats to other masterservers
@@ -57,7 +60,11 @@ sub do_uplink {
timeout => $self->{timeout_time},
on_timeout => sub {$udp_client->destroy()},
on_error => sub {$udp_client->destroy()},
- on_recv => sub {$self->process_uplink_response(@_)},
+ on_recv => sub {
+ my ($self, $buf, $udp, $pa) = @_;
+ $self->handle_status_query($udp, $pa, $buf)
+ if ($buf =~ m/secure/);
+ },
);
# Send heardbeat
@@ -65,78 +72,101 @@ sub do_uplink {
}
################################################################################
-## Process requests received after uplinking
-##
+## Respond to status-like queries. Supported queries are basic, info, rules,
+## players, status.
+## Note: this replaces the \about\ query in the TCP handler!
################################################################################
-sub process_uplink_response {
- # $self, beacon address, handle, packed client address
- my ($self, $b, $udp, $pa) = @_;
-
- # unpack ip from packed client address
- my ($port, $iaddr) = sockaddr_in($pa);
- my $peer_addr = inet_ntoa($iaddr);
-
- # assume fraud/crash attempt if response too long
- if (length $b > 64) {
- # log
- $self->log("attack","length exceeded in uplink response: $peer_addr:$port sent $b");
-
- # truncate and try to continue
- $b = substr $b, 0, 64;
- }
-
- # check if this is a secure challenge
- $self->process_udp_secure($udp, $pa, $b, $peer_addr)
- if ($b =~ m/\\secure\\/);
-}
+sub handle_status_query {
+ my ($self, $udp, $pa, $buf) = @_;
-
-################################################################################
-## Process the received secure query and respond with the correct response
-## TODO: expand queries with support for info, rules, players, status, etc
-################################################################################
-sub process_udp_secure {
- # $self, handle, packed address, udp data, peer ip address, $port
- my ($self, $udp, $pa, $buf, $peer_addr) = @_;
-
- # received secure in $buf: \basic\\secure\wookie
+ # hotfix for one-word queries
+ $buf .= "\\dummy\\";
my %r;
-
+
$buf = encode('UTF-8', $buf);
- $buf =~ s/\\\\/\\undef\\/;
$buf =~ s/\n//;
+ $buf =~ s/\\\\/\\undef\\/g; # where to add the +? seperate perl script!
$buf =~ s/\\([^\\]+)\\([^\\]+)/$r{$1}=$2/eg;
# response string
my $response = "";
- # compile basic string
+ # for compliance, query ids between 0-99
+ $query_id = ($query_id >= 99) ? 1 : ++$query_id;
+ my $sub_id = 1;
- # provide basic information if asked for
- if (defined $r{basic} || defined $r{status} || defined $r{info}) {
+ # get database info to present game stats as players, where num_total > 0
+ my $maxgames = $self->check_cipher_count();
+ my $gameinfo = $self->get_game_props(
+ num_gt => 1,
+ sort => "num_total",
+ reverse => 1
+ );
- # format: \gamename\ut\gamever\348\minnetver\348\location\0\final\\queryid\16.1
+ # secure challenge
+ if (defined $r{secure}) {
+ $response .= "\\validate\\"
+ . $self->validate_string(
+ gamename => "333networks",
+ enctype => 0,
+ secure => $r{secure}
+ );
+ }
+
+ # basic query
+ if (defined $r{basic} || defined $r{status}) {
$response .= "\\gamename\\333networks"
. "\\gamever\\$self->{short_version}"
. "\\location\\0"
- . "\\hostname\\$self->{masterserver_hostname}"
- . "\\hostport\\$self->{listen_port}";
+ . "\\queryid\\$query_id.".$sub_id++;
+ }
+
+ # info query
+ if (defined $r{info} || defined $r{status}) {
+ $response .= "\\hostname\\$self->{masterserver_hostname}"
+ . "\\hostport\\$self->{listen_port}"
+ . "\\gametype\\MasterServer"
+ . "\\numplayers\\". scalar @{$gameinfo}
+ . "\\maxplayers\\$maxgames"
+ . "\\gamemode\\openplaying"
+ . "\\queryid\\$query_id.".$sub_id++;
+ }
+
+ # rules query
+ if (defined $r{rules} || defined $r{status}) {
+ $response .= "\\mutators\\333networks synchronization, master applet synchronization"
+ . "\\AdminName\\$self->{masterserver_name}"
+ . "\\AdminEMail\\$self->{masterserver_contact}"
+ . "\\queryid\\$query_id.".$sub_id++;
}
- # TODO: add queryid -- not because it's useful, but because protocol compliant
+ # players query
+ if (defined $r{players} || defined $r{status}) {
+ # list game stats as if they were players, with game description as
+ # player_$, gamename as skin_$, total servers as frags_$ and number of
+ # direct uplinks as deaths_$
+ my $c = 0;
- # support for secure/validate
- if (defined $r{secure}) {
- # generate response
-
- $response .= "\\validate\\"
- . $self->validate_string(gamename => "333networks",
- enctype => 0,
- secure => $r{secure});
+ foreach my $p (@{$gameinfo}) {
+ $c++; # count players
+ $response .= "\\player_$c\\$p->{description}"
+ . "\\skin_$c\\$p->{gamename}"
+ . "\\frags_$c\\$p->{num_total}"
+ . "\\deaths_$c\\$p->{num_uplink}";
+ }
+ $response .= "\\queryid\\$query_id.".$sub_id++;
}
- # send the response
- $udp->push_send("$response\\final\\", $pa);
+ # close query with final tag
+ $response .= "\\final\\";
+
+ # split the response in chunks of 512 bytes and send
+ while (length $response > 512) {
+ my $chunk = substr $response, 0, 512, '';
+ $udp->push_send($chunk, $pa);
+ }
+ # last <512 chunk
+ $udp->push_send($response, $pa);
}
1;