#!/usr/bin/perl -w # Mud-Client. Nov 2001 - August 2003 (c) Jess Robinson (castaway@desert-island.m.isar.de) # $Revision: 1.39 $ $Date: 2003/09/20 10:26:00 $ #use warnings; use lib "$ENV{HOME}/perl/lib"; use lib "/home/castaway/perl/lib"; use Config; use IO::Select; use IO::Socket; use Storable qw(store_fd retrieve_fd); use Cwd; use Data::Dumper; use File::Basename; use File::Spec; use File::Find; require TelnetClient::Map::Directions; require Term::Screen; # Global variables my $hostname = 'ff.franken.de'; my $port = 23; my $MAXHISTORY = 1000; my $DEBUG = 'log'; my $MAPPER = 0; my $LOG = ''; my $LOGFILE = undef; my $PWD = cwd; my $CONNECT = 0; # 1 = Connection closed my $TRIGGER = 0; my $HOMEDIR = ''; # $ENV{HOME}/perltelnet my $ECHOING = 1; my $sockthread = 0; # ThreadId of the mud-socket-thread my $IntPrompt = '>'; # Internal prompt my $PrevIntPrompt = ''; # Previous internal prompt my $ExtPrompt = '>'; # External (mud) prompt my $PromptType = 'none'; # none/ignore/import my $mudhandle = 0; # Handle of mudsocket (non-threading) my $sockets = 0; # IO::Select (non-threading) my $screen = 0; # Term::Screen object my $screenwidth = 0; # width of terminal window my $screenheight = 0; # height of terminal window my $editareaheight = 1; # height of input area my $previouseah = 1; # height of previous input area my $currentline = ''; # current typed text my $cursorpos = length($IntPrompt); # cursor position (in text) my $editline = 1; # cursor height in input area my $currentcommand = 0; my @commandhistory = (); # input command history my $Directions = new TelnetClient::Map::Directions; my %Commands = (); # Hash of available commands my %ParseMethods = (); # Methods to call when parsing mudtext my %InputMethods = (); # Methods to call when parsing input my %RawDataMethods = (); # Methods to call when parsing raw data my %OptionMethods = (); # Methods for telnet options my %InputSockets = (); # Input sockets my %ExternalSockets = (); # External sockets my %PromptMethods = (); # Methods to call when prompt found $screen = new Term::Screen; unless ($screen) {die " Can't get Term::Screen\n";} #$screen->clrscr(); $| = 1; init_telnet(); init_commands(); init_opts(); $screenheight = $screen->rows(); $screenwidth = $screen->cols(); debug("Screenheight: $screenheight, Screenwidth: $screenwidth\n"); my $conn; if($Config{use5005threads}) { debug("5005 Threads\n"); require Thread; Thread->import('yield'); require Thread::Queue; import Thread::Queue; $conn = start_connection(1); # Threaded = >1 $telnetprintqueue = Thread::Queue->new(); $screenprintqueue = Thread::Queue->new(); $connectqueue = Thread::Queue->new(); } elsif($Config{useithreads}) { debug("I-Threads\n"); require threads; import threads; # require threads::shared; # import threads::shared; require Thread::Queue; import Thread::Queue; import threads::shared; # our $CONNECT : shared = 0; share(\$CONNECT); $telnetprintqueue = Thread::Queue->new(); $screenprintqueue = Thread::Queue->new(); # $connectqueue = Thread::Queue->new(); $conn = start_connection(2); # Threaded = 1 } else { debug("No Threads\n"); $conn = start_connection(0); # Threaded = 1 } if($conn) { $telnet = $conn; $sockets = new IO::Select(); $sockets->add(\*STDIN); $sockets->add($conn); } $screen->noecho(); $screen->at($screenheight, 0); print STDOUT "$IntPrompt"; while(1) { my $kpressed; my @handles; if($sockets) { @handles = $sockets->can_read(0.5); foreach $handle (@handles) { if($handle == $conn) { if(!read_telnet_data($conn)) { last; } } else { $kpressed = 1; } } } else { # lock $screen; $kpressed = $screen->key_pressed(); } if($kpressed) { my $ch; { # lock $screen; $ch = $screen->getch(); } # Add character to current input line currentinput($ch); # printandprompt("Ord :" . ord($ch) . "\n"); # printandprompt("len ch :" . length($ch) . "\n"); if(ord($ch) == 3) { printandprompt("CTRL-C typed\n"); close_socket(); $sockthread->join(); if(length($LOG) > 0) {createlog($LOG);} last; } } # reset in case terminal was resized $screen->resize(); my $envw = $screen->cols(); my $envh = $screen->rows(); if(($envw != $screenwidth) or ($envh != $screenheight)) { $screenheight = $envh; $screenwidth = $envw; debug("Screenheight: $screenheight, Screenwidth: $screenwidth\n"); sendopt('SB', 31); } my $toprint; while($screenprintqueue && ($toprint = $screenprintqueue->dequeue_nb())) { printandprompt($toprint); } if($CONNECT) # if($connectqueue->pending()) { debug("socket not connected\n"); $sockthread->join(); last; } # sleep 0.5; } sub splitinputlines { # Takes an input line possibly containing the command separator character # and returns an array containing each of the commands # Parameter: Line my $line = $_[0]; my @lines = (); my $cs = $CS->getCommandSeparator(); pos $line = 0; while($line =~ /(.+?)$cs/gc) { my $command = $1; if($line =~ /\G[\)\-]/) { $line =~ /(.+?)$cs/g; $command .= $1; } $lines[scalar(@lines)] = $command; } if(pos($line) == 0) { $lines[0] = $line; } return @lines; } sub parselocalcommand : locked { # Parameter: String line my $data = $_[0]; # Use an array instead of a hash, to allow sorting of commandorder?? # list commands, resort etc.! my $olddata = $data; my $newdata = $data; if(substr($data, 0, 1) ne '#') { while (my ($modname, $method) = each %InputMethods) { my $mod = getmodule($modname); # debug("plc: $modname, $method\n"); if($mod->getInputType() eq 'line') { my $res = $mod->$method($data); if($mod->getInputChangeYN()) { $newdata = $res; } } } } # !! Before AND After modules?? # I could enter: blah;fasel;~alias; ... # sub splitinputlines .. # SpecialCharacters -> split line using commandseparator, # call parselocalcommand for each command, remove each command from $data # leave the last one to be parsed here # Watch out for ';' in smilies! my @lines = split("\n", $newdata); my $result = ''; foreach my $data (@lines) { my $enddata; # if(substr($data, 0, 1) eq '>') # { # # Command to mapper # substr($data, 0, 1) = ''; # my @commands = split('>', $data); ## debug("parselocalcommand :" . scalar(@commands) . "\n"); # if(!@commands) # { ## return '>' . $data; # $enddata = '>' . $data; # } # if(!$Directions->isdirection($commands[0])) # { ## return '>' . $data; # $enddata = '>' . $data; # } # $dir = $commands[0]; # $command = $commands[0]; # if($commands[1]) # { # $command = $commands[1]; # } ## debug("parselocalcommand :" . $dir . " " . $command . "\n"); # newroom($dir, $command); ## return $command; # $enddata = $command; # } # if(creatingpath()) # { ## debug("Test direction: $data\n"); # if($Directions->isdirection($data)) # { ## debug("Is direction: $data\n"); # addtopath($data, 0); # } # } # if(isdirection($data) && query_mapping()) # { # newroom($data, $data); # } # debug("plc: Thread: " . Thread->self->tid . "\n"); # debug("plc: Got text: $data\n"); if(substr($data, 0, 1) eq '#') { # Command to client - don't send substr($data, 0, 1) = ''; # debug("plc: Got command: $data\n"); # currentinput(''); # debug("parselocalcommand : $data\n"); my @reset = keys %Commands; foreach my $command (keys %Commands) { my $params = $Commands{$command}; # debug("parselocalcommand: Command: $command\n"); if(substr($data, 0, length($command)) eq $command) { my $pattern = $params->{'pattern'}; if(my @res = $data =~ $pattern) { if($#+ == $params->{'function'}[1][1]) { my $answer; if($params->{'function'}[0] eq '') { $answer = &{$params->{'function'}[1][0]}(@res); } else { $answer = modulemethod($params->{'function'}[0], $params->{'function'}[1][0], @res); } if($answer) { printandprompt($answer); last; } } } else { printandprompt("Syntax: #" . $params->{'syntax'} . "\n"); } } } $data = ''; printandprompt(''); # clear input } if(!$enddata && $data && (length($data) > 0)) { $enddata = $data; } # debug("parselocalcommand: Data: $data Enddata: $enddata\n"); if($enddata) { $result .= $enddata . "\n"; } } if($newdata ne $olddata) { printandprompt($data); } chomp($result); return $result; } my $lastchar; sub output_vars : locked { # get new values for currentline, cursorpos, editline, editareaheight # and return actual values my ($cl, $cp, $el, $eah) = @_; # lock $currentline; # lock $cursorpos; # lock $editline; # lock $editareaheight; # lock $previouseah; if(defined($cl)) {$currentline = $cl;} if(defined($cp)) {$cursorpos = $cp;} if(defined($el)) {$editline = $el;} if(defined($eah)) {$previouseah = $editareaheight; $editareaheight = $eah;} return ($currentline, $cursorpos, $editline, $editareaheight, $previouseah); } sub listhist : locked { # Output the contents of the command history # Parameter: From, To my ($from, $to) = @_; for( my $i = $from; $i <= $to; $i++) { printandprompt($i . " :" . $commandhistory[$i] . "\n"); } } sub currentinput : locked { # Parameter: text to add if(@_) { # debug("currentinput : editareaheight :" . $editareaheight . "\n"); # debug("currentinput : cursorpos :" . $cursorpos . "\n"); if($_[0] eq '') { output_vars('', length($IntPrompt), 1, undef); } elsif(ord($_[0]) == 13) { my ($cl, $cp, $el, $eah) = output_vars(); printandprompt($cl . "\n"); if($cl && length($cl) > 1) { if($#commandhistory >= $MAXHISTORY) { shift(@commandhistory); } my $pos = ismember($cl, \@commandhistory); if($pos > -1) { splice(@commandhistory, $pos, 1); } $commandhistory[scalar(@commandhistory)] = $cl; $currentcommand = (scalar(@commandhistory)); } $cl = parselocalcommand($cl); if(defined($cl)) { # debug("currentinput :" . $cl . "\n"); $telnetprintqueue->enqueue($cl . "\n"); # print_socket($cl . "\n"); } # printandprompt("\n"); output_vars('', length($IntPrompt), 1, 1); } elsif(ord($_[0]) == 127) { # Backspace, remove character before the cursor # editareaheight wieder reinrechnen # debug("backspace-before : cursorpos :" . $cursorpos . "\n"); { my ($cl, $cp, $el, $eah) = output_vars(); $cp = (($el - 1) * ($screenwidth)) + $cp; output_vars(undef, $cp, undef, undef); } # debug("backspace : editline :" . $editline . "\n"); # debug("backspace : editareaheight :" . $editareaheight . "\n"); # debug("backspace : cursorpos :" . $cursorpos . "\n"); my ($cl, $cp, $el, $eah) = output_vars(); if($cp > length($IntPrompt)) { substr($cl, $cp - length($IntPrompt) - 1, 1) = ''; $cp -= 1; $eah = int((length($cl) + 1)/$screenwidth) + 1; $el = int(($cp / $screenwidth)) + 1; $cp = $cp % $screenwidth; output_vars($cl, $cp, $el, $eah); printandprompt(''); } } elsif(ord($_[0]) < 32) { # editareaheight wieder reinrechnen { my ($cl, $cp, $el, $eah) = output_vars(); $cp = (($el - 1) * ($screenwidth)) + $cp; if(ord($_[0]) == 2) { # CTRL-B - Cursor back one character if($cp > length($IntPrompt)) { $cp -= 1; } } elsif(ord($_[0]) == 6) { # CTRL-F - Cursor forward one character if($cp < (length($cl) + length($IntPrompt))) { $cp += 1; } } elsif(ord($_[0]) == 1) { # CTRL-A - Cursor to beginning of line if($cp != length($IntPrompt)) { $cp = length($IntPrompt); } } elsif(ord($_[0]) == 5) { # CTRL-E - Cursor to end of line if($cp != (length($cl) + length($IntPrompt))) { $cp = length($cl) + length($IntPrompt); } } elsif(ord($_[0]) == 4) { # CTRL-D - Delete char under the cursor if($cp >= length($IntPrompt) && $cp < (length($cl) + length($IntPrompt))) { { substr($cl, $cp - length($IntPrompt), 1) = ''; } } } elsif(ord($_[0]) == 16 || ord($_[0]) == 9) { # CTRL-P / Tab - Get previous history command if(length($cl) > 0 and $lastchar != 16 and $lastchar != 9) { # Search for last line beginning with this text for(my $i = (scalar(@commandhistory)) - 1; $i > 0; $i--) { if(substr($commandhistory[$i], 0, length($cl)) eq $cl) { $cl = $commandhistory[$i]; $cp = length($cl) + length($IntPrompt); last; } } } elsif($currentcommand > 0) { $currentcommand -= 1; $cl = $commandhistory[$currentcommand]; $cp = length($cl) + length($IntPrompt); } } elsif(ord($_[0]) == 14) { # CTRL-N - Get next history command print STDOUT chr(15); if($currentcommand < $#commandhistory) { $currentcommand += 1; $cl = $commandhistory[$currentcommand]; $cp = length($cl) + length($IntPrompt); } } else { debug("currentline: Don't know char: " . ord($_[0]) . "\n"); } output_vars($cl, $cp, $el, $eah); } { # lock $currentline; # lock $cursorpos; # lock $editline; # lock $editareaheight; $editline = int(($cursorpos / $screenwidth)) + 1; $cursorpos = $cursorpos % $screenwidth; $editareaheight = int((length($currentline) + 1)/$screenwidth) + 1; # printandprompt("CP :" . $cursorpos . " EAH :" . $editareaheight . "\n"); # lock $screen; # $screen->at($screenheight - $editareaheight, $cursorpos); $screen->at(($screenheight - $editareaheight) + $editline - 1, $cursorpos); } printandprompt(''); } elsif(ord($_[0]) > 31) { my $char = $_[0]; # reset Hash my @k = keys %InputMethods; while (my ($modname, $method) = each %InputMethods) { # debug("Module: InputMethods: $modname, $char\n"); my $mod = getmodule($modname); if($mod->getInputType() eq 'char') { my $res = $mod->$method($char); if($mod->getInputChangeYN()) { if(length($res) == 0) { return $currentline; } $char = $res; } } } # editareaheight wieder reinrechnen { my ($cl, $cp, $el, $eah) = output_vars(); $cp = (($el - 1) * ($screenwidth)) + $cp; # substr($cl, $cp - length($IntPrompt)) = # $_[0] . substr($cl, $cp - length($IntPrompt)); substr($cl, $cp - length($IntPrompt)) = $char . substr($cl, $cp - length($IntPrompt)); $cp += 1; $el = int(($cp / $screenwidth)) + 1; $cp = $cp % $screenwidth; #debug("currentinput :" . $_[0] . "\n"); #debug("currentinput :" . $cursorpos . "\n"); #debug("currentinput :" . $currentline . "\n"); $eah = int((length($cl) + 1)/$screenwidth) + 1; output_vars($cl, $cp, $el, $eah); } printandprompt(''); } $lastchar = ord($_[0]); # debug("backspace_end : editareaheight :" . $editareaheight . "\n"); # debug("backspace_end : cursorpos :" . $cursorpos . "\n"); } # return $currentline; } sub printandprompt : locked { my ($data) = @_; $data .= "\n" unless($data =~ /n$/s); # debug("pap: Thread: " . Thread->self->tid . "\n"); # debug("pap: Got data:" . $data . "\n"); # Parameter: Text to print # debug("printandprompt: Editheight :" . $editareaheight . "\n"); # ??? my ($cl, $cp, $el, $eah, $peah) = output_vars(); if(!$ECHOING) { $cl = '*' x length($cl); } # debug("CL: $cl\n"); # debug("CP: $cp\n"); # debug("EL: $el\n"); # debug("EAH: $eah\n"); # lock $screen; # clear screen for current editor height (or previous if that was larger) # (means we're deleting text) $screen->at($screenheight - ($peah > $eah ? $peah : $eah), 0); $screen->clreos(); # shove text up a line if this is a new line in the editline (cursor # position = 0) $screen->at($screenheight, 0); print STDOUT "\n" if($cp == 0 && $peah < $eah); # Print new text to screen, if any $screen->at($screenheight - $eah, 0); print STDOUT $_[0]; # Print current input line $screen->at($screenheight - $eah, 0); if(length($cl) == 0) { print STDOUT "$IntPrompt"; } else { print STDOUT "$IntPrompt" . $cl; } # Reset cursor $screen->at(($screenheight - $eah) + $el - 1, $cp); # $screen->at($screenheight, $cursorpos); } sub setprompttype { # What to do with the prompt from the mud: # None - Do nothing (default, ignores only '> ' prompts!) # Import - Use prompt as client prompt # Ignore - Throw it away # Parameter: None, Import, Ignore my $ptype = $_[0]; if(lc($ptype) =~ /^(none|import|ignore)$/) { $PromptType = lc($ptype); } } sub setprompt { # Set the current prompt, save the previous one # Parameter: New prompt text my $prompt = $_[0]; # debug("Prompt: $prompt\n"); my ($cl, $cp, $el, $eah) = output_vars(); if($prompt eq 'OLD') { my $tmp = $IntPrompt; $IntPrompt = $PrevIntPrompt; $PrevIntPrompt = $tmp; if($cp == length($PrevIntPrompt)) { $cp = length($IntPrompt); } output_vars($cl, $cp, $el, $eah); return 1; } elsif($prompt eq 'MUD') { if($ExtPrompt ne $IntPrompt) { $PrevIntPrompt = $IntPrompt; $IntPrompt = $ExtPrompt; $cp = length($IntPrompt); output_vars($cl, $cp, $el, $eah); return 1; } return 1; } $PrevIntPrompt = $IntPrompt; $IntPrompt = $prompt; $cp = length($prompt); output_vars($cl, $cp, $el, $eah); return 1; } sub start_connection { # Check the Arguments for a value from perltelnet/sites # Parameter: Threaded? (@ARGV) my ($threaded) = @_; # 'chdir' changes to the home directory # search for 'perltelnet' directory in home directory, create when # not available? my $cdir = cwd; # debug("Current Directory: $cdir\n"); chdir(); if(!chdir("perltelnet")) { # debug("No 'perltelnet' directory: Creating..\n"); if(!mkdir("perltelnet")) { # debug("Can't create 'perltelnet' directory!\n"); } else { chdir("perltelnet"); } } $HOMEDIR=cwd; chdir($cdir); if(@ARGV == 1) { my $settingsfile = ''; my $sites = File::Spec->catfile($HOMEDIR, "sites"); if(open(SITES, $sites)) { while($line = ) { chomp($line); next if $line =~ /^\#/; my ($s_name, $s_addr, $s_port, $s_file) = split(" ", $line); if(lc($s_name) eq lc($ARGV[0])) { $hostname = $s_addr; $port = $s_port; $settingsfile = $s_file; } } } else { # debug("Can't open $sites: $!\n"); } if(length($settingsfile) > 0) { loadmodules(File::Spec->catfile($HOMEDIR, $settingsfile)); } } if(@ARGV == 2) { # set new hostname/port if given $hostname = $ARGV[0]; $port = $ARGV[1]; } if($threaded) { if($threaded == 1) { $sockthread = Thread->new(\&socket_thread); } elsif($threaded == 2) { $sockthread = threads->new(\&socket_thread); } return 0; } else { my $sockhandle = create_mudsocket(); return $sockhandle; } } { # my $telnet; my $packetdata; my $socketsselect; my $message_id; my $messages; my $MapMode; # Scalar (1 - Map, 2 - Follow, 0 - Off) my $FieldSeparator; my $DataSeparator; my $MapperSocket; sub create_mudsocket { # Create the mudsocket and return its handle. # Parameter: None my $t = new IO::Socket::INET( PeerAddr => $hostname, PeerPort => $port, Proto => 'tcp', Timeout => 60 ); die "Kann Verbindung nicht herstellen ($!)" unless $t; return $t; } sub init_telnet { $packetdata = ''; $messages = (); $message_id = 0; $packetdata = ''; $FieldSeparator = '#'; $DataSeparator = ':'; $MapMode = 1; $MapperSocket = undef; } sub socket_thread { $telnet = create_mudsocket(); # $telnet->autoflush(1); $socketselect = new IO::Select(); $socketselect->add($telnet); while(1) { @handles = $socketselect->can_read(0.5); foreach $handle (@handles) { if($handle == $telnet) { if(!read_telnet_data($telnet)) { last; } } while (my ($modname, $exsocket) = each %ExternalSockets) { if($handle == $exsocket) { my $mod = getmodule($modname); my $method = $mod->getExternalSocketMethod(); my $res; if(!defined($res = $mod->$method())) { $socketselect->remove($exsocket); } $screenprintqueue->enqueue($res); # printandprompt($res); } } } my $toprint; while($telnetprintqueue && ($toprint = $telnetprintqueue->dequeue_nb())) { $telnet->print($toprint); } if($CONNECT) # if($connectqueue->pending()) { $telnet->close(); last; } if(!$telnet->connected()) { # $connectqueue->enqueue(1); $CONNECT = 1; last; } # yield; # sleep 0.5; } return; } sub addexternalsocket { # Add external sockets to IO::Select # Parameter: Socket my ($sock) = @_; $socketselect->add($sock); } sub read_telnet_data : locked { # Receive and process data from mud socket # Parameter: Socket my $sock = $_[0]; my $data; { # lock $sock; my $y = recv($sock, $data, 1024, 0); } if(!length($data)) { return 0; } my $lendata = length($data); # debug("before answer_opts: $data\n:LINEEND:" . length($data). "\n"); # RawData methods! my @k = keys %RawDataMethods; while (my ($modname, $method) = each %RawDataMethods) { my $mod = getmodule($modname); my $res = $mod->$method($data); if($mod->getRawDataChangeYN()) { $data = $res; } } if(!$data) { return; } $data = answer_telnet_opts($sock, $data); if(length($packetdata) > 0) { $data = $packetdata . $data; $packetdata = ''; } # debug("read_telnet_data :$data\n:LINEEND:" . length($data). "\n"); my @lines = split('\n', $data); for (my $i = 0; $i < @lines; $i++) { # debug("read_telnet_data :" . $line . "\n"); $line=$lines[$i]; if(!$line) { next; } # debug("Length of Data: $lendata\n"); if($i == (@lines - 1) && $lendata == 1024) { $packetdata .= $line; # debug("Keeping data:$packetdata\n\n"); return 1; } my @k = keys %ParseMethods; my $endline = 0; while (my ($modname, $method) = each %ParseMethods) { my $mod = getmodule($modname); my $res = $mod->$method($line); if($mod->getParseChangeYN()) { $endline = 1, last if(!defined($res)); $line = $res; } } next if($endline); # If prompttype is 'none' just match '> ', if its 'import' # or 'ignore' then match $mudprompt. # debug("Mudprompt: " . mudprompt() . "\n"); # debug("Mudline: $line\n"); # compare lines without ansicodes! my $pline = $line; $pline =~ s/\e\[\d+m//g; my $pprompt = mudprompt(); $pprompt =~ s/\e\[\d+m//g; # Ignore lines which only consist of ansi codes? # Do ansi-code ends, without newlines! if((length($pline) != length($line)) && length($pline) == 0) { $screenprintqueue->enqueue->($line); # printandprompt($line); if($LOG) { writelog($line); } next; } if((($PromptType eq 'none' || $pprompt eq "\0") && $pline eq '> ') || ($PromptType =~ /ignore|import/ && $pline eq $pprompt)) # if($line eq '> ') { # debug("Prompttype: $PromptType\n"); # Ignore mud-prompt my @k = keys %PromptMethods; while (my ($modname, $method) = each %PromptMethods) { my $mod = getmodule($modname); $mod->$method($line); } if($PromptType eq 'import' && mudprompt() ne "\0") { if(mudprompt() ne $ExtPrompt) { $ExtPrompt = mudprompt(); $ExtPrompt =~ s/\e\[\d+m//g; setprompt('MUD'); # screenprintqueue? # printandprompt(''); # debug("Prompt: $ExtPrompt\n"); } } if($LOG) { writelog($line); } next; } # if(creatingroom()) # { # addroom($line); # } # $line .= "\n"; # debug("Line: $line\n\n"); ## debug("Length of Data: $lendata\n"); # if($i == (@lines - 1) && $lendata == 1024) # { # $packetdata .= $line; # debug("Keeping data:$packetdata\n\n"); # return 1; # } $line .= "\n"; if($LOG) { writelog($line); } $screenprintqueue->enqueue($line); # printandprompt($line); } return 1; } sub print_socket : locked { # Parameter: Socket, String to print my ($data) = @_; $telnet->print($data); } sub close_socket : locked { # Parameter: None # $connectqueue->enqueue(1); $CONNECT = 1; # close($telnet); } { my $lastcommand; my ($chIAC, $chDONT, $chDO, $chWONT, $chWILL, $chSB, $chSE, $chSEND, $chIS, $chEOR); my ($OPT_TTYPE, $OPT_LINEMODE, $OPT_BINARY, $OPT_NAWS, $OPT_MSP, $OPT_MXP, $OPT_MCCP, $OPT_EOR); my $OPT_LINEMODE_MODE; my $OPT_LINEMODE_MODE_EDIT; my %telnetopts; my $mudprompt; my $olddata; sub init_opts { $lastcommand = ''; $chIAC = chr(255); $chDONT = chr(254); $chDO = chr(253); $chWONT = chr(252); $chWILL = chr(251); $chSB = chr(250); $chSE = chr(240); $chSEND = chr(1); $chIS = chr(0); $chEOR = chr(239); $OPT_ECHO = 1; $OPT_TTYPE = 24; $OPT_LINEMODE = 34; $OPT_BINARY = 0; $OPT_NAWS = 31; $OPT_MSP = 90; $OPT_MXP = 91; $OPT_MCCP = 86; $OPT_EOR = 25; $OPT_LINEMODE_MODE = 1; $OPT_LINEMODE_MODE_EDIT = 1; $telnetopts{$OPT_ECHO} = 0; $telnetopts{$OPT_BINARY} = 0; $telnetopts{$OPT_LINEMODE} = 0; $telnetopts{$OPT_TTYPE} = 0; $telnetopts{$OPT_NAWS} = 0; $telnetopts{$OPT_MSP} = 0; $telnetopts{$OPT_MXP} = 0; $telnetopts{$OPT_MCCP} = 0; $telnetopts{$OPT_EOR} = 0; $mudprompt = "\0"; $olddata = ''; } sub mudprompt { return $mudprompt; } sub gettelnetoptstate { # Return the current state of an option by number # If not available, assume '0' = dont know this option my $opt = $_[0]; if(defined($telnetopts{$opt})) { return $telnetopts{$opt}; } return 0; } sub answer_telnet_opts : locked { my ($sock, $data) = @_; my $pos = -1; my $option; # No prompt found as yet $mudprompt = "\0"; { # lock $lastcommand; $data = $lastcommand . $data; $lastcommand = ''; } while(($pos = index($data, $chIAC, $pos)) > -1) { # debug("Found IAC\n"); my $nextchar = substr($data, $pos + 1, 1); if(!length($nextchar)) { $lastcommand = $chIAC; chop($data); last; } if($nextchar eq $chIAC) { substr($data, $pos, 1) = ''; $pos++; } elsif ($nextchar eq $chDONT or $nextchar eq $chDO or $nextchar eq $chWONT or $nextchar eq $chWILL) { $option = substr($data, $pos + 2, 1); if(!length($option)) { $lastcommand .= $chIAC . $nextchar; chop($data); chop($data); last; } substr($data, $pos, 3) = ''; # Extermal Option methods! my @k = keys %OptionMethods; my $res; while (my ($modname, $mhash) = each %OptionMethods) { my $mod = getmodule($modname); my %methods = %{$mhash}; # debug("Options: $modname\n"); # debug("Options: " . Dumper($mhash)); if(defined($methods{ord($option)})) { my $m = $methods{ord($option)}; # debug(Dumper($m)); if(($nextchar eq $chDO) && $m->{'DO'}) { my $meth = $m->{'DO'}; $res = $mod->$meth(); } elsif(($nextchar eq $chDONT) && $m->{'DONT'}) { my $meth = $m->{'DONT'}; $res = $mod->$meth(); } elsif(($nextchar eq $chWILL) && $m->{'WILL'}) { my $meth = $m->{'WILL'}; $res = $mod->$meth(); } elsif(($nextchar eq $chWONT) && $m->{'WONT'}) { my $meth = $m->{'WONT'}; $res = $mod->$meth(); } } } if(!$res) { # Wasn't answered by external module $data = negotiate_option($sock, $data, $nextchar, ord($option), $pos); } else { # Answer option sendopt($res, ord($option)); } } elsif($nextchar eq $chSB) { my $endpos = index($data, $chSE, $pos); if($endpos == -1) { $lastcommand .= substr($data, $pos); substr($data, $pos) = ''; last; } my $subcmd = substr($data, $pos + 2, $endpos - $pos + 1); substr($data, $pos, $endpos - $pos + 1) = ''; # Extermal Option methods! my @k = keys %OptionMethods; my $res; while (my ($modname, $mhash) = each %OptionMethods) { # debug("Options-SB: $modname\n"); my $mod = getmodule($modname); my %methods = %{$mhash}; # debug("Options-SB: " . ord(substr($subcmd, 0, 1)) . "\n"); if(defined($methods{ord(substr($subcmd, 0, 1))})) { my $m = $methods{ord(substr($subcmd, 0, 1))}; if($m->{'SB'}) { my $meth = $m->{'SB'}; $res = $mod->$meth($subcmd, $data, $pos); } } } if(!$res) { # Wasn't answered by external module # debug("Telnet opt: $nextchar\n"); $data = negotiate_suboption($sock, $data, $nextchar, $subcmd, $pos); } else { $data = $res; } } elsif($nextchar eq $chEOR) { # extract prompt from datastring # debug("Extracting prompt..\n"); $testdata = $olddata . $data; if($testdata =~ /(?:\r\n)?(.+)\x{FF}\x{EF}/) { $mudprompt = $1; $olddata = ''; # debug("Found prompt: $mudprompt\n"); } substr($data, $pos, 2) = ''; } else # Unknown option, delete { substr($data, $pos, 2) = ''; } } # Add previous data line because of EORs/prompts if(gettelnetoptstate(25) eq 'WANTING') { $olddata = $data; } return $data; } sub sendopt { # Request a telnet option direct # Parameter: DO/WILL/DONT/WONT/SB, Option number my ($req, $opt) = @_; debug("Sendopt: $req, $opt\n"); if($req eq 'WILL') { if($telnetopts{$opt} eq 'DOING') { debug("Already doing $opt.\n"); return; } print $telnet $chIAC . $chWILL . chr($opt); $telnetopts{$opt} = 'WILL'; } if($req eq 'WONT') { if($telnetopts{$opt} ne 'WILL' && $telnetopts{$opt} ne 'DOING') { debug("We're not wanting that anyway!\n"); return; } print $telnet $chIAC . $chWONT . chr($opt); $telnetopts{$opt} = 'WONT'; } if($req eq 'DO') { if($telnetopts{$opt} eq 'WILL') { debug("Already wanting $opt.\n"); return; } print $telnet $chIAC . $chDO . chr($opt); $telnetopts{$opt} = 'WILL'; } if($req eq 'DONT') { if($telnetopts{$opt} && $telnetopts{$opt} ne 'DO' && $telnetopts{$opt} ne 'WILL') { debug("We're not wanting that anyway!\n"); return; } print $telnet $chIAC . $chDONT . chr($opt); $telnetopts{$opt} = 'WONT'; } if($req eq 'SB') { debug("Sendopt: $opt, Status: $telnetopts{$opt}\n"); if($opt == 31 && $telnetopts{$opt} eq 'DOING') { print $telnet $chIAC . $chSB . chr($opt) . chr(0) . chr($screenwidth) . chr(0) . chr($screenheight) . $chIAC . $chSE; } } } sub negotiate_option : locked { my ($telnet, $data, $opt_req, $opt, $optpos) = @_; # debug("Mud sent option request:" . ord($opt_req) . ":" . $opt . "\n"); if($opt_req eq $chDO) { # debug("Do $opt $OPT_TTYPE\n"); if($opt == $OPT_TTYPE) { debug("Do Terminal Type\n"); if($telnetopts{$OPT_TTYPE} ne 'DOING') { print $telnet $chIAC . $chWILL . chr($OPT_TTYPE); $telnetopts{$OPT_TTYPE} = 'DO'; } } elsif($opt == $OPT_BINARY) { debug("Do Binary\n"); if($telnetopts{$OPT_BINARY} ne 'DOING' && $telnetopts{$OPT_BINARY} ne 'DO') { print $telnet $chIAC . $chWILL . chr($OPT_BINARY); $telnetopts{$OPT_BINARY} = 'DOING'; } } elsif($opt == $OPT_NAWS) { debug("Do Naws\n"); # print $telnet $chIAC . $chWONT . chr($OPT_NAWS); if($telnetopts{$OPT_NAWS} ne 'DOING' && $telnetopts{$OPT_NAWS} ne 'DO') { print $telnet $chIAC . $chWILL . chr($OPT_NAWS); print $telnet $chIAC . $chSB . chr($OPT_NAWS) . chr(0) . chr($screenwidth) . chr(0) . chr($screenheight) . $chIAC . $chSE; $telnetopts{$OPT_NAWS} = 'DOING'; } } elsif($opt == $OPT_LINEMODE) { debug("Do Linemode\n"); if($telnetopts{$OPT_LINEMODE} ne 'DOING' && $telnetopts{$OPT_LINEMODE} ne 'DO') { print $telnet $chIAC . $chWILL . chr($OPT_LINEMODE); $telnetopts{$OPT_LINEMODE} = 'DOING'; } #Fiona teilt Dir mit: wennde linemode erlaubst senden wir IAC SB LINEMODE MODE # EDIT IAC SE und IAC SB LINEMODE DO FORWARDMASK 00 0x24 IAC SE } elsif($opt == $OPT_MSP) { debug("Do MSP\n"); print $telnet $chIAC . $chWONT . chr($OPT_MSP); } else { debug("Do $opt\n"); print $telnet $chIAC . $chWONT . chr($opt); } } elsif($opt_req eq $chWILL) { if($opt == $OPT_EOR) { debug("Will EOR\n"); if($telnetopts{$OPT_EOR} ne 'WANTING') { print $telnet $chIAC . $chDO . chr($OPT_EOR); $telnetopts{$OPT_EOR} = 'WANTING'; } } elsif($opt == $OPT_MXP) { debug("Will MXP\n"); if($telnetopts{$OPT_MXP} ne 'WILL' && $telnetopts{$OPT_MXP} ne 'DOING') { print $telnet $chIAC . $chWONT . chr($OPT_MXP); } else { # We asked for it! - Dont answer debug("Server does MXP\n"); # Now we got what we wanted. $telnetopts{$OPT_MXP} = 'DOING'; } } elsif($opt == $OPT_ECHO) { debug("Will ECHO\n"); if($telnetopts{$OPT_ECHO} eq 'DOING') { print $telnet $chIAC . $chWONT . chr($OPT_ECHO); } else { $telnetopts{$OPT_ECHO} = 'DOING'; print $telnet $chIAC . $chDO . chr($OPT_ECHO); $ECHOING = 0; } } else { debug("Will $opt\n"); print $telnet $chIAC . $chDONT . chr($opt); } } elsif($opt_req eq $chWONT) { if($opt == $OPT_ECHO) { if($telnetopts{$OPT_ECHO} eq 'DOING') { debug("WONT ECHO\n"); $telnetopts{$OPT_ECHO} = 0; print $telnet $chIAC . $chDONT . chr($opt); $ECHOING = 1; } } else { debug("Won't $opt\n"); print $telnet $chIAC . $chDONT . chr($opt); } } elsif($opt_req eq $chDONT) { if($opt == $OPT_MXP) { debug("Don't MXP\n"); if($telnetopts{$OPT_MXP} ne 'WILL' && $telnetopts{$OPT_MXP} ne 'WONT') { # The server refuses debug("Server wont do MXP\n"); print $telnet $chIAC . $chWONT . chr($OPT_MXP); $telnetopts{$OPT_MXP} = 0; } # elsif($telnetopts{$OPT_MXP} eq 'WONT') else { # We didn't want MXP anyway debug("Server doesn't MXP any more.\n"); $telnetopts{$OPT_MXP} = 0; } } else { debug("Won't $opt\n"); print $telnet $chIAC . $chWONT . chr($opt); } } return $data; } sub negotiate_suboption : locked { my ($telnet, $data, $opt_req, $cmd, $optpos) = @_; my $option = substr($cmd, 0, 1); # IAC, SB, TTYPE, SEND, IAC, SE # debug("Mud sent suboption request:" . ord($opt_req) . " :"); if(ord($option) == $OPT_TTYPE) { # Always answer! # if($telnetopts{$OPT_TTYPE} eq 'DO') # { if(substr($cmd, 1, 1) eq $chSEND) { # Request Terminal Type print $telnet $chIAC . $chSB . chr($OPT_TTYPE) . $chIS . 'V' . 'T' . '1' . '0' . '0' . $chIAC . $chSE; } $telnetopts{$OPT_TTYPE} = 'DOING'; # } } elsif(ord($option) == $OPT_LINEMODE) { #IAC SB LINEMODE MODE mask IAC SE if($telnetopts{$OPT_LINEMODE} eq 'DO') { if(ord(substr($cmd, 2, 1)) == $OPT_LINEMODE_MODE) { if(ord(substr($cmd, 3, 1)) == $OPT_LINEMODE_MODE_EDIT) { # ?? } } } } # No Subnegotiation by NAWS! # elsif(ord($option) == $OPT_NAWS) # { # if($telnetopts{$OPT_NAWS} eq 'DO') # { # if(ord(substr($cmd, 2, 1)) == $chSEND) # { # Request Terminal Size ## IAC SB NAWS IS 00 24 00 80 IAC SE ## Fiona teilt Dir mit: also 255 zeichen breites terminal: IAC SB NAWS IS 00 24 ## 00 FF FF IAC SE # } # } # } return $data; } } } sub init_commands { # Fill %Commands hash for parsecommands # %Commands{commandname}{pattern} = # %Commands{commandname}{function} = ['name', arguments] # %Commands{commandname}{syntax} = $Commands{'log'}->{'pattern'} = '^log (.*)$'; $Commands{'log'}->{'function'} = ['', ['createlog', 1]]; $Commands{'log'}->{'syntax'} = 'log '; $Commands{'modules saveall'}->{'pattern'} = '^modules saveall$'; $Commands{'modules saveall'}->{'function'} = ['', ['savemodules', 0]]; $Commands{'modules saveall'}->{'syntax'} = 'modules saveall'; $Commands{'modules loadall'}->{'pattern'} = '^modules loadall ([\w\.]+)$'; $Commands{'modules loadall'}->{'function'} = ['', ['loadmodules', 1]]; $Commands{'modules loadall'}->{'syntax'} = 'modules loadall '; $Commands{'modules available'}->{'pattern'} = '^modules available$'; $Commands{'modules available'}->{'function'} = ['', ['availmodules', 0]]; $Commands{'modules available'}->{'syntax'} = "modules available\r\nLists all avilable TelnetClient:: modules."; $Commands{'module load'}->{'pattern'} = '^module load ([\w\:\.]+)$'; $Commands{'module load'}->{'function'} = ['', ['loadmodule', 1]]; $Commands{'module load'}->{'syntax'} = "module load \r\nLoads the named module.\r\nThe TelnetClient:: prefix is not required, and will be ignored if present."; $Commands{'module unload'}->{'pattern'} = '^module unload ([\w\:\.]+)$'; $Commands{'module unload'}->{'function'} = ['', ['unloadmodule', 1]]; $Commands{'module unload'}->{'syntax'} = "module unload \r\nUnloads the named module.\r\nWill not unload if other modules depend on the chosen module.\r\n.The TelnetClient:: prefix is not required, and will be ignored if present."; $Commands{'module reload'}->{'pattern'} = '^module reload ([\w\:\.]+)$'; $Commands{'module reload'}->{'function'} = ['', ['reloadmodule', 1]]; $Commands{'module reload'}->{'syntax'} = 'module reload '; $Commands{'modules list'}->{'pattern'} = '^modules list$'; $Commands{'modules list'}->{'function'} = ['', ['listmodules', 0]]; $Commands{'modules list'}->{'syntax'} = "modules list\r\nLists loaded modules."; # $Commands{'addmark'}->{'pattern'} = '^addmark (\w+)$'; # $Commands{'addmark'}->{'function'} = ['', ['addmark', 1]]; # $Commands{'addmark'}->{'syntax'} = 'addmark '; # $Commands{'listmarks'}->{'pattern'} = '^listmarks$'; # $Commands{'listmarks'}->{'function'} = ['', ['listmarks', 0]]; # $Commands{'listmarks'}->{'syntax'} = 'listmarks'; $Commands{'telopt'}->{'pattern'} = '^telopt (\w+) (\d+)$'; $Commands{'telopt'}->{'function'} = ['', ['sendopt', 2]]; $Commands{'telopt'}->{'syntax'} = 'telopt '; # $Commands{'listnodes'}->{'pattern'} = '^listnodes$'; # $Commands{'listnodes'}->{'function'} = ['', ['listnodes', 0]]; # $Commands{'listnodes'}->{'syntax'} = 'listnodes'; # $Commands{'listplanets'}->{'pattern'} = '^listplanets$'; # $Commands{'listplanets'}->{'function'} = ['', ['listplanets', 0]]; # $Commands{'listplanets'}->{'syntax'} = 'listplanets'; # $Commands{'nomap'}->{'pattern'} = '^nomap (.*)$'; # $Commands{'nomap'}->{'function'} = ['', ['addnomap', 1]]; # $Commands{'nomap'}->{'syntax'} = 'nomap '; # $Commands{'nodir'}->{'pattern'} = '^nodir (.*)$'; # $Commands{'nodir'}->{'function'} = ['', ['addnodir', 1]]; # $Commands{'nodir'}->{'syntax'} = 'nodir '; # $Commands{'savenodes'}->{'pattern'} = '^savenodes$'; # $Commands{'savenodes'}->{'function'} = ['', ['savenodes', 0]]; # $Commands{'savenodes'}->{'syntax'} = 'savenodes'; # $Commands{'mapper'}->{'pattern'} = '^mapper$'; # $Commands{'mapper'}->{'function'} = ['', ['open_mapper_socket', 0]]; # $Commands{'mapper'}->{'syntax'} = 'mapper'; # $Commands{'setplanet'}->{'pattern'} = '^setplanet (\w+)$'; # $Commands{'setplanet'}->{'function'} = ['', ['change_planet', 1]]; # $Commands{'setplanet'}->{'syntax'} = 'setplanet '; $Commands{'history'}->{'pattern'} = '^history(?: (.*))$'; $Commands{'history'}->{'function'} = ['', ['listhistory', 1]]; $Commands{'history'}->{'syntax'} = 'history'; $Commands{'histmax'}->{'pattern'} = '^histmax$'; $Commands{'histmax'}->{'function'} = ['', ['histmax', 0]]; $Commands{'histmax'}->{'syntax'} = 'histmax'; $Commands{'prompt'}->{'pattern'} = '^prompt (\S+)$'; $Commands{'prompt'}->{'function'} = ['', ['setprompt', 1]]; $Commands{'prompt'}->{'syntax'} = 'prompt '; $Commands{'read'}->{'pattern'} = '^read ([\w\.\/_-]+) (\w+)$'; $Commands{'read'}->{'function'} = ['', ['readinfile', 2]]; $Commands{'read'}->{'syntax'} = 'read echo|noecho'; $Commands{'set prompt type'}->{'pattern'} = '^set prompt type (none|import|ignore)$'; $Commands{'set prompt type'}->{'function'} = ['', ['setprompttype', 1]]; $Commands{'set prompt type'}->{'syntax'} = 'set prompt type '; $Commands{'read'}->{'pattern'} = '^read ([\w\.\/_-]+) (\w+)$'; $Commands{'read'}->{'function'} = ['readinfile', 2]; $Commands{'read'}->{'syntax'} = 'path start '; $Commands{'help'}->{'pattern'} = '^help(?: (.*))?$'; $Commands{'help'}->{'function'} = ['', ['help', 1]]; $Commands{'help'}->{'syntax'} = "help [command]\r\nDisplays the list of known commands, or help on the named command."; } ## JMM sub help { my $command = shift; if ($command) { my $cmdhash = $Commands{$command}; if (!$cmdhash) { printandprompt("I don't know the '$command' command, try #help with no parameters for a command list.\n"); return undef; } else { printandprompt("$command\n"); printandprompt("-" x length($command) . "\n"); printandprompt("\n"); printandprompt("$cmdhash->{syntax}\n"); # printandprompt("$cmdhash->{pattern}\n"); printandprompt("Calls the $cmdhash->{function}[1][0] function" . ($cmdhash->{function}[0] ? " in $cmdhash->{function}[0].\n" : ".\n")); printandprompt("Takes " . ($cmdhash->{function}[1][1] ? "up to $cmdhash->{function}[1][1] parameters.\n" : "no parameters.\n") ); return undef; } } else { printandprompt("Recognized #foo commands: \n"); foreach (sort keys %Commands) { printandprompt("$_\n"); } } return undef; } sub listhistory { my $data = $_[0]; my $min = $#commandhistory > 20 ? $#commandhistory - 20 : 0; my $max = $#commandhistory; if($data =~ /^(\d+) (\d+)$/) { $min = $1; $max = $2; } listhist($min, $max); return undef; } sub histmax { printandprompt($#commandhistory . " voll von $MAXHISTORY\n"); return undef; } sub readinfile { # Read a local file and send it to the mud. # Parameter: FileName my ($filename, $echo) = @_; if($echo eq 'echo') { $echo = 1; } elsif($echo eq 'noecho') { $echo = 0; } else { return("Syntax: #read \n"); } my ($name, $dir, $type) = fileparse($filename, '\..*'); if(length($dir) == 0) { $dir = cwd; } my $opentest = open(FILE, "< " . File::Spec->catfile($dir, $name . $type)); if(!defined($opentest)) { return("Read: Can't open file: $filename\n"); } while($line = ) { if($echo) { printandprompt($line); } $telnetprintqueue->enqueue($line); # print_socket("$line"); } close(FILE); } { ##### # TelnetClient modules # my %Modules; my %AddLaterTo; sub init_modules { %Modules = (); %AddLaterTo = (); } sub loadmodule { # Finds and loads a Module with the given name # eval returns undef when module not found, unreadable, etc. # Parameter: Module name my $modname = $_[0]; $modname = getmodulename($modname); debug("Module: Load module: $modname\n"); if (exists($Modules{$modname})) { return "Module: $modname is already loaded.\n"; } my $loaded; { # ignore warnings if subs are being redefined local $^W = 0; $loaded = eval "require $modname"; } if (!defined($loaded)) { return "Module: Can't load $modname: $@\n"; } # eval the 'new' function in case of errors! my $mod = eval "$modname->new();"; if (!defined($mod)) { return "Module: Can't create instance of $modname: $@\n"; } # Check if the module descends from TelnetClient::Module, if it doesnt, # complain! if (!$mod->isa('TelnetClient::Module')) { return "That module doesn't descend from TelnetClient::Module!"; } # Set temporary value in %Modules to show that this module is being # loaded. Used when loading two modules which depend upon each other $Modules{$modname} = 1; $mod->setDebug(\&debug); if (!checkdeps($mod)) { printandprompt("Modules: Warning: Failed to load all dependencies for: $modname.\n"); } # Set Screen, Socket, ChangePrompt in module $mod->setOutputSocket(sub {$telnetprintqueue->enqueue(@_)} ); # $mod->setOutputSocket(\&print_socket); $mod->setOutputScreen(sub {$screenprintqueue->enqueue(@_)} ); # $mod->setOutputScreen(\&printandprompt); $mod->setChangePrompt(\&setprompt); # Set/add external socket? (eg for mapper ;) addmodules($mod); $Modules{$modname} = $mod; addmodulecommands($modname); if ($AddLaterTo{$modname}) { # debug("Modules: AddLaterTo: " . $AddLaterTo{$modname} . "\n"); } if ($AddLaterTo{$modname}) { foreach my $m (@{$AddLaterTo{$modname}}) { # debug("Modules: Add $modname later to " . $m->getName . "\n"); $m->addModule($mod); } } return "Module: Loaded $modname\n"; } sub getmodulename { # If a modulename was entered without a leading 'TelnetClient::' # it is added. # Parameter: Modulename my ($modname) = @_; debug("getmodulename: $modname\n"); unless($modname =~ /^TelnetClient::/) { debug("getmodulename: Nope\n"); return 'TelnetClient::' . $modname; } return $modname; } sub reloadmodule { # Temporarily save the contents of a module, delete all references, # and reload. # Parameter: Module Name my $modname = $_[0]; $modname = getmodulename($modname); if (!exists($Modules{$modname})) { return "Module: $modname is not loaded.\n"; } my $mod = $Modules{$modname}; my $tmpd = File::Spec->tmpdir(); if (!length($tmpd)) { $tmpd = cwd; } my $file = File::Spec->catfile($tmpd, "modulesettings.tmp"); my $res = $mod->saveModule($file); if ($res ne "Module: Saved.\n") { return "Module: Can't save module, canceled reload.\n"; } my $unload = 1; foreach $m (values %Modules) { # debug("Modules: " . ref($m) . " reload $modname\n"); if ((ref($m) ne $modname) && (!$m->reloadModule($modname))) { $unload = 0; } } if (!$unload) { return "Module: Can't unload module, canceled reload.\n"; } $Modules{$modname} = undef; my $modrealname = File::Spec->catfile(split(/::/, $modname)) . '.pm'; debug("Modules: RealName: $modrealname\n"); delete $INC{$modrealname}; # debug(Dumper(%INC). "\n"); $res = loadmodules($file); unlink $file; return $res; } sub unloadmodule { # Unload the module from the Methods hashes etc. # !! Check to see if any other modules are depending on this one, # and complain first!! # Parameter: Module Name my ($modname) = @_; $modname = getmodulename($modname); if(!exists($Modules{$modname})) { return "Module: $modname is not loaded.\n"; } my $mod = $Modules{$modname}; my $unload = 1; my $mods = ''; foreach $m (values %Modules) { if(grep(/$modname/, $m->getDeps())) { $mods .= $m->getName() . ', '; $unload = 0; } } if(!$unload) { substr($mods, -2, 2, ''); return "Module: Can't unload $modname, $mods are depending on it!\n"; } # Remove commands from %Commands foreach $c ($mod->getCommands) { delete $Commands{$c->{'name'}}; } delete $InputMethods{$modname}; delete $ParseMethods{$modname}; delete $RawDataMethods{$modname}; delete $OptionMethods{$modname}; delete $InputSockets{$modname}; delete $ExternalSockets{$modname}; delete $PromptMethods{$modname}; return "Module: Unloaded $modname.\n"; } sub checkdeps { # Check if the dependencies for this module are loaded # (or are being loaded, value 1 in %Modules) # Parameter: Module object my ($mod) = @_; my @deps = $mod->getDeps(); # debug("Modules: Dependencies: " . @deps . "\n"); foreach $d (@deps) { # debug("Modules: Add: $d\n"); if (!defined($Modules{$d})) { printandprompt(loadmodule($d)); # loadmodule($d); if (!defined($Modules{$d})) { # debug("Modules: Not defined: $d\n"); return 0; } } } return 1; } sub addmodules { # Trys to give each currently loaded module to the new module # Parameter: Module object my ($mod) = @_; # my @mods = values(%Modules); debug("Module: Add modules to: " . $mod->getName() . "\n"); while (my ($mname, $m) = each %Modules) { if ($m != 1) { # debug("Module: Add " . $m->getName() . "\n"); $mod->addModule($m); } elsif ($mname ne $mod->getName()) { # debug("Module: Don't add " . $mname . "\n"); # when loading is finished, add loaded module to: $AddLaterTo{$mname}[scalar @{$AddLaterTo{$mname}}] = $mod; } } } sub addmodulecommands { # Adds any commands in the module to the commands list # Parameter: Module name my $modname = $_[0]; my $mod = $Modules{$modname}; my @commands = $mod->getCommands(); foreach $c (@commands) { $Commands{$c->{'name'}}->{'pattern'} = $c->{'pattern'}; $Commands{$c->{'name'}}->{'function'} = [$modname, $c->{'function'}]; $Commands{$c->{'name'}}->{'syntax'} = $c->{'syntax'}; } # Add Input/Parse commands to appropriate Hashes! if ($mod->getInputYN()) { $InputMethods{$modname} = $mod->getInputMethod(); } if ($mod->getParseYN()) { $ParseMethods{$modname} = $mod->getParseMethod(); } if ($mod->getRawDataYN()) { $RawDataMethods{$modname} = $mod->getRawDataMethod(); } if ($mod->getTelnetOptions()) { $OptionMethods{$modname} = $mod->getTelnetOptionMethods(); } if ($mod->getInputSocketYN()) { $InputSockets{$modname} = $mod->getInputSocket(); } if ($mod->getExternalSocketYN()) { $ExternalSockets{$modname} = $mod->getExternalSocket(); addexternalsocket($mod->getExternalSocket()); } if ($mod->getPromptYN()) { $PromptMethods{$modname} = $mod->getPromptMethod(); } } sub getmodule { # Returns a module with the given name # Parameter: Module name my $modname = $_[0]; if (defined($Modules{$modname})) { return $Modules{$modname}; } return undef; } sub modulemethod { # Call a module method # Parameter: Module name, method name, parameters my ($modname, $meth, @para) = @_; my $mod = getmodule($modname); my $answer; if (defined($mod)) { # Call method with error catching! eval {$answer = $mod->$meth(@para)}; if ($@) { return "Module: Error in Module $modname: $@\n"; } return $answer; } return undef; } sub savemodules { # Call saveModule method for all loaded modules # Parameter: None debug("Module: savemodules\n"); my $res; while (my ($modname, $mod) = each %Modules) { $res = $mod->saveModule("modulesettings.txt"); } return $res; } sub loadmodules { # Load all saved modules # Parameter: Filename my $file = $_[0]; debug("Module: Load modules\n"); if (open MODULES, "< $file") { while ($line = ) { debug("Modules: Parse: $line"); # printandprompt($line); chomp($line); parselocalcommand($line); } close(MODULES); return "Modules: loaded.\n"; } else { return "Can't open file: $!\n"; } } sub listmodules { # List all loaded modules # Parameter: None while (my ($key, $value) = each %Modules) { if ($value != 1) { printandprompt($key . "\n"); } } } sub availmodules { # Show a list of available modules that can be loaded.. # (Searches @INC for all 'TelnetClient::' files.) # Parameter: None my %amods; find( sub { if ($File::Find::name =~ /\/TelnetClient\/(.*).pm$/) { my $name = $1; $name =~ s/\//::/; $amods{$name} = 1 unless $name eq 'Module'; } }, @INC); foreach my $n (sort keys %amods) { printandprompt "$n\n"; } } } sub ismember : locked { # Parameter: String item to find, String/Array ref list my ($item, $array) = @_; # debug("ismember params :" . @_ . "\n"); # debug("ismember item :$item\n"); # debug("ismember array :$array\n"); if(!ref($array) and ($array ne '')) { # Its a string list if($item =~ /^(?:$array)(.*)/i) # if($item =~ /$array/i) { debug("ismember :" . $1 . "\n"); return 1; } return -1; } for(my $i = 0; $i < @{$array}; $i++) { my $arritem = $array->[$i]; if(lc($item) eq $arritem) { return $i; } } return -1; } ##### # Logging # sub createlog : locked { # Parameter: logfilename, new/append my ($param) = @_; if($param =~ /^(\S+)(?:\s?(\w+))?$/) { $name = $1; $mode = $2; } if($LOG) { debug("createlog : Log exists"); close($LOGFILE); $LOG = ''; return "Logfile $LOG closed\n"; } my $dir = dirname($name); if($dir) { $PWD = $dir; } $LOG = $PWD . '/' . basename($name); debug("createlog: new log $LOG\n"); if(-e $LOG && (!-T $LOG || !-o $LOG)) { $LOG = ''; return "$LOG is a binary file, or doesn't belong to you. Won't overwrite\n"; } my $openmode = ">"; if(!$mode || $mode eq 'append') { $openmode = ">>"; } if(!open($LOGFILE, $openmode, $LOG)) { return "Couldn't open $LOG\n"; } return "Opened log $LOG\n"; } sub writelog : locked { # Parameter: Text to write my ($text) = @_; print $LOGFILE $text; } ##### # Debug ausgaben # sub debug : locked { # Parameter: None. if ($DEBUG eq 'on') { print STDOUT "$_[0]"; } elsif ($DEBUG eq 'log') { # Append text to debug log my $filename = './debug_telnetclient.log'; open(DEBUGFILE, ">>", $filename) or die "Can't open $filename: $!\n"; print DEBUGFILE "$_[0]"; close(DEBUGFILE); } } # # %triggers -> $triggers{"pattern"} = "command1;command2" # ## #path start <.name> ## #path stop ## #path list ## #path add ## #path delete ## #path show # path functions should return error message instead of calling p+p ? # ## %commands{commandname}{pattern} ## %commands{commandname}{function} ## %commands{commandname}{syntax} ## = [, , ] ? ## #read - read a file and send to mud open(FILE, "< name") true/undef ## #alias new - create new alias ## #alias list # #alias edit - delete old alias, create new (put text of old in $cl ## #alias delete ## aliases as command liste with ; as seperartor char ($CommandCharacter) # path is an alias with name '#path' ? # #inventory add - keep date when item accquired, count repairs? # ## #module load ?? # zB #module load #path -> requires #alias ! (getDeps) ## TelnetClient::Module -> other modules need this module to add commands etc. # TelnetClient::Mud::Path # TelnetClient::Mud::Inventory .. # TelnetClient::CommandHistory # TelnetClient::Mud::Map::Directions .. # TelnetClient::Mud::Map::Marks # # Module/Ding der die Tasten definiert? (CTRL-B, cursor etc. pp. ??) # Save modules/contents!? # # Room scripts! Room Desc, Room Name, show + change! # ## Load list of muds/names etc from .telnetclientrc?! ## Answer EOR telnet option with DO, and use text to recognise a ## prompt. Put this text as a prompt in the input line? Option #set prompt ## to change behaviour # ## #schau - macht schau und faengt alles was bis 'Ausgaenge' kommt ## erzeugt eine liste von alle Nouns (remove .|,|~|;) ## presents a word in the commandline, with a choice: y/n/p/c (yes, no, ## pause, cancel) ## ~=/^[A-Z]/ # <20:29> Pfy verkuendet dem Clan: $zeichen = char(255); $text =~ /$zeichen/ ## protect all variables that should be locked with a synchronised function # ## Threading/Non-threading: ## If no threads available ($Config{usethreads}), call a function to create and ## return a handle to the mud-socket, and use IO::Select together with STDIN in ## the main loop, call getch() or mud_data as normal. # ## #modules list # #overlay list commands for # #overlay load # #overlay translate to # #overlay save current to # ?? # # PerlMud Module? Socket to perlmud, show missed d-chat lines, show d-code, # send on d-code?? :) # # #count new ?? # Or trigger with parsing?? # # #help! # and POD! # Tests ? -> man Test::Tutorial # test all functions !? # # #module unload? - removes the methods of modules from the hashes.. # - recheck the YN method when trying to run ParseMethod etc.. sub TELNET_IAC () {255}; # interpret as command: sub TELNET_DONT () {254}; # you are not to use option sub TELNET_DO () {253}; # please, you use option sub TELNET_WONT () {252}; # I won't use option sub TELNET_WILL () {251}; # I will use option sub TELNET_SB () {250}; # interpret as subnegotiation sub TELNET_GA () {249}; # you may reverse the line sub TELNET_EL () {248}; # erase the current line sub TELNET_EC () {247}; # erase the current character sub TELNET_AYT () {246}; # are you there sub TELNET_AO () {245}; # abort output--but let prog finish sub TELNET_IP () {244}; # interrupt process--permanently sub TELNET_BREAK () {243}; # break sub TELNET_DM () {242}; # data mark--for connect. cleaning sub TELNET_NOP () {241}; # nop sub TELNET_SE () {240}; # end sub negotiation sub TELNET_EOR () {239}; # end of record (transparent mode) sub TELNET_ABORT () {238}; # Abort process sub TELNET_SUSP () {237}; # Suspend process sub TELNET_EOF () {236}; # End of file sub TELNET_SYNCH () {242}; # for telfunc calls sub TELOPT_BINARY () {0}; # Binary Transmission sub TELOPT_ECHO () {1}; # Echo sub TELOPT_RCP () {2}; # Reconnection sub TELOPT_SGA () {3}; # Suppress Go Ahead sub TELOPT_NAMS () {4}; # Approx Message Size Negotiation sub TELOPT_STATUS () {5}; # Status sub TELOPT_TM () {6}; # Timing Mark sub TELOPT_RCTE () {7}; # Remote Controlled Trans and Echo sub TELOPT_NAOL () {8}; # Output Line Width sub TELOPT_NAOP () {9}; # Output Page Size sub TELOPT_NAOCRD () {10}; # Output Carriage-Return Disposition sub TELOPT_NAOHTS () {11}; # Output Horizontal Tab Stops sub TELOPT_NAOHTD () {12}; # Output Horizontal Tab Disposition sub TELOPT_NAOFFD () {13}; # Output Formfeed Disposition sub TELOPT_NAOVTS () {14}; # Output Vertical Tabstops sub TELOPT_NAOVTD () {15}; # Output Vertical Tab Disposition sub TELOPT_NAOLFD () {16}; # Output Linefeed Disposition sub TELOPT_XASCII () {17}; # Extended ASCII sub TELOPT_LOGOUT () {18}; # Logout sub TELOPT_BM () {19}; # Byte Macro sub TELOPT_DET () {20}; # Data Entry Terminal sub TELOPT_SUPDUP () {21}; # SUPDUP sub TELOPT_SUPDUPOUTPUT () {22}; # SUPDUP Output sub TELOPT_SNDLOC () {23}; # Send Location sub TELOPT_TTYPE () {24}; # Terminal Type sub TELOPT_EOR () {25}; # End of Record sub TELOPT_TUID () {26}; # TACACS User Identification sub TELOPT_OUTMRK () {27}; # Output Marking sub TELOPT_TTYLOC () {28}; # Terminal Location Number sub TELOPT_3270REGIME () {29}; # Telnet 3270 Regime sub TELOPT_X3PAD () {30}; # X.3 PAD sub TELOPT_NAWS () {31}; # Negotiate About Window Size sub TELOPT_TSPEED () {32}; # Terminal Speed sub TELOPT_LFLOW () {33}; # Remote Flow Control sub TELOPT_LINEMODE () {34}; # Linemode sub TELOPT_XDISPLOC () {35}; # X Display Location sub TELOPT_OLD_ENVIRON () {36}; # Environment Option sub TELOPT_AUTHENTICATION () {37}; # Authentication Option sub TELOPT_ENCRYPT () {38}; # Encryption Option sub TELOPT_NEW_ENVIRON () {39}; # New Environment Option sub TELOPT_MCCP () {86}; # Mud Compression Protocol sub TELOPT_MSP () {90}; # Mud Sound Protocol sub TELOPT_MXP () {91}; # Mud eXtension Protocol sub TELOPT_EXOPL () {255}; # Extended-Options-List