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