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