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