File: qcommon\cmd.c

    1 /*
    2 Copyright (C) 1997-2001 Id Software, Inc.
    3 
    4 This program is free software; you can redistribute it and/or
    5 modify it under the terms of the GNU General Public License
    6 as published by the Free Software Foundation; either version 2
    7 of the License, or (at your option) any later version.
    8 
    9 This program is distributed in the hope that it will be useful,
   10 but WITHOUT ANY WARRANTY; without even the implied warranty of
   11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  
   12 
   13 See the GNU General Public License for more details.
   14 
   15 You should have received a copy of the GNU General Public License
   16 along with this program; if not, write to the Free Software
   17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
   18 
   19 */
   20 // cmd.c -- Quake script command processing module
   21 
   22 #include "qcommon.h"
   23 
   24 void Cmd_ForwardToServer (void);
   25 
   26 #define MAX_ALIAS_NAME  32
   27 
   28 typedef struct cmdalias_s
   29 {
   30         struct cmdalias_s       *next;
   31         char    name[MAX_ALIAS_NAME];
   32         char    *value;
   33 } cmdalias_t;
   34 
   35 cmdalias_t      *cmd_alias;
   36 
   37 qboolean        cmd_wait;
   38 
   39 #define ALIAS_LOOP_COUNT        16
   40 int             alias_count;            // for detecting runaway loops
   41 
   42 
   43 //=============================================================================
   44 
   45 /*
   46 ============
   47 Cmd_Wait_f
   48 
   49 Causes execution of the remainder of the command buffer to be delayed until
   50 next frame.  This allows commands like:
   51 bind g "impulse 5 ; +attack ; wait ; -attack ; impulse 2"
   52 ============
   53 */
   54 void Cmd_Wait_f (void)
   55 {
   56         cmd_wait = true;
   57 }
   58 
   59 
   60 /*
   61 =============================================================================
   62 
   63                                                 COMMAND BUFFER
   64 
   65 =============================================================================
   66 */
   67 
   68 sizebuf_t       cmd_text;
   69 byte            cmd_text_buf[8192];
   70 
   71 byte            defer_text_buf[8192];
   72 
   73 /*
   74 ============
   75 Cbuf_Init
   76 ============
   77 */
   78 void Cbuf_Init (void)
   79 {
   80         SZ_Init (&cmd_text, cmd_text_buf, sizeof(cmd_text_buf));
   81 }
   82 
   83 /*
   84 ============
   85 Cbuf_AddText
   86 
   87 Adds command text at the end of the buffer
   88 ============
   89 */
   90 void Cbuf_AddText (char *text)
   91 {
   92         int             l;
   93         
   94         l = strlen (text);
   95 
   96         if (cmd_text.cursize + l >= cmd_text.maxsize)
   97         {
   98                 Com_Printf ("Cbuf_AddText: overflow\n");
   99                 return;
  100         }
  101         SZ_Write (&cmd_text, text, strlen (text));
  102 }
  103 
  104 
  105 /*
  106 ============
  107 Cbuf_InsertText
  108 
  109 Adds command text immediately after the current command
  110 Adds a \n to the text
  111 FIXME: actually change the command buffer to do less copying
  112 ============
  113 */
  114 void Cbuf_InsertText (char *text)
  115 {
  116         char    *temp;
  117         int             templen;
  118 
  119 // copy off any commands still remaining in the exec buffer
  120         templen = cmd_text.cursize;
  121         if (templen)
  122         {
  123                 temp = Z_Malloc (templen);
  124                 memcpy (temp, cmd_text.data, templen);
  125                 SZ_Clear (&cmd_text);
  126         }
  127         else
  128                 temp = NULL;    // shut up compiler
  129                 
  130 // add the entire text of the file
  131         Cbuf_AddText (text);
  132         
  133 // add the copied off data
  134         if (templen)
  135         {
  136                 SZ_Write (&cmd_text, temp, templen);
  137                 Z_Free (temp);
  138         }
  139 }
  140 
  141 
  142 /*
  143 ============
  144 Cbuf_CopyToDefer
  145 ============
  146 */
  147 void Cbuf_CopyToDefer (void)
  148 {
  149         memcpy(defer_text_buf, cmd_text_buf, cmd_text.cursize);
  150         defer_text_buf[cmd_text.cursize] = 0;
  151         cmd_text.cursize = 0;
  152 }
  153 
  154 /*
  155 ============
  156 Cbuf_InsertFromDefer
  157 ============
  158 */
  159 void Cbuf_InsertFromDefer (void)
  160 {
  161         Cbuf_InsertText (defer_text_buf);
  162         defer_text_buf[0] = 0;
  163 }
  164 
  165 
  166 /*
  167 ============
  168 Cbuf_ExecuteText
  169 ============
  170 */
  171 void Cbuf_ExecuteText (int exec_when, char *text)
  172 {
  173         switch (exec_when)
  174         {
  175         case EXEC_NOW:
  176                 Cmd_ExecuteString (text);
  177                 break;
  178         case EXEC_INSERT:
  179                 Cbuf_InsertText (text);
  180                 break;
  181         case EXEC_APPEND:
  182                 Cbuf_AddText (text);
  183                 break;
  184         default:
  185                 Com_Error (ERR_FATAL, "Cbuf_ExecuteText: bad exec_when");
  186         }
  187 }
  188 
  189 /*
  190 ============
  191 Cbuf_Execute
  192 ============
  193 */
  194 void Cbuf_Execute (void)
  195 {
  196         int             i;
  197         char    *text;
  198         char    line[1024];
  199         int             quotes;
  200 
  201         alias_count = 0;                // don't allow infinite alias loops
  202 
  203         while (cmd_text.cursize)
  204         {
  205 // find a \n or ; line break
  206                 text = (char *)cmd_text.data;
  207 
  208                 quotes = 0;
  209                 for (i=0 ; i< cmd_text.cursize ; i++)
  210                 {
  211                         if (text[i] == '"')
  212                                 quotes++;
  213                         if ( !(quotes&1) &&  text[i] == ';')
  214                                 break;  // don't break if inside a quoted string
  215                         if (text[i] == '\n')
  216                                 break;
  217                 }
  218                         
  219                                 
  220                 memcpy (line, text, i);
  221                 line[i] = 0;
  222                 
  223 // delete the text from the command buffer and move remaining commands down
  224 // this is necessary because commands (exec, alias) can insert data at the
  225 // beginning of the text buffer
  226 
  227                 if (i == cmd_text.cursize)
  228                         cmd_text.cursize = 0;
  229                 else
  230                 {
  231                         i++;
  232                         cmd_text.cursize -= i;
  233                         memmove (text, text+i, cmd_text.cursize);
  234                 }
  235 
  236 // execute the command line
  237                 Cmd_ExecuteString (line);
  238                 
  239                 if (cmd_wait)
  240                 {
  241                         // skip out while text still remains in buffer, leaving it
  242                         // for next frame
  243                         cmd_wait = false;
  244                         break;
  245                 }
  246         }
  247 }
  248 
  249 
  250 /*
  251 ===============
  252 Cbuf_AddEarlyCommands
  253 
  254 Adds command line parameters as script statements
  255 Commands lead with a +, and continue until another +
  256 
  257 Set commands are added early, so they are guaranteed to be set before
  258 the client and server initialize for the first time.
  259 
  260 Other commands are added late, after all initialization is complete.
  261 ===============
  262 */
  263 void Cbuf_AddEarlyCommands (qboolean clear)
  264 {
  265         int             i;
  266         char    *s;
  267 
  268         for (i=0 ; i<COM_Argc() ; i++)
  269         {
  270                 s = COM_Argv(i);
  271                 if (strcmp (s, "+set"))
  272                         continue;
  273                 Cbuf_AddText (va("set %s %s\n", COM_Argv(i+1), COM_Argv(i+2)));
  274                 if (clear)
  275                 {
  276                         COM_ClearArgv(i);
  277                         COM_ClearArgv(i+1);
  278                         COM_ClearArgv(i+2);
  279                 }
  280                 i+=2;
  281         }
  282 }
  283 
  284 /*
  285 =================
  286 Cbuf_AddLateCommands
  287 
  288 Adds command line parameters as script statements
  289 Commands lead with a + and continue until another + or -
  290 quake +vid_ref gl +map amlev1
  291 
  292 Returns true if any late commands were added, which
  293 will keep the demoloop from immediately starting
  294 =================
  295 */
  296 qboolean Cbuf_AddLateCommands (void)
  297 {
  298         int             i, j;
  299         int             s;
  300         char    *text, *build, c;
  301         int             argc;
  302         qboolean        ret;
  303 
  304 // build the combined string to parse from
  305         s = 0;
  306         argc = COM_Argc();
  307         for (i=1 ; i<argc ; i++)
  308         {
  309                 s += strlen (COM_Argv(i)) + 1;
  310         }
  311         if (!s)
  312                 return false;
  313                 
  314         text = Z_Malloc (s+1);
  315         text[0] = 0;
  316         for (i=1 ; i<argc ; i++)
  317         {
  318                 strcat (text,COM_Argv(i));
  319                 if (i != argc-1)
  320                         strcat (text, " ");
  321         }
  322         
  323 // pull out the commands
  324         build = Z_Malloc (s+1);
  325         build[0] = 0;
  326         
  327         for (i=0 ; i<s-1 ; i++)
  328         {
  329                 if (text[i] == '+')
  330                 {
  331                         i++;
  332 
  333                         for (j=i ; (text[j] != '+') && (text[j] != '-') && (text[j] != 0) ; j++)
  334                                 ;
  335 
  336                         c = text[j];
  337                         text[j] = 0;
  338                         
  339                         strcat (build, text+i);
  340                         strcat (build, "\n");
  341                         text[j] = c;
  342                         i = j-1;
  343                 }
  344         }
  345 
  346         ret = (build[0] != 0);
  347         if (ret)
  348                 Cbuf_AddText (build);
  349         
  350         Z_Free (text);
  351         Z_Free (build);
  352 
  353         return ret;
  354 }
  355 
  356 
  357 /*
  358 ==============================================================================
  359 
  360                                                 SCRIPT COMMANDS
  361 
  362 ==============================================================================
  363 */
  364 
  365 
  366 /*
  367 ===============
  368 Cmd_Exec_f
  369 ===============
  370 */
  371 void Cmd_Exec_f (void)
  372 {
  373         char    *f, *f2;
  374         int             len;
  375 
  376         if (Cmd_Argc () != 2)
  377         {
  378                 Com_Printf ("exec <filename> : execute a script file\n");
  379                 return;
  380         }
  381 
  382         len = FS_LoadFile (Cmd_Argv(1), (void **)&f);
  383         if (!f)
  384         {
  385                 Com_Printf ("couldn't exec %s\n",Cmd_Argv(1));
  386                 return;
  387         }
  388         Com_Printf ("execing %s\n",Cmd_Argv(1));
  389         
  390         // the file doesn't have a trailing 0, so we need to copy it off
  391         f2 = Z_Malloc(len+1);
  392         memcpy (f2, f, len);
  393         f2[len] = 0;
  394 
  395         Cbuf_InsertText (f2);
  396 
  397         Z_Free (f2);
  398         FS_FreeFile (f);
  399 }
  400 
  401 
  402 /*
  403 ===============
  404 Cmd_Echo_f
  405 
  406 Just prints the rest of the line to the console
  407 ===============
  408 */
  409 void Cmd_Echo_f (void)
  410 {
  411         int             i;
  412         
  413         for (i=1 ; i<Cmd_Argc() ; i++)
  414                 Com_Printf ("%s ",Cmd_Argv(i));
  415         Com_Printf ("\n");
  416 }
  417 
  418 /*
  419 ===============
  420 Cmd_Alias_f
  421 
  422 Creates a new command that executes a command string (possibly ; seperated)
  423 ===============
  424 */
  425 void Cmd_Alias_f (void)
  426 {
  427         cmdalias_t      *a;
  428         char            cmd[1024];
  429         int                     i, c;
  430         char            *s;
  431 
  432         if (Cmd_Argc() == 1)
  433         {
  434                 Com_Printf ("Current alias commands:\n");
  435                 for (a = cmd_alias ; a ; a=a->next)
  436                         Com_Printf ("%s : %s\n", a->name, a->value);
  437                 return;
  438         }
  439 
  440         s = Cmd_Argv(1);
  441         if (strlen(s) >= MAX_ALIAS_NAME)
  442         {
  443                 Com_Printf ("Alias name is too long\n");
  444                 return;
  445         }
  446 
  447         // if the alias already exists, reuse it
  448         for (a = cmd_alias ; a ; a=a->next)
  449         {
  450                 if (!strcmp(s, a->name))
  451                 {
  452                         Z_Free (a->value);
  453                         break;
  454                 }
  455         }
  456 
  457         if (!a)
  458         {
  459                 a = Z_Malloc (sizeof(cmdalias_t));
  460                 a->next = cmd_alias;
  461                 cmd_alias = a;
  462         }
  463         strcpy (a->name, s);    
  464 
  465 // copy the rest of the command line
  466         cmd[0] = 0;             // start out with a null string
  467         c = Cmd_Argc();
  468         for (i=2 ; i< c ; i++)
  469         {
  470                 strcat (cmd, Cmd_Argv(i));
  471                 if (i != (c - 1))
  472                         strcat (cmd, " ");
  473         }
  474         strcat (cmd, "\n");
  475         
  476         a->value = CopyString (cmd);
  477 }
  478 
  479 /*
  480 =============================================================================
  481 
  482                                         COMMAND EXECUTION
  483 
  484 =============================================================================
  485 */
  486 
  487 typedef struct cmd_function_s
  488 {
  489         struct cmd_function_s   *next;
  490         char                                    *name;
  491         xcommand_t                              function;
  492 } cmd_function_t;
  493 
  494 
  495 static  int                     cmd_argc;
  496 static  char            *cmd_argv[MAX_STRING_TOKENS];
  497 static  char            *cmd_null_string = "";
  498 static  char            cmd_args[MAX_STRING_CHARS];
  499 
  500 static  cmd_function_t  *cmd_functions;         // possible commands to execute
  501 
  502 /*
  503 ============
  504 Cmd_Argc
  505 ============
  506 */
  507 int             Cmd_Argc (void)
  508 {
  509         return cmd_argc;
  510 }
  511 
  512 /*
  513 ============
  514 Cmd_Argv
  515 ============
  516 */
  517 char    *Cmd_Argv (int arg)
  518 {
  519         if ( (unsigned)arg >= cmd_argc )
  520                 return cmd_null_string;
  521         return cmd_argv[arg];   
  522 }
  523 
  524 /*
  525 ============
  526 Cmd_Args
  527 
  528 Returns a single string containing argv(1) to argv(argc()-1)
  529 ============
  530 */
  531 char            *Cmd_Args (void)
  532 {
  533         return cmd_args;
  534 }
  535 
  536 
  537 /*
  538 ======================
  539 Cmd_MacroExpandString
  540 ======================
  541 */
  542 char *Cmd_MacroExpandString (char *text)
  543 {
  544         int             i, j, count, len;
  545         qboolean        inquote;
  546         char    *scan;
  547         static  char    expanded[MAX_STRING_CHARS];
  548         char    temporary[MAX_STRING_CHARS];
  549         char    *token, *start;
  550 
  551         inquote = false;
  552         scan = text;
  553 
  554         len = strlen (scan);
  555         if (len >= MAX_STRING_CHARS)
  556         {
  557                 Com_Printf ("Line exceeded %i chars, discarded.\n", MAX_STRING_CHARS);
  558                 return NULL;
  559         }
  560 
  561         count = 0;
  562 
  563         for (i=0 ; i<len ; i++)
  564         {
  565                 if (scan[i] == '"')
  566                         inquote ^= 1;
  567                 if (inquote)
  568                         continue;       // don't expand inside quotes
  569                 if (scan[i] != '$')
  570                         continue;
  571                 // scan out the complete macro
  572                 start = scan+i+1;
  573                 token = COM_Parse (&start);
  574                 if (!start)
  575                         continue;
  576         
  577                 token = Cvar_VariableString (token);
  578 
  579                 j = strlen(token);
  580                 len += j;
  581                 if (len >= MAX_STRING_CHARS)
  582                 {
  583                         Com_Printf ("Expanded line exceeded %i chars, discarded.\n", MAX_STRING_CHARS);
  584                         return NULL;
  585                 }
  586 
  587                 strncpy (temporary, scan, i);
  588                 strcpy (temporary+i, token);
  589                 strcpy (temporary+i+j, start);
  590 
  591                 strcpy (expanded, temporary);
  592                 scan = expanded;
  593                 i--;
  594 
  595                 if (++count == 100)
  596                 {
  597                         Com_Printf ("Macro expansion loop, discarded.\n");
  598                         return NULL;
  599                 }
  600         }
  601 
  602         if (inquote)
  603         {
  604                 Com_Printf ("Line has unmatched quote, discarded.\n");
  605                 return NULL;
  606         }
  607 
  608         return scan;
  609 }
  610 
  611 
  612 /*
  613 ============
  614 Cmd_TokenizeString
  615 
  616 Parses the given string into command line tokens.
  617 $Cvars will be expanded unless they are in a quoted token
  618 ============
  619 */
  620 void Cmd_TokenizeString (char *text, qboolean macroExpand)
  621 {
  622         int             i;
  623         char    *com_token;
  624 
  625 // clear the args from the last string
  626         for (i=0 ; i<cmd_argc ; i++)
  627                 Z_Free (cmd_argv[i]);
  628                 
  629         cmd_argc = 0;
  630         cmd_args[0] = 0;
  631         
  632         // macro expand the text
  633         if (macroExpand)
  634                 text = Cmd_MacroExpandString (text);
  635         if (!text)
  636                 return;
  637 
  638         while (1)
  639         {
  640 // skip whitespace up to a /n
  641                 while (*text && *text <= ' ' && *text != '\n')
  642                 {
  643                         text++;
  644                 }
  645                 
  646                 if (*text == '\n')
  647                 {       // a newline seperates commands in the buffer
  648                         text++;
  649                         break;
  650                 }
  651 
  652                 if (!*text)
  653                         return;
  654 
  655                 // set cmd_args to everything after the first arg
  656                 if (cmd_argc == 1)
  657                 {
  658                         int             l;
  659 
  660                         strcpy (cmd_args, text);
  661 
  662                         // strip off any trailing whitespace
  663                         l = strlen(cmd_args) - 1;
  664                         for ( ; l >= 0 ; l--)
  665                                 if (cmd_args[l] <= ' ')
  666                                         cmd_args[l] = 0;
  667                                 else
  668                                         break;
  669                 }
  670                         
  671                 com_token = COM_Parse (&text);
  672                 if (!text)
  673                         return;
  674 
  675                 if (cmd_argc < MAX_STRING_TOKENS)
  676                 {
  677                         cmd_argv[cmd_argc] = Z_Malloc (strlen(com_token)+1);
  678                         strcpy (cmd_argv[cmd_argc], com_token);
  679                         cmd_argc++;
  680                 }
  681         }
  682         
  683 }
  684 
  685 
  686 /*
  687 ============
  688 Cmd_AddCommand
  689 ============
  690 */
  691 void    Cmd_AddCommand (char *cmd_name, xcommand_t function)
  692 {
  693         cmd_function_t  *cmd;
  694         
  695 // fail if the command is a variable name
  696         if (Cvar_VariableString(cmd_name)[0])
  697         {
  698                 Com_Printf ("Cmd_AddCommand: %s already defined as a var\n", cmd_name);
  699                 return;
  700         }
  701         
  702 // fail if the command already exists
  703         for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
  704         {
  705                 if (!strcmp (cmd_name, cmd->name))
  706                 {
  707                         Com_Printf ("Cmd_AddCommand: %s already defined\n", cmd_name);
  708                         return;
  709                 }
  710         }
  711 
  712         cmd = Z_Malloc (sizeof(cmd_function_t));
  713         cmd->name = cmd_name;
  714         cmd->function = function;
  715         cmd->next = cmd_functions;
  716         cmd_functions = cmd;
  717 }
  718 
  719 /*
  720 ============
  721 Cmd_RemoveCommand
  722 ============
  723 */
  724 void    Cmd_RemoveCommand (char *cmd_name)
  725 {
  726         cmd_function_t  *cmd, **back;
  727 
  728         back = &cmd_functions;
  729         while (1)
  730         {
  731                 cmd = *back;
  732                 if (!cmd)
  733                 {
  734                         Com_Printf ("Cmd_RemoveCommand: %s not added\n", cmd_name);
  735                         return;
  736                 }
  737                 if (!strcmp (cmd_name, cmd->name))
  738                 {
  739                         *back = cmd->next;
  740                         Z_Free (cmd);
  741                         return;
  742                 }
  743                 back = &cmd->next;
  744         }
  745 }
  746 
  747 /*
  748 ============
  749 Cmd_Exists
  750 ============
  751 */
  752 qboolean        Cmd_Exists (char *cmd_name)
  753 {
  754         cmd_function_t  *cmd;
  755 
  756         for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
  757         {
  758                 if (!strcmp (cmd_name,cmd->name))
  759                         return true;
  760         }
  761 
  762         return false;
  763 }
  764 
  765 
  766 
  767 /*
  768 ============
  769 Cmd_CompleteCommand
  770 ============
  771 */
  772 char *Cmd_CompleteCommand (char *partial)
  773 {
  774         cmd_function_t  *cmd;
  775         int                             len;
  776         cmdalias_t              *a;
  777         
  778         len = strlen(partial);
  779         
  780         if (!len)
  781                 return NULL;
  782                 
  783 // check for exact match
  784         for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
  785                 if (!strcmp (partial,cmd->name))
  786                         return cmd->name;
  787         for (a=cmd_alias ; a ; a=a->next)
  788                 if (!strcmp (partial, a->name))
  789                         return a->name;
  790 
  791 // check for partial match
  792         for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
  793                 if (!strncmp (partial,cmd->name, len))
  794                         return cmd->name;
  795         for (a=cmd_alias ; a ; a=a->next)
  796                 if (!strncmp (partial, a->name, len))
  797                         return a->name;
  798 
  799         return NULL;
  800 }
  801 
  802 
  803 /*
  804 ============
  805 Cmd_ExecuteString
  806 
  807 A complete command line has been parsed, so try to execute it
  808 FIXME: lookupnoadd the token to speed search?
  809 ============
  810 */
  811 void    Cmd_ExecuteString (char *text)
  812 {       
  813         cmd_function_t  *cmd;
  814         cmdalias_t              *a;
  815 
  816         Cmd_TokenizeString (text, true);
  817                         
  818         // execute the command line
  819         if (!Cmd_Argc())
  820                 return;         // no tokens
  821 
  822         // check functions
  823         for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
  824         {
  825                 if (!Q_strcasecmp (cmd_argv[0],cmd->name))
  826                 {
  827                         if (!cmd->function)
  828                         {       // forward to server command
  829                                 Cmd_ExecuteString (va("cmd %s", text));
  830                         }
  831                         else
  832                                 cmd->function ();
  833                         return;
  834                 }
  835         }
  836 
  837         // check alias
  838         for (a=cmd_alias ; a ; a=a->next)
  839         {
  840                 if (!Q_strcasecmp (cmd_argv[0], a->name))
  841                 {
  842                         if (++alias_count == ALIAS_LOOP_COUNT)
  843                         {
  844                                 Com_Printf ("ALIAS_LOOP_COUNT\n");
  845                                 return;
  846                         }
  847                         Cbuf_InsertText (a->value);
  848                         return;
  849                 }
  850         }
  851         
  852         // check cvars
  853         if (Cvar_Command ())
  854                 return;
  855 
  856         // send it as a server command if we are connected
  857         Cmd_ForwardToServer ();
  858 }
  859 
  860 /*
  861 ============
  862 Cmd_List_f
  863 ============
  864 */
  865 void Cmd_List_f (void)
  866 {
  867         cmd_function_t  *cmd;
  868         int                             i;
  869 
  870         i = 0;
  871         for (cmd=cmd_functions ; cmd ; cmd=cmd->next, i++)
  872                 Com_Printf ("%s\n", cmd->name);
  873         Com_Printf ("%i commands\n", i);
  874 }
  875 
  876 /*
  877 ============
  878 Cmd_Init
  879 ============
  880 */
  881 void Cmd_Init (void)
  882 {
  883 //
  884 // register our commands
  885 //
  886         Cmd_AddCommand ("cmdlist",Cmd_List_f);
  887         Cmd_AddCommand ("exec",Cmd_Exec_f);
  888         Cmd_AddCommand ("echo",Cmd_Echo_f);
  889         Cmd_AddCommand ("alias",Cmd_Alias_f);
  890         Cmd_AddCommand ("wait", Cmd_Wait_f);
  891 }
  892 
  893