3 # Copyright (c) 2008 Rudolf "divVerent" Polzer
5 # Permission is hereby granted, free of charge, to any person
6 # obtaining a copy of this software and associated documentation
7 # files (the "Software"), to deal in the Software without
8 # restriction, including without limitation the rights to use,
9 # copy, modify, merge, publish, distribute, sublicense, and/or sell
10 # copies of the Software, and to permit persons to whom the
11 # Software is furnished to do so, subject to the following
14 # The above copyright notice and this permission notice shall be
15 # included in all copies or substantial portions of the Software.
17 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19 # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21 # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24 # OTHER DEALINGS IN THE SOFTWARE.
27 # parts copied from rcon2irc
29 # convert mIRC color codes to DP color codes
30 our @color_irc2dp_table = (7, 0, 4, 2, 1, 1, 6, 1, 3, 2, 5, 5, 4, 6, 7, 7);
31 our @color_dp2irc_table = (14, 4, 9, 8, 12, 11, 13, 14, 15, 15); # not accurate, but legible
32 our @color_dp2ansi_table = ("m", "1;31m", "1;32m", "1;33m", "1;34m", "1;36m", "1;35m", "m", "1m", "1m"); # not accurate, but legible
33 our %color_team2dp_table = (5 => 1, 14 => 4, 13 => 3, 10 => 6);
34 our %color_team2irc_table = (5 => 4, 14 => 12, 13 => 8, 10 => 13);
38 $message =~ s/\^/^^/g;
40 $message =~ s{\003(\d\d?)(?:,(\d?\d?))?|(\017)}{
41 # $1 is FG, $2 is BG, but let's ignore BG
42 my $oldcolor = $color;
49 $color = $color_irc2dp_table[$1];
50 $color = $oldcolor if not defined $color;
52 ($color == $oldcolor) ? '' : '^' . $color;
54 $message =~ s{[\000-\037]}{}gs; # kill bold etc. for now
58 our @text_qfont_table = ( # ripped from DP console.c qfont_table
59 "\0", '#', '#', '#', '#', '.', '#', '#',
60 '#', 9, 10, '#', ' ', 13, '.', '.',
61 '[', ']', '0', '1', '2', '3', '4', '5',
62 '6', '7', '8', '9', '.', '<', '=', '>',
63 ' ', '!', '"', '#', '$', '%', '&', '\'',
64 '(', ')', '*', '+', ',', '-', '.', '/',
65 '0', '1', '2', '3', '4', '5', '6', '7',
66 '8', '9', ':', ';', '<', '=', '>', '?',
67 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
68 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
69 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
70 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
71 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
72 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
73 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
74 'x', 'y', 'z', '{', '|', '}', '~', '<',
75 '<', '=', '>', '#', '#', '.', '#', '#',
76 '#', '#', ' ', '#', ' ', '>', '.', '.',
77 '[', ']', '0', '1', '2', '3', '4', '5',
78 '6', '7', '8', '9', '.', '<', '=', '>',
79 ' ', '!', '"', '#', '$', '%', '&', '\'',
80 '(', ')', '*', '+', ',', '-', '.', '/',
81 '0', '1', '2', '3', '4', '5', '6', '7',
82 '8', '9', ':', ';', '<', '=', '>', '?',
83 '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G',
84 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
85 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
86 'X', 'Y', 'Z', '[', '\\', ']', '^', '_',
87 '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
88 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
89 'p', 'q', 'r', 's', 't', 'u', 'v', 'w',
90 'x', 'y', 'z', '{', '|', '}', '~', '<'
95 $message = join '', map { $text_qfont_table[ord $_] } split //, $message;
102 $message =~ s{\^(.)(?=([0-9,]?))}{
105 $c =~ /^[0-9]$/ ? '' : "^$c";
107 return text_dp2ascii $message;
114 $message =~ s{\^(.)(?=([0-9,]?))}{
118 $c =~ /^[0-9]$/ ? do {
119 my $oldcolor = $color;
120 $c = 0 if $c >= 7; # map 0, 7, 8, 9 to default (no bright white or such stuff)
121 $color = $color_dp2irc_table[$c];
122 ($color == $oldcolor) ? '' :
124 $f eq ',' ? "\0003$color\0002\0002" :
125 $f ne '' ? sprintf "\0003%02d", $color : "\0003$color";
128 $message = text_dp2ascii $message;
129 $message =~ s/\0001/\017/g;
130 $message =~ s/\0002/\002/g;
131 $message =~ s/\0003/\003/g;
139 $message =~ s{\^(.)}{
142 $c =~ /^[0-9]$/ ? do {
143 my $oldcolor = $color;
144 $color = $color_dp2ansi_table[$c];
145 ($color eq $oldcolor) ? '' :
149 $message = text_dp2ascii $message;
150 $message =~ s/\000/\033/g;
157 # if the message ends with an odd number of ^, kill one
158 chop $message if $message =~ /(?:^|[^\^])\^(\^\^)*$/;
166 # $conn->sockname() returns a connection type specific representation
167 # string of the local address, or undef if not applicable.
168 # $conn->send("string") sends something over the connection.
169 # $conn->recv() receives a string from the connection, or returns "" if no
171 # $conn->fds() returns all file descriptors used by the connection, so one
172 # can use select() on them.
174 # Usually wraps around a connection and implements a command based
175 # structure over it. It usually is constructed using new
176 # ChannelType($connection, someparameters...)
177 # @cmds = $chan->join_commands(@cmds) joins multiple commands to a single
178 # command string if the protocol supports it, or does nothing and leaves
179 # @cmds unchanged if the protocol does not support that usage (this is
180 # meant to save send() invocations).
181 # $chan->send($command, $nothrottle) sends a command over the channel. If
182 # $nothrottle is sent, the command must not be left out even if the channel
183 # is saturated (for example, because of IRC's flood control mechanism).
184 # $chan->quote($str) returns a string in a quoted form so it can safely be
185 # inserted as a substring into a command, or returns $str as is if not
186 # applicable. It is assumed that the result of the quote method is used
187 # as part of a quoted string, if the protocol supports that.
188 # $chan->recv() returns a list of received commands from the channel, or
189 # the empty list if none are available.
190 # $conn->fds() returns all file descriptors used by the channel's
191 # connections, so one can use select() on them.
200 # Represents a connection over a socket.
201 # Mainly used to wrap a channel around it for, in this case, line based or rcon-like operation.
202 package Connection::Socket;
205 use IO::Socket::INET;
209 # my $conn = new Connection::Socket(tcp => "localaddress" => "remoteaddress" => 6667);
210 # If the remote address does not contain a port number, the numeric port is
211 # used (it serves as a default port).
214 my ($class, $proto, $local, $remote, $defaultport) = @_;
215 my $sock = IO::Socket::INET->new(
217 (length($local) ? (LocalAddr => $local) : ()),
219 PeerPort => $defaultport
220 ) or die "socket $proto/$local/$remote: $!";
223 # Mortal fool! Release me from this wretched tomb! I must be set free
224 # or I will haunt you forever! I will hide your keys beneath the
225 # cushions of your upholstered furniture... and NEVERMORE will you be
226 # able to find socks that match!
228 # My demonic powers have made me OMNIPOTENT! Bwahahahahahahaha!
231 bless $you, 'Connection::Socket';
234 # $sock->sockname() returns the local address of the socket.
238 my ($port, $addr) = sockaddr_in $self->{sock}->sockname();
239 return "@{[inet_ntoa $addr]}:$port";
242 # $sock->send($data) sends some data over the socket; on success, 1 is returned.
245 my ($self, $data) = @_;
248 if(not eval { $self->{sock}->send($data); })
256 # $sock->recv() receives as much as possible from the socket (or at most 32k). Returns "" if no data is available.
261 $self->{sock}->recv($data, 32768, 0);
265 # $sock->fds() returns the socket file descriptor.
269 return fileno $self->{sock};
278 # QW rcon protocol channel.
279 # Wraps around a UDP based Connection and sends commands as rcon commands as
280 # well as receives rcon replies. The quote and join_commands methods are using
281 # DarkPlaces engine specific rcon protocol extensions.
287 # my $chan = new Channel::QW($connection, "password");
290 my ($class, $conn, $password) = @_;
293 password => $password,
297 bless $you, 'Channel::QW';
300 # Note: multiple commands in one rcon packet is a DarkPlaces extension.
301 sub join_commands($@)
303 my ($self, @data) = @_;
304 return join "\0", @data;
309 my ($self, $line, $nothrottle) = @_;
310 return $self->{connector}->send("\377\377\377\377rcon $self->{password} $line");
313 # Note: backslash and quotation mark escaping is a DarkPlaces extension.
316 my ($self, $data) = @_;
317 $data =~ s/[\000-\037]//g;
318 $data =~ s/([\\"])/\\$1/g;
319 $data =~ s/\$/\$\$/g;
328 length(my $s = $self->{connector}->recv())
331 if $s !~ /^\377\377\377\377n(.*)$/s;
332 $self->{recvbuf} .= $1;
335 while($self->{recvbuf} =~ s/^(.*?)(?:\r\n?|\n)//)
345 return $self->{connector}->fds();
361 my ($default, $value) = @_;
362 return $value if defined $value;
366 my $server = default '', $ENV{rcon_address};
367 my $password = default '', $ENV{rcon_password};
368 my $timeout = default '5', $ENV{rcon_timeout};
369 my $colors = default '0', $ENV{rcon_colorcodes_raw};
373 print STDERR "Usage: rcon_address=SERVERIP:PORT rcon_password=PASSWORD $0 rconcommands...\n";
374 print STDERR "Optional: rcon_timeout=... (default: 5)\n";
375 print STDERR " rcon_colorcodes_raw=1 (to disable color codes translation)\n";
379 my $connection = Connection::Socket->new("udp", "", $server, 26000);
380 my $rcon = Channel::QW->new($connection, $password);
382 if(!$rcon->send($rcon->join_commands(@ARGV)))
389 my $sel = IO::Select->new($rcon->fds());
390 if($sel->can_read($timeout))
394 $_ = (color_dp2ansi $_) . "\033[m" unless $colors;