1 /**
2  * @file
3  * @brief Script command processing module
4  * Command text buffering. Any number of commands can be added in a frame, from several different sources.
5  * Most commands come from either keyBindings or console line input, but remote
6  * servers can also send across commands and entire text files can be accessed.
7  *
8  * The + command line options are also added to the command buffer.
9  *
10  * Command execution takes a null terminated string, breaks it into tokens,
11  * then searches for a command or variable that matches the first token.
12  */
13 
14 /*
15 Copyright (C) 1997-2001 Id Software, Inc.
16 
17 This program is free software; you can redistribute it and/or
18 modify it under the terms of the GNU General Public License
19 as published by the Free Software Foundation; either version 2
20 of the License, or (at your option) any later version.
21 
22 This program is distributed in the hope that it will be useful,
23 but WITHOUT ANY WARRANTY; without even the implied warranty of
24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
25 
26 See the GNU General Public License for more details.
27 
28 You should have received a copy of the GNU General Public License
29 along with this program; if not, write to the Free Software
30 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
31 
32 */
33 
34 #include "cmd.h"
35 #include "common.h"
36 #include "msg.h"
37 #include "../shared/parse.h"
38 
39 void Cmd_ForwardToServer(void);
40 #define ALIAS_HASH_SIZE	32
41 
42 #define	MAX_ALIAS_NAME	32
43 
44 typedef struct cmd_alias_s {
45 	char name[MAX_ALIAS_NAME];
46 	char* value;
47 	bool archive;	/**< store the alias and reload it on the next game start */
48 	struct cmd_alias_s* hash_next;
49 	struct cmd_alias_s* next;
50 } cmd_alias_t;
51 
52 static cmd_alias_t* cmd_alias;
53 static cmd_alias_t* cmd_alias_hash[ALIAS_HASH_SIZE];
54 static bool cmdWait;
55 static bool cmdClosed;
56 
57 #define	ALIAS_LOOP_COUNT	16
58 static int alias_count;				/* for detecting runaway loops */
59 
60 
61 /**
62  * @brief Reopens the command buffer for writing
63  * @sa Cmd_Close_f
64  */
Cmd_Open_f(void)65 static void Cmd_Open_f (void)
66 {
67 	Com_DPrintf(DEBUG_COMMANDS, "Cmd_Close_f: command buffer opened again\n");
68 	cmdClosed = false;
69 }
70 
71 /**
72  * @brief Will no longer add any command to command buffer
73  * ...until cmd_close is false again
74  * @sa Cmd_Open_f
75  */
Cmd_Close_f(void)76 static void Cmd_Close_f (void)
77 {
78 	Com_DPrintf(DEBUG_COMMANDS, "Cmd_Close_f: command buffer closed\n");
79 	cmdClosed = true;
80 }
81 
82 /**
83  * @brief Causes execution of the remainder of the command buffer to be delayed until
84  * next frame. This allows commands like:
85  * bind g "impulse 5; +attack; wait; -attack; impulse 2"
86  */
Cmd_Wait_f(void)87 static void Cmd_Wait_f (void)
88 {
89 	cmdWait = true;
90 }
91 
92 /*
93 =============================================================================
94 COMMAND BUFFER
95 =============================================================================
96 */
97 
98 #define CMD_BUFFER_SIZE 8192
99 static sizebuf_t cmd_text;
100 static byte cmd_text_buf[CMD_BUFFER_SIZE];
101 static char defer_text_buf[CMD_BUFFER_SIZE];
102 
103 /**
104  * @note The initial buffer will grow as needed.
105  */
Cbuf_Init(void)106 void Cbuf_Init (void)
107 {
108 	SZ_Init(&cmd_text, cmd_text_buf, sizeof(cmd_text_buf));
109 }
110 
111 /**
112  * @note Reset the Cbuf memory.
113  */
Cbuf_Shutdown(void)114 void Cbuf_Shutdown (void)
115 {
116 	SZ_Init(&cmd_text, nullptr, 0);
117 }
118 
119 /**
120  * @brief Adds command text at the end of the buffer
121  * @note Normally when a command is generate from the console or keyBindings, it will be added to the end of the command buffer.
122  */
Cbuf_AddText(const char * format,...)123 void Cbuf_AddText (const char* format, ...)
124 {
125 	va_list argptr;
126 	static char text[CMD_BUFFER_SIZE];
127 	va_start(argptr, format);
128 	Q_vsnprintf(text, sizeof(text), format, argptr);
129 	va_end(argptr);
130 
131 	if (cmdClosed) {
132 		if (!Q_strstart(text, "cmdopen")) {
133 			Com_DPrintf(DEBUG_COMMANDS, "Cbuf_AddText: currently closed\n");
134 			return;
135 		}
136 	}
137 
138 	const int l = strlen(text);
139 
140 	if (cmd_text.cursize + l >= cmd_text.maxsize) {
141 		Com_Printf("Cbuf_AddText: overflow (%i) (%s)\n", cmd_text.maxsize, text);
142 		Com_Printf("buffer content: %s\n", cmd_text_buf);
143 		return;
144 	}
145 	SZ_Write(&cmd_text, text, l);
146 }
147 
148 
149 /**
150  * @brief Adds command text immediately after the current command
151  * @note Adds a @c \\n to the text
152  * @todo actually change the command buffer to do less copying
153  */
Cbuf_InsertText(const char * text)154 void Cbuf_InsertText (const char* text)
155 {
156 	char* temp;
157 	int templen;
158 
159 	if (Q_strnull(text))
160 		return;
161 
162 	/* copy off any commands still remaining in the exec buffer */
163 	templen = cmd_text.cursize;
164 	if (templen) {
165 		temp = Mem_AllocTypeN(char, templen);
166 		memcpy(temp, cmd_text.data, templen);
167 		SZ_Clear(&cmd_text);
168 	} else {
169 		temp = nullptr;			/* shut up compiler */
170 	}
171 
172 	/* add the entire text of the file */
173 	Cbuf_AddText("%s\n", text);
174 
175 	/* add the copied off data */
176 	if (templen) {
177 		SZ_Write(&cmd_text, temp, templen);
178 		Mem_Free(temp);
179 	}
180 }
181 
182 
183 /**
184  * @brief Defers any outstanding commands.
185  *
186  * Used when loading a map, for example.
187  * Copies then clears the command buffer to a temporary area.
188  */
Cbuf_CopyToDefer(void)189 void Cbuf_CopyToDefer (void)
190 {
191 	memcpy(defer_text_buf, cmd_text_buf, cmd_text.cursize);
192 	defer_text_buf[cmd_text.cursize] = 0;
193 	cmd_text.cursize = 0;
194 }
195 
196 /**
197  * @brief Copies back any deferred commands.
198  */
Cbuf_InsertFromDefer(void)199 void Cbuf_InsertFromDefer (void)
200 {
201 	Cbuf_InsertText(defer_text_buf);
202 	defer_text_buf[0] = 0;
203 }
204 
205 /**
206  * @sa Cmd_ExecuteString
207  * Pulls off @c \\n terminated lines of text from the command buffer and sends them
208  * through Cmd_ExecuteString, stopping when the buffer is empty.
209  * Normally called once per frame, but may be explicitly invoked.
210  * @note Do not call inside a command function!
211  */
Cbuf_Execute(void)212 void Cbuf_Execute (void)
213 {
214 	unsigned int i;
215 	char line[1024];
216 
217 	/* don't allow infinite alias loops */
218 	alias_count = 0;
219 
220 	while (cmd_text.cursize) {
221 		/* find a \n or ; line break */
222 		char* text = (char*) cmd_text.data;
223 		int quotes = 0;
224 
225 		for (i = 0; i < cmd_text.cursize; i++) {
226 			if (text[i] == '"')
227 				quotes++;
228 			/* don't break if inside a quoted string */
229 			if (!(quotes & 1) && text[i] == ';')
230 				break;
231 			if (text[i] == '\n')
232 				break;
233 		}
234 
235 		if (i > sizeof(line) - 1)
236 			i = sizeof(line) - 1;
237 
238 		memcpy(line, text, i);
239 		line[i] = 0;
240 
241 		/* delete the text from the command buffer and move remaining commands down
242 		 * this is necessary because commands (exec, alias) can insert data at the
243 		 * beginning of the text buffer */
244 		if (i == cmd_text.cursize)
245 			cmd_text.cursize = 0;
246 		else {
247 			i++;
248 			cmd_text.cursize -= i;
249 			memmove(text, text + i, cmd_text.cursize);
250 		}
251 
252 		/* execute the command line */
253 		Cmd_ExecuteString("%s", line);
254 
255 		if (cmdWait) {
256 			/* skip out while text still remains in buffer, leaving it
257 			 * for next frame */
258 			cmdWait = false;
259 			break;
260 		}
261 	}
262 }
263 
264 
265 /**
266  * @brief Adds command line parameters as script statements
267  * Commands lead with a +, and continue until another +
268  * Set commands are added early, so they are guaranteed to be set before
269  * the client and server initialize for the first time.
270  * Other commands are added late, after all initialization is complete.
271  * @sa Cbuf_AddLateCommands
272  */
Cbuf_AddEarlyCommands(bool clear)273 void Cbuf_AddEarlyCommands (bool clear)
274 {
275 	const int argc = Com_Argc();
276 	for (int i = 1; i < argc; i++) {
277 		const char* s = Com_Argv(i);
278 		if (!Q_streq(s, "+set"))
279 			continue;
280 		Cbuf_AddText("set %s %s\n", Com_Argv(i + 1), Com_Argv(i + 2));
281 		if (clear) {
282 			Com_ClearArgv(i);
283 			Com_ClearArgv(i + 1);
284 			Com_ClearArgv(i + 2);
285 		}
286 		i += 2;
287 	}
288 }
289 
290 /**
291  * @brief Adds command line parameters as script statements
292  * @note Commands lead with a + and continue until another + or -
293  * @return true if any late commands were added
294  * @sa Cbuf_AddEarlyCommands
295  */
Cbuf_AddLateCommands(void)296 bool Cbuf_AddLateCommands (void)
297 {
298 	/* build the combined string to parse from */
299 	int s = 0;
300 	const int argc = Com_Argc();
301 	for (int i = 1; i < argc; i++) {
302 		s += strlen(Com_Argv(i)) + 1;
303 	}
304 	if (!s)
305 		return false;
306 
307 	char* text = Mem_AllocTypeN(char, s + 1);
308 	for (int i = 1; i < argc; i++) {
309 		Q_strcat(text, s, "%s", Com_Argv(i));
310 		if (i != argc - 1)
311 			Q_strcat(text, s, " ");
312 	}
313 
314 	/* pull out the commands */
315 	char* build = Mem_AllocTypeN(char, s + 1);
316 
317 	for (int i = 0; i < s - 1; i++) {
318 		if (text[i] != '+')
319 			continue;
320 		i++;
321 
322 		int j;
323 		for (j = i; text[j] != '+' && text[j] != '-' && text[j] != '\0'; j++) {}
324 
325 		const char c = text[j];
326 		text[j] = '\0';
327 
328 		Q_strcat(build, s, "%s\n", text + i);
329 		text[j] = c;
330 		i = j - 1;
331 	}
332 
333 	const bool ret = build[0] != '\0';
334 	if (ret)
335 		Cbuf_AddText("%s", build);
336 
337 	Mem_Free(text);
338 	Mem_Free(build);
339 
340 	return ret;
341 }
342 
343 
344 /*
345 ==============================================================================
346 SCRIPT COMMANDS
347 ==============================================================================
348 */
349 
Cmd_Exec_f(void)350 static void Cmd_Exec_f (void)
351 {
352 	byte* f;
353 	char* f2;
354 	int len;
355 
356 	if (Cmd_Argc() != 2) {
357 		Com_Printf("Usage: %s <filename> : execute a script file\n", Cmd_Argv(0));
358 		return;
359 	}
360 
361 	len = FS_LoadFile(Cmd_Argv(1), &f);
362 	if (!f) {
363 		Com_Printf("couldn't execute %s\n", Cmd_Argv(1));
364 		return;
365 	}
366 	Com_Printf("executing %s\n", Cmd_Argv(1));
367 
368 	/* the file doesn't have a trailing 0, so we need to copy it off */
369 	f2 = Mem_AllocTypeN(char, len + 1);
370 	memcpy(f2, f, len);
371 	/* make really sure that there is a newline */
372 	f2[len] = 0;
373 
374 	Cbuf_InsertText(f2);
375 
376 	Mem_Free(f2);
377 	FS_FreeFile(f);
378 }
379 
380 
381 /**
382  * @brief Just prints the rest of the line to the console
383  */
Cmd_Echo_f(void)384 static void Cmd_Echo_f (void)
385 {
386 	int i;
387 
388 	for (i = 1; i < Cmd_Argc(); i++)
389 		Com_Printf("%s ", Cmd_Argv(i));
390 	Com_Printf("\n");
391 }
392 
393 /**
394  * @brief Creates a new command that executes a command string (possibly ; separated)
395  */
Cmd_Alias_f(void)396 static void Cmd_Alias_f (void)
397 {
398 	cmd_alias_t* a;
399 	char cmd[MAX_STRING_CHARS];
400 	size_t len;
401 	unsigned int hash;
402 	int i, c;
403 	const char* s;
404 
405 	if (Cmd_Argc() == 1) {
406 		Com_Printf("Current alias commands:\n");
407 		for (a = cmd_alias; a; a = a->next)
408 			Com_Printf("%s : %s\n", a->name, a->value);
409 		return;
410 	}
411 
412 	s = Cmd_Argv(1);
413 	len = strlen(s);
414 	if (len == 0)
415 		return;
416 
417 	if (len >= MAX_ALIAS_NAME) {
418 		Com_Printf("Alias name is too long\n");
419 		return;
420 	}
421 
422 	/* if the alias already exists, reuse it */
423 	hash = Com_HashKey(s, ALIAS_HASH_SIZE);
424 	for (a = cmd_alias_hash[hash]; a; a = a->hash_next) {
425 		if (Q_streq(s, a->name)) {
426 			Mem_Free(a->value);
427 			break;
428 		}
429 	}
430 
431 	if (!a) {
432 		a = Mem_PoolAllocType(cmd_alias_t, com_aliasSysPool);
433 		a->next = cmd_alias;
434 		/* cmd_alias_hash should be null on the first run */
435 		a->hash_next = cmd_alias_hash[hash];
436 		cmd_alias_hash[hash] = a;
437 		cmd_alias = a;
438 	}
439 	Q_strncpyz(a->name, s, sizeof(a->name));
440 
441 	/* copy the rest of the command line */
442 	cmd[0] = 0;					/* start out with a null string */
443 	c = Cmd_Argc();
444 	for (i = 2; i < c; i++) {
445 		Q_strcat(cmd, sizeof(cmd), "%s", Cmd_Argv(i));
446 		if (i != (c - 1))
447 			Q_strcat(cmd, sizeof(cmd), " ");
448 	}
449 
450 	if (Q_streq(Cmd_Argv(0), "aliasa"))
451 		a->archive = true;
452 
453 	a->value = Mem_PoolStrDup(cmd, com_aliasSysPool, 0);
454 }
455 
456 /**
457  * @brief Write lines containing "aliasa alias value" for all aliases
458  * with the archive flag set to true
459  * @param f Filehandle to write the aliases to
460  */
Cmd_WriteAliases(qFILE * f)461 void Cmd_WriteAliases (qFILE *f)
462 {
463 	cmd_alias_t* a;
464 
465 	for (a = cmd_alias; a; a = a->next)
466 		if (a->archive) {
467 			int i;
468 			FS_Printf(f, "aliasa %s \"", a->name);
469 			for (i = 0; i < strlen(a->value); i++) {
470 				if (a->value[i] == '"')
471 					FS_Printf(f, "\\\"");
472 				else
473 					FS_Printf(f, "%c", a->value[i]);
474 			}
475 			FS_Printf(f, "\"\n");
476 		}
477 }
478 
479 /*
480 =============================================================================
481 COMMAND EXECUTION
482 =============================================================================
483 */
484 
485 #define CMD_HASH_SIZE 32
486 
487 typedef struct cmd_function_s {
488 	struct cmd_function_s* next;
489 	struct cmd_function_s* hash_next;
490 	const char* name;
491 	const char* description;
492 	xcommand_t function;
493 	int (*completeParam) (const char* partial, const char** match);
494 	void* userdata;
495 } cmd_function_t;
496 
497 static int cmd_argc;
498 static char* cmd_argv[MAX_STRING_TOKENS];
499 static char cmd_args[MAX_STRING_CHARS];
500 static void* cmd_userdata;
501 
502 static cmd_function_t* cmd_functions;	/* possible commands to execute */
503 static cmd_function_t* cmd_functions_hash[CMD_HASH_SIZE];
504 
505 /**
506  * @brief Return the number of arguments of the current command.
507  * "command parameter" will result in a @c argc of 2, not 1.
508  * @return the number of arguments including the command itself.
509  * @sa Cmd_Argv
510  */
Cmd_Argc(void)511 int Cmd_Argc (void)
512 {
513 	return cmd_argc;
514 }
515 
516 /**
517  * @brief Returns a given argument
518  * @param[in] arg The argument at position arg in cmd_argv. @c 0 will return the command name.
519  * @return The argument from @c cmd_argv
520  * @sa Cmd_Argc
521  */
Cmd_Argv(int arg)522 const char* Cmd_Argv (int arg)
523 {
524 	if (arg >= cmd_argc)
525 		return "";
526 	return cmd_argv[arg];
527 }
528 
529 /**
530  * @brief Returns a single string containing argv(1) to argv(argc()-1)
531  */
Cmd_Args(void)532 const char* Cmd_Args (void)
533 {
534 	return cmd_args;
535 }
536 
537 /**
538  * @brief Return the userdata of the called command
539  */
Cmd_Userdata(void)540 void* Cmd_Userdata (void)
541 {
542 	return cmd_userdata;
543 }
544 
545 /**
546  * @brief Clears the argv vector and set argc to zero
547  * @sa Cmd_TokenizeString
548  */
Cmd_BufClear(void)549 void Cmd_BufClear (void)
550 {
551 	int i;
552 
553 	/* clear the args from the last string */
554 	for (i = 0; i < cmd_argc; i++) {
555 		Mem_Free(cmd_argv[i]);
556 		cmd_argv[i] = nullptr;
557 	}
558 
559 	cmd_argc = 0;
560 	cmd_args[0] = 0;
561 	cmd_userdata = nullptr;
562 }
563 
564 /**
565  * @brief Parses the given string into command line tokens.
566  * @note @c cmd_argv and @c cmd_argv are filled and set here
567  * @note *cvars will be expanded unless they are in a quoted token
568  * @sa Com_MacroExpandString
569  * @param[in] text The text to parse and tokenize
570  * @param[in] macroExpand expand cvar string with their values
571  * @param[in] replaceWhitespaces Replace \\t and \\n to \t and \n
572  */
Cmd_TokenizeString(const char * text,bool macroExpand,bool replaceWhitespaces)573 void Cmd_TokenizeString (const char* text, bool macroExpand, bool replaceWhitespaces)
574 {
575 	const char* expanded;
576 
577 	Cmd_BufClear();
578 
579 	/* macro expand the text */
580 	if (macroExpand) {
581 		expanded = Com_MacroExpandString(text);
582 		if (expanded)
583 			text = expanded;
584 	}
585 
586 	while (1) {
587 		/* skip whitespace up to a newline */
588 		while (*text && *text <= ' ' && *text != '\n') {
589 			text++;
590 		}
591 
592 		if (*text == '\n') {	/* a newline seperates commands in the buffer */
593 			text++;
594 			break;
595 		}
596 
597 		if (!*text)
598 			return;
599 
600 		/* set cmd_args to everything after the first arg */
601 		if (cmd_argc == 1) {
602 			Q_strncpyz(cmd_args, text, sizeof(cmd_args));
603 			Com_Chop(cmd_args);
604 		}
605 
606 		const char* com_token = Com_Parse(&text, nullptr, 0, replaceWhitespaces);
607 		if (!text)
608 			return;
609 
610 		if (cmd_argc < MAX_STRING_TOKENS) {
611 			/* check first char of string if it is a variable */
612 			if (com_token[0] == '*') {
613 				com_token++;
614 				com_token = Cvar_GetString(com_token);
615 			}
616 			assert(!cmd_argv[cmd_argc]);
617 			cmd_argv[cmd_argc] = Mem_PoolStrDup(com_token, com_cmdSysPool, 0);
618 			cmd_argc++;
619 		}
620 	}
621 }
622 
623 /**
624  * @brief Returns the command description for a given command
625  * @param[in] cmd_name Command id in global command array
626  * @note never returns a nullptr pointer
627  * @todo - search alias, too
628  */
Cmd_GetCommandDesc(const char * cmd_name)629 const char* Cmd_GetCommandDesc (const char* cmd_name)
630 {
631 	cmd_function_t* cmd;
632 	char* sep = nullptr;
633 	unsigned int hash;
634 	char searchName[MAX_VAR];
635 
636 	/* remove parameters */
637 	Q_strncpyz(searchName, cmd_name, sizeof(searchName));
638 	sep = strstr(searchName, " ");
639 	if (sep)
640 		*sep = '\0';
641 
642 	/* fail if the command already exists */
643 	hash = Com_HashKey(searchName, CMD_HASH_SIZE);
644 	for (cmd = cmd_functions_hash[hash]; cmd; cmd = cmd->hash_next) {
645 		if (!Q_streq(searchName, cmd->name))
646 			continue;
647 		if (cmd->description)
648 			return cmd->description;
649 		return "";
650 	}
651 	return "";
652 }
653 
Cmd_GenericCompleteFunction(char const * candidate,char const * partial,char const ** match)654 bool Cmd_GenericCompleteFunction(char const* candidate, char const* partial, char const** match)
655 {
656 	static char matchString[MAX_QPATH];
657 
658 	if (!Q_strstart(candidate, partial))
659 		return false;
660 
661 	if (!*match) {
662 		/* First match. */
663 		Q_strncpyz(matchString, candidate, sizeof(matchString));
664 		*match = matchString;
665 	} else {
666 		/* Subsequent match, determine common prefix with previous match(es). */
667 		char*       dst = matchString;
668 		char const* src = candidate;
669 		while (*dst == *src) {
670 			++dst;
671 			++src;
672 		}
673 		*dst = '\0';
674 	}
675 
676 	return true;
677 }
678 
679 /**
680  * @param[in] cmd_name The name the command we want to add the complete function
681  * @param[in] function The complete function pointer
682  * @sa Cmd_AddCommand
683  * @sa Cmd_CompleteCommandParameters
684  */
Cmd_AddParamCompleteFunction(const char * cmd_name,int (* function)(const char * partial,const char ** match))685 void Cmd_AddParamCompleteFunction (const char* cmd_name, int (*function)(const char* partial, const char** match))
686 {
687 	cmd_function_t* cmd;
688 	unsigned int hash;
689 
690 	if (!cmd_name || !cmd_name[0])
691 		return;
692 
693 	hash = Com_HashKey(cmd_name, CMD_HASH_SIZE);
694 	for (cmd = cmd_functions_hash[hash]; cmd; cmd = cmd->hash_next) {
695 		if (Q_streq(cmd_name, cmd->name)) {
696 			cmd->completeParam = function;
697 			return;
698 		}
699 	}
700 }
701 
702 /**
703  * @brief Fetches the userdata for a console command.
704  * @param[in] cmd_name The name the command we want to add edit
705  * @return @c nullptr if no userdata was set or the command wasn't found, the userdata
706  * pointer if it was found and set
707  * @sa Cmd_AddCommand
708  * @sa Cmd_CompleteCommandParameters
709  * @sa Cmd_AddUserdata
710  */
Cmd_GetUserdata(const char * cmd_name)711 void* Cmd_GetUserdata (const char* cmd_name)
712 {
713 	cmd_function_t* cmd;
714 	unsigned int hash;
715 
716 	if (!cmd_name || !cmd_name[0]) {
717 		Com_Printf("Cmd_GetUserdata: Invalide parameter\n");
718 		return nullptr;
719 	}
720 
721 	hash = Com_HashKey(cmd_name, CMD_HASH_SIZE);
722 	for (cmd = cmd_functions_hash[hash]; cmd; cmd = cmd->hash_next) {
723 		if (Q_streq(cmd_name, cmd->name)) {
724 			return cmd->userdata;
725 		}
726 	}
727 
728 	Com_Printf("Cmd_GetUserdata: '%s' not found\n", cmd_name);
729 	return nullptr;
730 }
731 
732 /**
733  * @brief Adds userdata to the console command.
734  * @param[in] cmd_name The name the command we want to add edit
735  * @param[in] userdata for this function
736  * @sa Cmd_AddCommand
737  * @sa Cmd_CompleteCommandParameters
738  * @sa Cmd_GetUserdata
739  */
Cmd_AddUserdata(const char * cmd_name,void * userdata)740 void Cmd_AddUserdata (const char* cmd_name, void* userdata)
741 {
742 	cmd_function_t* cmd;
743 	unsigned int hash;
744 
745 	if (!cmd_name || !cmd_name[0])
746 		return;
747 
748 	hash = Com_HashKey(cmd_name, CMD_HASH_SIZE);
749 	for (cmd = cmd_functions_hash[hash]; cmd; cmd = cmd->hash_next) {
750 		if (Q_streq(cmd_name, cmd->name)) {
751 			cmd->userdata = userdata;
752 			return;
753 		}
754 	}
755 }
756 
757 /**
758  * @brief Add a new command to the script interface
759  * @param[in] cmdName The name the command is available via script interface
760  * @param[in] function The function pointer
761  * @param[in] desc A short description of what the cmd does. It is shown for e.g. the tab
762  * completion or the command list.
763  * @sa Cmd_RemoveCommand
764  */
Cmd_AddCommand(const char * cmdName,xcommand_t function,const char * desc)765 void Cmd_AddCommand (const char* cmdName, xcommand_t function, const char* desc)
766 {
767 	if (!Q_strvalid(cmdName))
768 		return;
769 
770 	/* fail if the command is a variable name */
771 	if (Cvar_GetString(cmdName)[0]) {
772 		Com_Printf("Cmd_AddCommand: %s already defined as a var\n", cmdName);
773 		return;
774 	}
775 
776 	/* fail if the command already exists */
777 	const unsigned int hash = Com_HashKey(cmdName, CMD_HASH_SIZE);
778 	for (cmd_function_t* cmd = cmd_functions_hash[hash]; cmd; cmd = cmd->hash_next) {
779 		if (Q_streq(cmdName, cmd->name)) {
780 			Com_DPrintf(DEBUG_COMMANDS, "Cmd_AddCommand: %s already defined\n", cmdName);
781 			return;
782 		}
783 	}
784 
785 	cmd_function_t* const cmd = Mem_PoolAllocType(cmd_function_t, com_cmdSysPool);
786 	cmd->name = cmdName;
787 	cmd->description = desc;
788 	cmd->function = function;
789 	cmd->completeParam = nullptr;
790 	HASH_Add(cmd_functions_hash, cmd, hash);
791 	cmd->next = cmd_functions;
792 	cmd_functions = cmd;
793 }
794 
795 /**
796  * @brief Removes a command from script interface
797  * @param[in] cmd_name The script interface function name to remove
798  * @sa Cmd_AddCommand
799  */
Cmd_RemoveCommand(const char * cmd_name)800 void Cmd_RemoveCommand (const char* cmd_name)
801 {
802 	cmd_function_t* cmd, **back;
803 	unsigned int hash;
804 	hash = Com_HashKey(cmd_name, CMD_HASH_SIZE);
805 	back = &cmd_functions_hash[hash];
806 
807 	while (1) {
808 		cmd = *back;
809 		if (!cmd) {
810 			Com_Printf("Cmd_RemoveCommand: %s not added\n", cmd_name);
811 			return;
812 		}
813 		if (!Q_strcasecmp(cmd_name, cmd->name)) {
814 			*back = cmd->hash_next;
815 			break;
816 		}
817 		back = &cmd->hash_next;
818 	}
819 
820 	back = &cmd_functions;
821 	while (1) {
822 		cmd = *back;
823 		if (!cmd) {
824 			Com_Printf("Cmd_RemoveCommand: %s not added\n", cmd_name);
825 			return;
826 		}
827 		if (Q_streq(cmd_name, cmd->name)) {
828 			*back = cmd->next;
829 			Mem_Free(cmd);
830 			return;
831 		}
832 		back = &cmd->next;
833 	}
834 }
835 
836 /**
837  * @brief Checks whether a function exists already
838  * @param[in] cmd_name The script interface function name to search for
839  */
Cmd_Exists(const char * cmd_name)840 bool Cmd_Exists (const char* cmd_name)
841 {
842 	cmd_function_t* cmd;
843 	unsigned int hash;
844 	hash = Com_HashKey(cmd_name, CMD_HASH_SIZE);
845 
846 	for (cmd = cmd_functions_hash[hash]; cmd; cmd = cmd->hash_next) {
847 		if (Q_streq(cmd_name, cmd->name))
848 			return true;
849 	}
850 
851 	return false;
852 }
853 
854 /**
855  * @brief Unix like tab completion for console commands parameters
856  * @param[in] command The command we try to complete the parameter for
857  * @param[in] partial The beginning of the parameter we try to complete
858  * @param[out] match The command we are writing back (if something was found)
859  * @sa Cvar_CompleteVariable
860  * @sa Key_CompleteCommand
861  */
Cmd_CompleteCommandParameters(const char * command,const char * partial,const char ** match)862 int Cmd_CompleteCommandParameters (const char* command, const char* partial, const char** match)
863 {
864 	const cmd_function_t* cmd;
865 	unsigned int hash;
866 
867 	/* check for partial matches in commands */
868 	hash = Com_HashKey(command, CMD_HASH_SIZE);
869 	for (cmd = cmd_functions_hash[hash]; cmd; cmd = cmd->hash_next) {
870 		if (!Q_strcasecmp(command, cmd->name)) {
871 			if (!cmd->completeParam)
872 				return 0;
873 			return cmd->completeParam(partial, match);
874 		}
875 	}
876 	return 0;
877 }
878 
879 /**
880  * @brief Unix like tab completion for console commands
881  * @param[in] partial The beginning of the command we try to complete
882  * @param[out] match The found entry of the list we are searching, in case of more than one entry their common suffix is returned.
883  * @sa Cvar_CompleteVariable
884  * @sa Key_CompleteCommand
885  */
Cmd_CompleteCommand(const char * partial,const char ** match)886 int Cmd_CompleteCommand (const char* partial, const char** match)
887 {
888 	if (partial[0] == '\0')
889 		return 0;
890 
891 	int n = 0;
892 
893 	/* check for partial matches in commands */
894 	for (cmd_function_t const* cmd = cmd_functions; cmd; cmd = cmd->next) {
895 		if (Cmd_GenericCompleteFunction(cmd->name, partial, match)) {
896 			Com_Printf("[cmd] %s\n", cmd->name);
897 			if (cmd->description)
898 				Com_Printf(S_COLOR_GREEN "      %s\n", cmd->description);
899 			++n;
900 		}
901 	}
902 
903 	/* and then aliases */
904 	for (cmd_alias_t const* a = cmd_alias; a; a = a->next) {
905 		if (Cmd_GenericCompleteFunction(a->name, partial, match)) {
906 			Com_Printf("[ali] %s\n", a->name);
907 			++n;
908 		}
909 	}
910 
911 	return n;
912 }
913 
Cmd_vExecuteString(const char * fmt,va_list ap)914 void Cmd_vExecuteString (const char* fmt, va_list ap)
915 {
916 	char text[1024];
917 
918 	Q_vsnprintf(text, sizeof(text), fmt, ap);
919 
920 	const cmd_function_t* cmd;
921 	const cmd_alias_t* a;
922 	const char* str;
923 	unsigned int hash;
924 
925 	Com_DPrintf(DEBUG_COMMANDS, "ExecuteString: '%s'\n", text);
926 
927 	Cmd_TokenizeString(text, true);
928 
929 	/* execute the command line */
930 	if (!Cmd_Argc())
931 		/* no tokens */
932 		return;
933 
934 	str = Cmd_Argv(0);
935 
936 	/* check functions */
937 	hash = Com_HashKey(str, CMD_HASH_SIZE);
938 	for (cmd = cmd_functions_hash[hash]; cmd; cmd = cmd->hash_next) {
939 		if (!Q_strcasecmp(str, cmd->name)) {
940 			if (!cmd->function) {	/* forward to server command */
941 				Cmd_ExecuteString("cmd %s", text);
942 			} else {
943 				cmd_userdata = cmd->userdata;
944 				cmd->function();
945 			}
946 			return;
947 		}
948 	}
949 
950 	/* check alias */
951 	hash = Com_HashKey(str, ALIAS_HASH_SIZE);
952 	for (a = cmd_alias_hash[hash]; a; a = a->hash_next) {
953 		if (!Q_strcasecmp(str, a->name)) {
954 			if (++alias_count == ALIAS_LOOP_COUNT) {
955 				Com_Printf("ALIAS_LOOP_COUNT\n");
956 				return;
957 			}
958 			Cbuf_InsertText(a->value);
959 			return;
960 		}
961 	}
962 
963 	/* check cvars */
964 	if (Cvar_Command())
965 		return;
966 
967 	/* send it as a server command if we are connected */
968 	Cmd_ForwardToServer();
969 }
970 
971 /**
972  * @brief A complete command line has been parsed, so try to execute it
973  * @todo lookupnoadd the token to speed search?
974  */
Cmd_ExecuteString(const char * text,...)975 void Cmd_ExecuteString (const char* text, ...)
976 {
977 	va_list ap;
978 
979 	va_start(ap, text);
980 	Cmd_vExecuteString(text, ap);
981 	va_end(ap);
982 }
983 
984 /**
985  * @brief List all available script interface functions
986  */
Cmd_List_f(void)987 static void Cmd_List_f (void)
988 {
989 	const cmd_function_t* cmd;
990 	const cmd_alias_t* alias;
991 	int i = 0, j = 0, c, l = 0;
992 	const char* token = nullptr;
993 
994 	c = Cmd_Argc();
995 
996 	if (c == 2) {
997 		token = Cmd_Argv(1);
998 		l = strlen(token);
999 	}
1000 
1001 	for (cmd = cmd_functions; cmd; cmd = cmd->next, i++) {
1002 		if (c == 2 && strncmp(cmd->name, token, l)) {
1003 			i--;
1004 			continue;
1005 		}
1006 		Com_Printf("[cmd] %s\n", cmd->name);
1007 		if (cmd->description)
1008 			Com_Printf(S_COLOR_GREEN "      %s\n", cmd->description);
1009 	}
1010 	/* check alias */
1011 	for (alias = cmd_alias; alias; alias = alias->next, j++) {
1012 		if (c == 2 && strncmp(alias->name, token, l)) {
1013 			j--;
1014 			continue;
1015 		}
1016 		Com_Printf("[ali] %s\n", alias->name);
1017 	}
1018 	Com_Printf("%i commands\n", i);
1019 	Com_Printf("%i macros\n", j);
1020 }
1021 
1022 
1023 /**
1024  * @brief Autocomplete function for exec command
1025  * @sa Cmd_AddParamCompleteFunction
1026  */
Cmd_CompleteExecCommand(const char * partial,const char ** match)1027 static int Cmd_CompleteExecCommand (const char* partial, const char** match)
1028 {
1029 	int n = 0;
1030 	while (char const* filename = FS_NextFileFromFileList("*.cfg")) {
1031 		if (Cmd_GenericCompleteFunction(filename, partial, match)) {
1032 			Com_Printf("%s\n", filename);
1033 			++n;
1034 		}
1035 	}
1036 	FS_NextFileFromFileList(nullptr);
1037 
1038 	return n;
1039 }
1040 
1041 /**
1042  * @brief Dummy binding if you don't want unknown commands forwarded to the server
1043  */
Cmd_Dummy_f(void)1044 void Cmd_Dummy_f (void)
1045 {
1046 }
1047 
1048 #ifdef DEBUG
1049 /**
1050  * @brief Tries to call every command (except the quit command) that's currently
1051  * in the command list - Use this function to test whether some console bindings
1052  * produce asserts or segfaults in some situations.
1053  */
Cmd_Test_f(void)1054 static void Cmd_Test_f (void)
1055 {
1056 	cmd_function_t* cmd;
1057 
1058 	for (cmd = cmd_functions; cmd; cmd = cmd->next) {
1059 		if (!Q_streq(cmd->name, "quit"))
1060 			Cmd_ExecuteString("%s", cmd->name);
1061 	}
1062 }
1063 
Cmd_PrintDebugCommands(void)1064 void Cmd_PrintDebugCommands (void)
1065 {
1066 	const cmd_function_t* cmd;
1067 	const char* otherCommands[] = {"mem_stats", "cl_configstrings", "cl_userinfo", "devmap"};
1068 	int num = lengthof(otherCommands);
1069 
1070 	Com_Printf("Debug commands:\n");
1071 	for (cmd = cmd_functions; cmd; cmd = cmd->next) {
1072 		if (Q_strstart(cmd->name, "debug_"))
1073 			Com_Printf(" * %s\n   %s\n", cmd->name, cmd->description);
1074 	}
1075 
1076 	Com_Printf("Other useful commands:\n");
1077 	while (num) {
1078 		const char* desc = Cmd_GetCommandDesc(otherCommands[num - 1]);
1079 		Com_Printf(" * %s\n   %s\n", otherCommands[num - 1], desc);
1080 		num--;
1081 	}
1082 	Com_Printf(" * sv debug_showall\n"
1083 			"   make everything visible to everyone\n"
1084 			" * sv debug_actorinvlist\n"
1085 			"   Show the whole inv of all actors on the server console\n"
1086 			);
1087 	Com_Printf("\n");
1088 }
1089 #endif
1090 
Cmd_Init(void)1091 void Cmd_Init (void)
1092 {
1093 	/* register our commands */
1094 	Cmd_AddCommand("cmdlist", Cmd_List_f, "List all commands to game console");
1095 	Cmd_AddCommand("exec", Cmd_Exec_f, "Execute a script file");
1096 	Cmd_AddParamCompleteFunction("exec", Cmd_CompleteExecCommand);
1097 	Cmd_AddCommand("echo", Cmd_Echo_f, "Print to game console");
1098 	Cmd_AddCommand("wait", Cmd_Wait_f);
1099 	Cmd_AddCommand("alias", Cmd_Alias_f, "Creates a new command that executes a command string");
1100 	Cmd_AddCommand("aliasa", Cmd_Alias_f, "Creates a new, persistent command that executes a command string");
1101 	Cmd_AddCommand("cmdclose", Cmd_Close_f, "Close the command buffer");
1102 	Cmd_AddCommand("cmdopen", Cmd_Open_f, "Open the command buffer again");
1103 #ifdef DEBUG
1104 	Cmd_AddCommand("debug_cmdtest", Cmd_Test_f, "Calls every command in the current list");
1105 #endif
1106 }
1107 
Cmd_Shutdown(void)1108 void Cmd_Shutdown (void)
1109 {
1110 	OBJZERO(cmd_functions_hash);
1111 	cmd_functions = nullptr;
1112 
1113 	OBJZERO(cmd_alias_hash);
1114 	cmd_alias = nullptr;
1115 	alias_count = 0;
1116 
1117 	OBJZERO(cmd_argv);
1118 	cmd_argc = 0;
1119 
1120 	cmdWait = false;
1121 	cmdClosed = false;
1122 
1123 	Mem_FreePool(com_cmdSysPool);
1124 }
1125