1 /*
2 ===========================================================================
3
4 Return to Castle Wolfenstein single player 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 single player GPL Source Code (RTCW SP Source Code).
8
9 RTCW SP 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 SP 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 SP Source Code. If not, see <http://www.gnu.org/licenses/>.
21
22 In addition, the RTCW SP 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 SP 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 "../qcommon/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 cmdsize; //DAJ renamed from 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.cmdsize = 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.cmdsize + l >= cmd_text.maxsize ) {
102 Com_Printf( "Cbuf_AddText: overflow\n" );
103 return;
104 }
105 memcpy( &cmd_text.data[cmd_text.cmdsize], text, l );
106 cmd_text.cmdsize += 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.cmdsize > 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.cmdsize - 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.cmdsize += 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.cmdsize )
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.cmdsize ; i++ )
201 {
202 if ( text[i] == '"' ) {
203 quotes++;
204 }
205 if ( !(quotes&1)) {
206 if (i < cmd_text.cmdsize - 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.cmdsize ) {
241 cmd_text.cmdsize = 0;
242 } else
243 {
244 i++;
245 cmd_text.cmdsize -= i;
246 memmove( text, text + i, cmd_text.cmdsize );
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
294 if (!quiet)
295 Com_Printf ("execing %s\n", filename);
296
297 Cbuf_InsertText (f.c);
298
299 FS_FreeFile (f.v);
300 }
301
302
303 /*
304 ===============
305 Cmd_Vstr_f
306
307 Inserts the current value of a variable as command text
308 ===============
309 */
Cmd_Vstr_f(void)310 void Cmd_Vstr_f( void ) {
311 char *v;
312
313 if ( Cmd_Argc() != 2 ) {
314 Com_Printf( "vstr <variablename> : execute a variable command\n" );
315 return;
316 }
317
318 v = Cvar_VariableString( Cmd_Argv( 1 ) );
319 Cbuf_InsertText( va( "%s\n", v ) );
320 }
321
322
323 /*
324 ===============
325 Cmd_Echo_f
326
327 Just prints the rest of the line to the console
328 ===============
329 */
Cmd_Echo_f(void)330 void Cmd_Echo_f( void ) {
331 Com_Printf ("%s\n", Cmd_Args());
332 }
333
334
335 /*
336 =============================================================================
337
338 COMMAND EXECUTION
339
340 =============================================================================
341 */
342
343 typedef struct cmd_function_s
344 {
345 struct cmd_function_s *next;
346 char *name;
347 xcommand_t function;
348 completionFunc_t complete;
349 } cmd_function_t;
350
351
352 static int cmd_argc;
353 static char *cmd_argv[MAX_STRING_TOKENS]; // points into cmd_tokenized
354 static char cmd_tokenized[BIG_INFO_STRING + MAX_STRING_TOKENS]; // will have 0 bytes inserted
355 static char cmd_cmd[BIG_INFO_STRING]; // the original command we received (no token processing)
356
357 static cmd_function_t *cmd_functions; // possible commands to execute
358
359 /*
360 ============
361 Cmd_Argc
362 ============
363 */
Cmd_Argc(void)364 int Cmd_Argc( void ) {
365 return cmd_argc;
366 }
367
368 /*
369 ============
370 Cmd_Argv
371 ============
372 */
Cmd_Argv(int arg)373 char *Cmd_Argv( int arg ) {
374 if ( (unsigned)arg >= cmd_argc ) {
375 return "";
376 }
377 return cmd_argv[arg];
378 }
379
380 /*
381 ============
382 Cmd_ArgvBuffer
383
384 The interpreted versions use this because
385 they can't have pointers returned to them
386 ============
387 */
Cmd_ArgvBuffer(int arg,char * buffer,int bufferLength)388 void Cmd_ArgvBuffer( int arg, char *buffer, int bufferLength ) {
389 Q_strncpyz( buffer, Cmd_Argv( arg ), bufferLength );
390 }
391
392
393 /*
394 ============
395 Cmd_Args
396
397 Returns a single string containing argv(1) to argv(argc()-1)
398 ============
399 */
Cmd_Args(void)400 char *Cmd_Args( void ) {
401 static char cmd_args[MAX_STRING_CHARS];
402 int i;
403
404 cmd_args[0] = 0;
405 for ( i = 1 ; i < cmd_argc ; i++ ) {
406 strcat( cmd_args, cmd_argv[i] );
407 if ( i != cmd_argc - 1 ) {
408 strcat( cmd_args, " " );
409 }
410 }
411
412 return cmd_args;
413 }
414
415 /*
416 ============
417 Cmd_Args
418
419 Returns a single string containing argv(arg) to argv(argc()-1)
420 ============
421 */
Cmd_ArgsFrom(int arg)422 char *Cmd_ArgsFrom( int arg ) {
423 static char cmd_args[BIG_INFO_STRING];
424 int i;
425
426 cmd_args[0] = 0;
427 if ( arg < 0 ) {
428 arg = 0;
429 }
430 for ( i = arg ; i < cmd_argc ; i++ ) {
431 strcat( cmd_args, cmd_argv[i] );
432 if ( i != cmd_argc - 1 ) {
433 strcat( cmd_args, " " );
434 }
435 }
436
437 return cmd_args;
438 }
439
440 /*
441 ============
442 Cmd_ArgsBuffer
443
444 The interpreted versions use this because
445 they can't have pointers returned to them
446 ============
447 */
Cmd_ArgsBuffer(char * buffer,int bufferLength)448 void Cmd_ArgsBuffer( char *buffer, int bufferLength ) {
449 Q_strncpyz( buffer, Cmd_Args(), bufferLength );
450 }
451
452 /*
453 ============
454 Cmd_Cmd
455
456 Retrieve the unmodified command string
457 For rcon use when you want to transmit without altering quoting
458 https://zerowing.idsoftware.com/bugzilla/show_bug.cgi?id=543
459 ============
460 */
Cmd_Cmd(void)461 char *Cmd_Cmd(void)
462 {
463 return cmd_cmd;
464 }
465
466 /*
467 Replace command separators with space to prevent interpretation
468 This is a hack to protect buggy qvms
469 https://bugzilla.icculus.org/show_bug.cgi?id=3593
470 https://bugzilla.icculus.org/show_bug.cgi?id=4769
471 */
472
Cmd_Args_Sanitize(void)473 void Cmd_Args_Sanitize(void)
474 {
475 int i;
476
477 for(i = 1; i < cmd_argc; i++)
478 {
479 char *c = cmd_argv[i];
480
481 if(strlen(c) > MAX_CVAR_VALUE_STRING - 1)
482 c[MAX_CVAR_VALUE_STRING - 1] = '\0';
483
484 while ((c = strpbrk(c, "\n\r;"))) {
485 *c = ' ';
486 ++c;
487 }
488 }
489 }
490
491 /*
492 ============
493 Cmd_TokenizeString
494
495 Parses the given string into command line tokens.
496 The text is copied to a seperate buffer and 0 characters
497 are inserted in the apropriate place, The argv array
498 will point into this temporary buffer.
499 ============
500 */
Cmd_TokenizeString2(const char * text_in,qboolean ignoreQuotes)501 static void Cmd_TokenizeString2( const char *text_in, qboolean ignoreQuotes ) {
502 const char *text;
503 char *textOut;
504
505 // clear previous args
506 cmd_argc = 0;
507
508 if ( !text_in ) {
509 return;
510 }
511
512 Q_strncpyz( cmd_cmd, text_in, sizeof( cmd_cmd ) );
513
514 text = text_in;
515 textOut = cmd_tokenized;
516
517 while ( 1 ) {
518 if ( cmd_argc == MAX_STRING_TOKENS ) {
519 return; // this is usually something malicious
520 }
521
522 while ( 1 ) {
523 // skip whitespace
524 while ( *text && *text <= ' ' ) {
525 text++;
526 }
527 if ( !*text ) {
528 return; // all tokens parsed
529 }
530
531 // skip // comments
532 if ( text[0] == '/' && text[1] == '/' ) {
533 return; // all tokens parsed
534 }
535
536 // skip /* */ comments
537 if ( text[0] == '/' && text[1] == '*' ) {
538 while ( *text && ( text[0] != '*' || text[1] != '/' ) ) {
539 text++;
540 }
541 if ( !*text ) {
542 return; // all tokens parsed
543 }
544 text += 2;
545 } else {
546 break; // we are ready to parse a token
547 }
548 }
549
550 // handle quoted strings
551 if ( !ignoreQuotes && *text == '"' ) {
552 cmd_argv[cmd_argc] = textOut;
553 cmd_argc++;
554 text++;
555 while ( *text && *text != '"' ) {
556 *textOut++ = *text++;
557 }
558 *textOut++ = 0;
559 if ( !*text ) {
560 return; // all tokens parsed
561 }
562 text++;
563 continue;
564 }
565
566 // regular token
567 cmd_argv[cmd_argc] = textOut;
568 cmd_argc++;
569
570 // skip until whitespace, quote, or command
571 while ( *text > ' ' ) {
572 if ( !ignoreQuotes && text[0] == '"' ) {
573 break;
574 }
575
576 if ( text[0] == '/' && text[1] == '/' ) {
577 break;
578 }
579
580 // skip /* */ comments
581 if ( text[0] == '/' && text[1] == '*' ) {
582 break;
583 }
584
585 *textOut++ = *text++;
586 }
587
588 *textOut++ = 0;
589
590 if ( !*text ) {
591 return; // all tokens parsed
592 }
593 }
594
595 }
596
597 /*
598 ============
599 Cmd_TokenizeString
600 ============
601 */
Cmd_TokenizeString(const char * text_in)602 void Cmd_TokenizeString( const char *text_in ) {
603 Cmd_TokenizeString2( text_in, qfalse );
604 }
605
606 /*
607 ============
608 Cmd_TokenizeStringIgnoreQuotes
609 ============
610 */
Cmd_TokenizeStringIgnoreQuotes(const char * text_in)611 void Cmd_TokenizeStringIgnoreQuotes( const char *text_in ) {
612 Cmd_TokenizeString2( text_in, qtrue );
613 }
614
615 /*
616 ============
617 Cmd_FindCommand
618 ============
619 */
Cmd_FindCommand(const char * cmd_name)620 cmd_function_t *Cmd_FindCommand( const char *cmd_name )
621 {
622 cmd_function_t *cmd;
623 for( cmd = cmd_functions; cmd; cmd = cmd->next )
624 if( !Q_stricmp( cmd_name, cmd->name ) )
625 return cmd;
626 return NULL;
627 }
628
629 /*
630 ============
631 Cmd_AddCommand
632 ============
633 */
Cmd_AddCommand(const char * cmd_name,xcommand_t function)634 void Cmd_AddCommand( const char *cmd_name, xcommand_t function ) {
635 cmd_function_t *cmd;
636
637 // fail if the command already exists
638 if( Cmd_FindCommand( cmd_name ) )
639 {
640 // allow completion-only commands to be silently doubled
641 if( function != NULL )
642 Com_Printf( "Cmd_AddCommand: %s already defined\n", cmd_name );
643 return;
644 }
645
646 // use a small malloc to avoid zone fragmentation
647 cmd = Z_Malloc( sizeof( cmd_function_t ) );
648 cmd->name = CopyString( cmd_name );
649 cmd->function = function;
650 cmd->complete = NULL;
651 cmd->next = cmd_functions;
652 cmd_functions = cmd;
653 }
654
655 /*
656 ============
657 Cmd_SetCommandCompletionFunc
658 ============
659 */
Cmd_SetCommandCompletionFunc(const char * command,completionFunc_t complete)660 void Cmd_SetCommandCompletionFunc( const char *command, completionFunc_t complete ) {
661 cmd_function_t *cmd;
662
663 for( cmd = cmd_functions; cmd; cmd = cmd->next ) {
664 if( !Q_stricmp( command, cmd->name ) ) {
665 cmd->complete = complete;
666 return;
667 }
668 }
669 }
670
671 /*
672 ============
673 Cmd_RemoveCommand
674 ============
675 */
Cmd_RemoveCommand(const char * cmd_name)676 void Cmd_RemoveCommand( const char *cmd_name ) {
677 cmd_function_t *cmd, **back;
678
679 back = &cmd_functions;
680 while ( 1 ) {
681 cmd = *back;
682 if ( !cmd ) {
683 // command wasn't active
684 return;
685 }
686 if ( !strcmp( cmd_name, cmd->name ) ) {
687 *back = cmd->next;
688 Z_Free( cmd->name );
689 Z_Free( cmd );
690 return;
691 }
692 back = &cmd->next;
693 }
694 }
695
696 /*
697 ============
698 Cmd_RemoveCommandSafe
699
700 Only remove commands with no associated function
701 ============
702 */
Cmd_RemoveCommandSafe(const char * cmd_name)703 void Cmd_RemoveCommandSafe( const char *cmd_name )
704 {
705 cmd_function_t *cmd = Cmd_FindCommand( cmd_name );
706
707 if( !cmd )
708 return;
709 if( cmd->function )
710 {
711 Com_Error( ERR_DROP, "Restricted source tried to remove "
712 "system command \"%s\"", cmd_name );
713 return;
714 }
715
716 Cmd_RemoveCommand( cmd_name );
717 }
718
719 /*
720 ============
721 Cmd_CommandCompletion
722 ============
723 */
Cmd_CommandCompletion(void (* callback)(const char * s))724 void Cmd_CommandCompletion( void ( *callback )(const char *s) ) {
725 cmd_function_t *cmd;
726
727 for ( cmd = cmd_functions ; cmd ; cmd = cmd->next ) {
728 callback( cmd->name );
729 }
730 }
731
732 /*
733 ============
734 Cmd_CompleteArgument
735 ============
736 */
Cmd_CompleteArgument(const char * command,char * args,int argNum)737 void Cmd_CompleteArgument( const char *command, char *args, int argNum ) {
738 cmd_function_t *cmd;
739
740 for( cmd = cmd_functions; cmd; cmd = cmd->next ) {
741 if( !Q_stricmp( command, cmd->name ) ) {
742 if ( cmd->complete ) {
743 cmd->complete( args, argNum );
744 }
745 return;
746 }
747 }
748 }
749
750 /*
751 ============
752 Cmd_ExecuteString
753
754 A complete command line has been parsed, so try to execute it
755 ============
756 */
Cmd_ExecuteString(const char * text)757 void Cmd_ExecuteString( const char *text ) {
758 cmd_function_t *cmd, **prev;
759
760 // execute the command line
761 Cmd_TokenizeString( text );
762 if ( !Cmd_Argc() ) {
763 return; // no tokens
764 }
765
766 // check registered command functions
767 for ( prev = &cmd_functions ; *prev ; prev = &cmd->next ) {
768 cmd = *prev;
769 if ( !Q_stricmp( cmd_argv[0],cmd->name ) ) {
770 // rearrange the links so that the command will be
771 // near the head of the list next time it is used
772 *prev = cmd->next;
773 cmd->next = cmd_functions;
774 cmd_functions = cmd;
775
776 // perform the action
777 if ( !cmd->function ) {
778 // let the cgame or game handle it
779 break;
780 } else {
781 cmd->function();
782 }
783 return;
784 }
785 }
786
787 // check cvars
788 if ( Cvar_Command() ) {
789 return;
790 }
791
792 // check client game commands
793 if ( com_cl_running && com_cl_running->integer && CL_GameCommand() ) {
794 return;
795 }
796
797 // check server game commands
798 if ( com_sv_running && com_sv_running->integer && SV_GameCommand() ) {
799 return;
800 }
801
802 // check ui commands
803 if ( com_cl_running && com_cl_running->integer && UI_GameCommand() ) {
804 return;
805 }
806
807 // send it as a server command if we are connected
808 // this will usually result in a chat message
809 CL_ForwardCommandToServer( text );
810 }
811
812 /*
813 ============
814 Cmd_List_f
815 ============
816 */
Cmd_List_f(void)817 void Cmd_List_f( void ) {
818 cmd_function_t *cmd;
819 int i;
820 char *match;
821
822 if ( Cmd_Argc() > 1 ) {
823 match = Cmd_Argv( 1 );
824 } else {
825 match = NULL;
826 }
827
828 i = 0;
829 for ( cmd = cmd_functions ; cmd ; cmd = cmd->next ) {
830 if ( match && !Com_Filter( match, cmd->name, qfalse ) ) {
831 continue;
832 }
833
834 Com_Printf( "%s\n", cmd->name );
835 i++;
836 }
837 Com_Printf( "%i commands\n", i );
838 }
839
840 /*
841 ==================
842 Cmd_CompleteCfgName
843 ==================
844 */
Cmd_CompleteCfgName(char * args,int argNum)845 void Cmd_CompleteCfgName( char *args, int argNum ) {
846 if( argNum == 2 ) {
847 Field_CompleteFilename( "", "cfg", qfalse, qtrue );
848 }
849 }
850
851 /*
852 ============
853 Cmd_Init
854 ============
855 */
Cmd_Init(void)856 void Cmd_Init( void ) {
857 Cmd_AddCommand( "cmdlist",Cmd_List_f );
858 Cmd_AddCommand( "exec",Cmd_Exec_f );
859 Cmd_AddCommand ("execq",Cmd_Exec_f);
860 Cmd_SetCommandCompletionFunc( "exec", Cmd_CompleteCfgName );
861 Cmd_SetCommandCompletionFunc( "execq", Cmd_CompleteCfgName );
862 Cmd_AddCommand( "vstr",Cmd_Vstr_f );
863 Cmd_SetCommandCompletionFunc( "vstr", Cvar_CompleteCvarName );
864 Cmd_AddCommand( "echo",Cmd_Echo_f );
865 Cmd_AddCommand( "wait", Cmd_Wait_f );
866 }
867
868