1 /*
2 ===========================================================================
3
4 Return to Castle Wolfenstein multiplayer GPL Source Code
5 Copyright (C) 1999-2010 id Software LLC, a ZeniMax Media company.
6
7 This file is part of the Return to Castle Wolfenstein multiplayer GPL Source Code (RTCW MP Source Code).
8
9 RTCW MP Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 RTCW MP Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with RTCW MP Source Code. If not, see <http://www.gnu.org/licenses/>.
21
22 In addition, the RTCW MP Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the RTCW MP Source Code. If not, please request a copy in writing from id Software at the address below.
23
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
25
26 ===========================================================================
27 */
28
29 // cmd.c -- Quake script command processing module
30
31 #include "q_shared.h"
32 #include "qcommon.h"
33
34 #define MAX_CMD_BUFFER 128*1024
35 #define MAX_CMD_LINE 1024
36
37 typedef struct {
38 byte *data;
39 int maxsize;
40 int cursize;
41 } cmd_t;
42
43 int cmd_wait;
44 cmd_t cmd_text;
45 byte cmd_text_buf[MAX_CMD_BUFFER];
46
47
48 //=============================================================================
49
50 /*
51 ============
52 Cmd_Wait_f
53
54 Causes execution of the remainder of the command buffer to be delayed until
55 next frame. This allows commands like:
56 bind g "cmd use rocket ; +attack ; wait ; -attack ; cmd use blaster"
57 ============
58 */
Cmd_Wait_f(void)59 void Cmd_Wait_f( void ) {
60 if ( Cmd_Argc() == 2 ) {
61 cmd_wait = atoi( Cmd_Argv( 1 ) );
62 if ( cmd_wait < 0 )
63 cmd_wait = 1; // ignore the argument
64 } else {
65 cmd_wait = 1;
66 }
67 }
68
69
70 /*
71 =============================================================================
72
73 COMMAND BUFFER
74
75 =============================================================================
76 */
77
78 /*
79 ============
80 Cbuf_Init
81 ============
82 */
Cbuf_Init(void)83 void Cbuf_Init( void ) {
84 cmd_text.data = cmd_text_buf;
85 cmd_text.maxsize = MAX_CMD_BUFFER;
86 cmd_text.cursize = 0;
87 }
88
89 /*
90 ============
91 Cbuf_AddText
92
93 Adds command text at the end of the buffer, does NOT add a final \n
94 ============
95 */
Cbuf_AddText(const char * text)96 void Cbuf_AddText( const char *text ) {
97 int l;
98
99 l = strlen( text );
100
101 if ( cmd_text.cursize + l >= cmd_text.maxsize ) {
102 Com_Printf( "Cbuf_AddText: overflow\n" );
103 return;
104 }
105 memcpy( &cmd_text.data[cmd_text.cursize], text, l );
106 cmd_text.cursize += l;
107 }
108
109
110 /*
111 ============
112 Cbuf_InsertText
113
114 Adds command text immediately after the current command
115 Adds a \n to the text
116 ============
117 */
Cbuf_InsertText(const char * text)118 void Cbuf_InsertText( const char *text ) {
119 int len;
120 int i;
121
122 len = strlen( text ) + 1;
123 if ( len + cmd_text.cursize > cmd_text.maxsize ) {
124 Com_Printf( "Cbuf_InsertText overflowed\n" );
125 return;
126 }
127
128 // move the existing command text
129 for ( i = cmd_text.cursize - 1 ; i >= 0 ; i-- ) {
130 cmd_text.data[ i + len ] = cmd_text.data[ i ];
131 }
132
133 // copy the new text in
134 memcpy( cmd_text.data, text, len - 1 );
135
136 // add a \n
137 cmd_text.data[ len - 1 ] = '\n';
138
139 cmd_text.cursize += len;
140 }
141
142
143 /*
144 ============
145 Cbuf_ExecuteText
146 ============
147 */
Cbuf_ExecuteText(int exec_when,const char * text)148 void Cbuf_ExecuteText( int exec_when, const char *text ) {
149 switch ( exec_when )
150 {
151 case EXEC_NOW:
152 if ( text && strlen( text ) > 0 ) {
153 Com_DPrintf(S_COLOR_YELLOW "EXEC_NOW %s\n", text);
154 Cmd_ExecuteString( text );
155 } else {
156 Cbuf_Execute();
157 Com_DPrintf(S_COLOR_YELLOW "EXEC_NOW %s\n", cmd_text.data);
158 }
159 break;
160 case EXEC_INSERT:
161 Cbuf_InsertText( text );
162 break;
163 case EXEC_APPEND:
164 Cbuf_AddText( text );
165 break;
166 default:
167 Com_Error( ERR_FATAL, "Cbuf_ExecuteText: bad exec_when" );
168 }
169 }
170
171 /*
172 ============
173 Cbuf_Execute
174 ============
175 */
Cbuf_Execute(void)176 void Cbuf_Execute( void ) {
177 int i;
178 char *text;
179 char line[MAX_CMD_LINE];
180 int quotes;
181
182 // This will keep // style comments all on one line by not breaking on
183 // a semicolon. It will keep /* ... */ style comments all on one line by not
184 // breaking it for semicolon or newline.
185 qboolean in_star_comment = qfalse;
186 qboolean in_slash_comment = qfalse;
187 while ( cmd_text.cursize )
188 {
189 if ( cmd_wait > 0 ) {
190 // skip out while text still remains in buffer, leaving it
191 // for next frame
192 cmd_wait--;
193 break;
194 }
195
196 // find a \n or ; line break or comment: // or /* */
197 text = (char *)cmd_text.data;
198
199 quotes = 0;
200 for ( i = 0 ; i < cmd_text.cursize ; i++ )
201 {
202 if ( text[i] == '"' ) {
203 quotes++;
204 }
205 if ( !(quotes&1)) {
206 if (i < cmd_text.cursize - 1) {
207 if (! in_star_comment && text[i] == '/' && text[i+1] == '/')
208 in_slash_comment = qtrue;
209 else if (! in_slash_comment && text[i] == '/' && text[i+1] == '*')
210 in_star_comment = qtrue;
211 else if (in_star_comment && text[i] == '*' && text[i+1] == '/') {
212 in_star_comment = qfalse;
213 // If we are in a star comment, then the part after it is valid
214 // Note: This will cause it to NUL out the terminating '/'
215 // but ExecuteString doesn't require it anyway.
216 i++;
217 break;
218 }
219 }
220 if (! in_slash_comment && ! in_star_comment && text[i] == ';')
221 break;
222 }
223 if (! in_star_comment && (text[i] == '\n' || text[i] == '\r')) {
224 in_slash_comment = qfalse;
225 break;
226 }
227 }
228
229 if ( i >= ( MAX_CMD_LINE - 1 ) ) {
230 i = MAX_CMD_LINE - 1;
231 }
232
233 memcpy( line, text, i );
234 line[i] = 0;
235
236 // delete the text from the command buffer and move remaining commands down
237 // this is necessary because commands (exec) can insert data at the
238 // beginning of the text buffer
239
240 if ( i == cmd_text.cursize ) {
241 cmd_text.cursize = 0;
242 } else
243 {
244 i++;
245 cmd_text.cursize -= i;
246 memmove( text, text + i, cmd_text.cursize );
247 }
248
249 // execute the command line
250
251 Cmd_ExecuteString( line );
252 }
253 }
254
255
256 /*
257 ==============================================================================
258
259 SCRIPT COMMANDS
260
261 ==============================================================================
262 */
263
264
265 /*
266 ===============
267 Cmd_Exec_f
268 ===============
269 */
Cmd_Exec_f(void)270 void Cmd_Exec_f( void ) {
271 qboolean quiet;
272 union {
273 char *c;
274 void *v;
275 } f;
276 char filename[MAX_QPATH];
277
278 quiet = !Q_stricmp(Cmd_Argv(0), "execq");
279
280 if ( Cmd_Argc() != 2 ) {
281 Com_Printf ("exec%s <filename> : execute a script file%s\n",
282 quiet ? "q" : "", quiet ? " without notification" : "");
283 return;
284 }
285
286 Q_strncpyz( filename, Cmd_Argv( 1 ), sizeof( filename ) );
287 COM_DefaultExtension( filename, sizeof( filename ), ".cfg" );
288 FS_ReadFile( filename, &f.v);
289 if (!f.c) {
290 Com_Printf ("couldn't exec %s\n", filename);
291 return;
292 }
293 if (!quiet)
294 Com_Printf ("execing %s\n", filename);
295
296 Cbuf_InsertText( f.c );
297
298 FS_FreeFile( f.v );
299 }
300
301
302 /*
303 ===============
304 Cmd_Vstr_f
305
306 Inserts the current value of a variable as command text
307 ===============
308 */
Cmd_Vstr_f(void)309 void Cmd_Vstr_f( void ) {
310 char *v;
311
312 if ( Cmd_Argc() != 2 ) {
313 Com_Printf( "vstr <variablename> : execute a variable command\n" );
314 return;
315 }
316
317 v = Cvar_VariableString( Cmd_Argv( 1 ) );
318 Cbuf_InsertText( va( "%s\n", v ) );
319 }
320
321
322 /*
323 ===============
324 Cmd_Echo_f
325
326 Just prints the rest of the line to the console
327 ===============
328 */
Cmd_Echo_f(void)329 void Cmd_Echo_f( void ) {
330 Com_Printf ("%s\n", Cmd_Args());
331 }
332
333
334 /*
335 =============================================================================
336
337 COMMAND EXECUTION
338
339 =============================================================================
340 */
341
342 typedef struct cmd_function_s
343 {
344 struct cmd_function_s *next;
345 char *name;
346 xcommand_t function;
347 completionFunc_t complete;
348 } cmd_function_t;
349
350
351 static int cmd_argc;
352 static char *cmd_argv[MAX_STRING_TOKENS]; // points into cmd_tokenized
353 static char cmd_tokenized[BIG_INFO_STRING + MAX_STRING_TOKENS]; // will have 0 bytes inserted
354 static char cmd_cmd[BIG_INFO_STRING]; // the original command we received (no token processing)
355
356 static cmd_function_t *cmd_functions; // possible commands to execute
357
358 /*
359 ============
360 Cmd_Argc
361 ============
362 */
Cmd_Argc(void)363 int Cmd_Argc( void ) {
364 return cmd_argc;
365 }
366
367 /*
368 ============
369 Cmd_Argv
370 ============
371 */
Cmd_Argv(int arg)372 char *Cmd_Argv( int arg ) {
373 if ( (unsigned)arg >= cmd_argc ) {
374 return "";
375 }
376 return cmd_argv[arg];
377 }
378
379 /*
380 ============
381 Cmd_ArgvBuffer
382
383 The interpreted versions use this because
384 they can't have pointers returned to them
385 ============
386 */
Cmd_ArgvBuffer(int arg,char * buffer,int bufferLength)387 void Cmd_ArgvBuffer( int arg, char *buffer, int bufferLength ) {
388 Q_strncpyz( buffer, Cmd_Argv( arg ), bufferLength );
389 }
390
391
392 /*
393 ============
394 Cmd_Args
395
396 Returns a single string containing argv(1) to argv(argc()-1)
397 ============
398 */
Cmd_Args(void)399 char *Cmd_Args( void ) {
400 static char cmd_args[MAX_STRING_CHARS];
401 int i;
402
403 cmd_args[0] = 0;
404 for ( i = 1 ; i < cmd_argc ; i++ ) {
405 strcat( cmd_args, cmd_argv[i] );
406 if ( i != cmd_argc - 1 ) {
407 strcat( cmd_args, " " );
408 }
409 }
410
411 return cmd_args;
412 }
413
414 /*
415 ============
416 Cmd_Args
417
418 Returns a single string containing argv(arg) to argv(argc()-1)
419 ============
420 */
Cmd_ArgsFrom(int arg)421 char *Cmd_ArgsFrom( int arg ) {
422 static char cmd_args[BIG_INFO_STRING];
423 int i;
424
425 cmd_args[0] = 0;
426 if ( arg < 0 ) {
427 arg = 0;
428 }
429 for ( i = arg ; i < cmd_argc ; i++ ) {
430 strcat( cmd_args, cmd_argv[i] );
431 if ( i != cmd_argc - 1 ) {
432 strcat( cmd_args, " " );
433 }
434 }
435
436 return cmd_args;
437 }
438
439 /*
440 ============
441 Cmd_ArgsBuffer
442
443 The interpreted versions use this because
444 they can't have pointers returned to them
445 ============
446 */
Cmd_ArgsBuffer(char * buffer,int bufferLength)447 void Cmd_ArgsBuffer( char *buffer, int bufferLength ) {
448 Q_strncpyz( buffer, Cmd_Args(), bufferLength );
449 }
450
451 /*
452 ============
453 Cmd_Cmd
454
455 Retrieve the unmodified command string
456 For rcon use when you want to transmit without altering quoting
457 ATVI Wolfenstein Misc #284
458 ============
459 */
Cmd_Cmd(void)460 char *Cmd_Cmd(void) {
461 return cmd_cmd;
462 }
463
464 /*
465 Replace command separators with space to prevent interpretation
466 This is a hack to protect buggy qvms
467 https://bugzilla.icculus.org/show_bug.cgi?id=3593
468 https://bugzilla.icculus.org/show_bug.cgi?id=4769
469 */
470
Cmd_Args_Sanitize(void)471 void Cmd_Args_Sanitize(void)
472 {
473 int i;
474
475 for(i = 1; i < cmd_argc; i++)
476 {
477 char *c = cmd_argv[i];
478
479 if(strlen(c) > MAX_CVAR_VALUE_STRING - 1)
480 c[MAX_CVAR_VALUE_STRING - 1] = '\0';
481
482 while ((c = strpbrk(c, "\n\r;"))) {
483 *c = ' ';
484 ++c;
485 }
486 }
487 }
488
489 /*
490 ============
491 Cmd_TokenizeString
492
493 Parses the given string into command line tokens.
494 The text is copied to a seperate buffer and 0 characters
495 are inserted in the apropriate place, The argv array
496 will point into this temporary buffer.
497 ============
498 */
Cmd_TokenizeString2(const char * text_in,qboolean ignoreQuotes)499 static void Cmd_TokenizeString2( const char *text_in, qboolean ignoreQuotes ) {
500 const char *text;
501 char *textOut;
502
503 // clear previous args
504 cmd_argc = 0;
505
506 if ( !text_in ) {
507 return;
508 }
509
510 Q_strncpyz( cmd_cmd, text_in, sizeof( cmd_cmd ) );
511
512 text = text_in;
513 textOut = cmd_tokenized;
514
515 while ( 1 ) {
516 if ( cmd_argc == MAX_STRING_TOKENS ) {
517 return; // this is usually something malicious
518 }
519
520 while ( 1 ) {
521 // skip whitespace
522 while ( *text && *text <= ' ' ) {
523 text++;
524 }
525 if ( !*text ) {
526 return; // all tokens parsed
527 }
528
529 // skip // comments
530 if ( text[0] == '/' && text[1] == '/' ) {
531 return; // all tokens parsed
532 }
533
534 // skip /* */ comments
535 if ( text[0] == '/' && text[1] == '*' ) {
536 while ( *text && ( text[0] != '*' || text[1] != '/' ) ) {
537 text++;
538 }
539 if ( !*text ) {
540 return; // all tokens parsed
541 }
542 text += 2;
543 } else {
544 break; // we are ready to parse a token
545 }
546 }
547
548 // handle quoted strings
549 if ( !ignoreQuotes && *text == '"' ) {
550 cmd_argv[cmd_argc] = textOut;
551 cmd_argc++;
552 text++;
553 while ( *text && *text != '"' ) {
554 *textOut++ = *text++;
555 }
556 *textOut++ = 0;
557 if ( !*text ) {
558 return; // all tokens parsed
559 }
560 text++;
561 continue;
562 }
563
564 // regular token
565 cmd_argv[cmd_argc] = textOut;
566 cmd_argc++;
567
568 // skip until whitespace, quote, or command
569 while ( *text > ' ' ) {
570 if ( !ignoreQuotes && text[0] == '"' ) {
571 break;
572 }
573
574 if ( text[0] == '/' && text[1] == '/' ) {
575 break;
576 }
577
578 // skip /* */ comments
579 if ( text[0] == '/' && text[1] == '*' ) {
580 break;
581 }
582
583 *textOut++ = *text++;
584 }
585
586 *textOut++ = 0;
587
588 if ( !*text ) {
589 return; // all tokens parsed
590 }
591 }
592
593 }
594
595 /*
596 ============
597 Cmd_TokenizeString
598 ============
599 */
Cmd_TokenizeString(const char * text_in)600 void Cmd_TokenizeString( const char *text_in ) {
601 Cmd_TokenizeString2( text_in, qfalse );
602 }
603
604 /*
605 ============
606 Cmd_TokenizeStringIgnoreQuotes
607 ============
608 */
Cmd_TokenizeStringIgnoreQuotes(const char * text_in)609 void Cmd_TokenizeStringIgnoreQuotes( const char *text_in ) {
610 Cmd_TokenizeString2( text_in, qtrue );
611 }
612
613 /*
614 ============
615 Cmd_FindCommand
616 ============
617 */
Cmd_FindCommand(const char * cmd_name)618 cmd_function_t *Cmd_FindCommand( const char *cmd_name )
619 {
620 cmd_function_t *cmd;
621 for( cmd = cmd_functions; cmd; cmd = cmd->next )
622 if( !Q_stricmp( cmd_name, cmd->name ) )
623 return cmd;
624 return NULL;
625 }
626
627 /*
628 ============
629 Cmd_AddCommand
630 ============
631 */
Cmd_AddCommand(const char * cmd_name,xcommand_t function)632 void Cmd_AddCommand( const char *cmd_name, xcommand_t function ) {
633 cmd_function_t *cmd;
634
635 // fail if the command already exists
636 if( Cmd_FindCommand( cmd_name ) )
637 {
638 // allow completion-only commands to be silently doubled
639 if( function != NULL )
640 Com_Printf( "Cmd_AddCommand: %s already defined\n", cmd_name );
641 return;
642 }
643
644 // use a small malloc to avoid zone fragmentation
645 cmd = S_Malloc( sizeof( cmd_function_t ) );
646 cmd->name = CopyString( cmd_name );
647 cmd->function = function;
648 cmd->complete = NULL;
649 cmd->next = cmd_functions;
650 cmd_functions = cmd;
651 }
652
653 /*
654 ============
655 Cmd_SetCommandCompletionFunc
656 ============
657 */
Cmd_SetCommandCompletionFunc(const char * command,completionFunc_t complete)658 void Cmd_SetCommandCompletionFunc( const char *command, completionFunc_t complete ) {
659 cmd_function_t *cmd;
660
661 for( cmd = cmd_functions; cmd; cmd = cmd->next ) {
662 if( !Q_stricmp( command, cmd->name ) ) {
663 cmd->complete = complete;
664 return;
665 }
666 }
667 }
668
669 /*
670 ============
671 Cmd_RemoveCommand
672 ============
673 */
Cmd_RemoveCommand(const char * cmd_name)674 void Cmd_RemoveCommand( const char *cmd_name ) {
675 cmd_function_t *cmd, **back;
676
677 back = &cmd_functions;
678 while ( 1 ) {
679 cmd = *back;
680 if ( !cmd ) {
681 // command wasn't active
682 return;
683 }
684 if ( !strcmp( cmd_name, cmd->name ) ) {
685 *back = cmd->next;
686 Z_Free( cmd->name );
687 Z_Free( cmd );
688 return;
689 }
690 back = &cmd->next;
691 }
692 }
693
694 /*
695 ============
696 Cmd_RemoveCommandSafe
697
698 Only remove commands with no associated function
699 ============
700 */
Cmd_RemoveCommandSafe(const char * cmd_name)701 void Cmd_RemoveCommandSafe( const char *cmd_name )
702 {
703 cmd_function_t *cmd = Cmd_FindCommand( cmd_name );
704
705 if( !cmd )
706 return;
707 if( cmd->function )
708 {
709 Com_Error( ERR_DROP, "Restricted source tried to remove "
710 "system command \"%s\"", cmd_name );
711 return;
712 }
713
714 Cmd_RemoveCommand( cmd_name );
715 }
716
717 /*
718 ============
719 Cmd_CommandCompletion
720 ============
721 */
Cmd_CommandCompletion(void (* callback)(const char * s))722 void Cmd_CommandCompletion( void ( *callback )(const char *s) ) {
723 cmd_function_t *cmd;
724
725 for ( cmd = cmd_functions ; cmd ; cmd = cmd->next ) {
726 callback( cmd->name );
727 }
728 }
729
730 /*
731 ============
732 Cmd_CompleteArgument
733 ============
734 */
Cmd_CompleteArgument(const char * command,char * args,int argNum)735 void Cmd_CompleteArgument( const char *command, char *args, int argNum ) {
736 cmd_function_t *cmd;
737
738 for( cmd = cmd_functions; cmd; cmd = cmd->next ) {
739 if( !Q_stricmp( command, cmd->name ) ) {
740 if ( cmd->complete ) {
741 cmd->complete( args, argNum );
742 }
743 return;
744 }
745 }
746 }
747
748 /*
749 ============
750 Cmd_ExecuteString
751
752 A complete command line has been parsed, so try to execute it
753 ============
754 */
Cmd_ExecuteString(const char * text)755 void Cmd_ExecuteString( const char *text ) {
756 cmd_function_t *cmd, **prev;
757
758 // execute the command line
759 Cmd_TokenizeString( text );
760 if ( !Cmd_Argc() ) {
761 return; // no tokens
762 }
763
764 // check registered command functions
765 for ( prev = &cmd_functions ; *prev ; prev = &cmd->next ) {
766 cmd = *prev;
767 if ( !Q_stricmp( cmd_argv[0],cmd->name ) ) {
768 // rearrange the links so that the command will be
769 // near the head of the list next time it is used
770 *prev = cmd->next;
771 cmd->next = cmd_functions;
772 cmd_functions = cmd;
773
774 // perform the action
775 if ( !cmd->function ) {
776 // let the cgame or game handle it
777 break;
778 } else {
779 cmd->function();
780 }
781 return;
782 }
783 }
784
785 // check cvars
786 if ( Cvar_Command() ) {
787 return;
788 }
789
790 // check client game commands
791 if ( com_cl_running && com_cl_running->integer && CL_GameCommand() ) {
792 return;
793 }
794
795 // check server game commands
796 if ( com_sv_running && com_sv_running->integer && SV_GameCommand() ) {
797 return;
798 }
799
800 // check ui commands
801 if ( com_cl_running && com_cl_running->integer && UI_GameCommand() ) {
802 return;
803 }
804
805 // send it as a server command if we are connected
806 // this will usually result in a chat message
807 CL_ForwardCommandToServer( text );
808 }
809
810 /*
811 ============
812 Cmd_List_f
813 ============
814 */
Cmd_List_f(void)815 void Cmd_List_f( void ) {
816 cmd_function_t *cmd;
817 int i;
818 char *match;
819
820 if ( Cmd_Argc() > 1 ) {
821 match = Cmd_Argv( 1 );
822 } else {
823 match = NULL;
824 }
825
826 i = 0;
827 for ( cmd = cmd_functions ; cmd ; cmd = cmd->next ) {
828 if ( match && !Com_Filter( match, cmd->name, qfalse ) ) {
829 continue;
830 }
831
832 Com_Printf( "%s\n", cmd->name );
833 i++;
834 }
835 Com_Printf( "%i commands\n", i );
836 }
837
838 /*
839 ==================
840 Cmd_CompleteCfgName
841 ==================
842 */
Cmd_CompleteCfgName(char * args,int argNum)843 void Cmd_CompleteCfgName( char *args, int argNum ) {
844 if( argNum == 2 ) {
845 Field_CompleteFilename( "", "cfg", qfalse, qtrue );
846 }
847 }
848
849 /*
850 ============
851 Cmd_Init
852 ============
853 */
Cmd_Init(void)854 void Cmd_Init( void ) {
855 Cmd_AddCommand( "cmdlist",Cmd_List_f );
856 Cmd_AddCommand( "exec",Cmd_Exec_f );
857 Cmd_AddCommand ("execq",Cmd_Exec_f);
858 Cmd_SetCommandCompletionFunc( "exec", Cmd_CompleteCfgName );
859 Cmd_SetCommandCompletionFunc( "execq", Cmd_CompleteCfgName );
860 Cmd_AddCommand( "vstr",Cmd_Vstr_f );
861 Cmd_SetCommandCompletionFunc( "vstr", Cvar_CompleteCvarName );
862 Cmd_AddCommand( "echo",Cmd_Echo_f );
863 Cmd_AddCommand( "wait", Cmd_Wait_f );
864 }
865
866