1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3 
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
8 
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12 
13 See the GNU General Public License for more details.
14 
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 
19 */
20 // cmd.c -- Quake script command processing module
21 
22 #include <string.h>
23 
24 #include "client.h"
25 #include "cmd.h"
26 #include "common.h"
27 #include "console.h"
28 #include "cvar.h"
29 #include "quakedef.h"
30 #include "shell.h"
31 #include "sys.h"
32 #include "zone.h"
33 
34 #ifdef NQ_HACK
35 #include "host.h"
36 #include "protocol.h"
37 #endif
38 
39 #if defined(QW_HACK) && !defined(SERVERONLY)
40 static void Cmd_ForwardToServer_f(void);
41 #endif
42 
43 #define	MAX_ALIAS_NAME	32
44 
45 typedef struct cmdalias_s {
46     char name[MAX_ALIAS_NAME];
47     const char *value;
48     struct stree_node stree;
49 } cmdalias_t;
50 
51 #define cmdalias_entry(ptr) container_of(ptr, struct cmdalias_s, stree)
52 static DECLARE_STREE_ROOT(cmdalias_tree);
53 
54 static qboolean cmd_wait;
55 
56 cvar_t cl_warncmd = { "cl_warncmd", "0" };
57 
58 //=============================================================================
59 
60 /*
61 ============
62 Cmd_Wait_f
63 
64 Causes execution of the remainder of the command buffer to be delayed until
65 next frame.  This allows commands like:
66 bind g "impulse 5 ; +attack ; wait ; -attack ; impulse 2"
67 ============
68 */
69 void
Cmd_Wait_f(void)70 Cmd_Wait_f(void)
71 {
72     cmd_wait = true;
73 }
74 
75 /*
76 =============================================================================
77 
78 						COMMAND BUFFER
79 
80 =============================================================================
81 */
82 
83 static sizebuf_t cmd_text;
84 #ifdef QW_HACK
85 static byte cmd_text_buf[8192];
86 #endif
87 
88 /*
89 ============
90 Cbuf_Init
91 ============
92 */
93 void
Cbuf_Init(void)94 Cbuf_Init(void)
95 {
96 #ifdef NQ_HACK
97     SZ_Alloc(&cmd_text, 8192);
98 #endif
99 #ifdef QW_HACK
100     cmd_text.data = cmd_text_buf;
101     cmd_text.maxsize = sizeof(cmd_text_buf);
102     cmd_text.cursize = 0;
103 #endif
104 }
105 
106 
107 /*
108 ============
109 Cbuf_AddText
110 
111 Adds command text at the end of the buffer
112 ============
113 */
114 void
Cbuf_AddText(const char * fmt,...)115 Cbuf_AddText(const char *fmt, ...)
116 {
117     va_list ap;
118     int len, maxlen;
119     char *buf;
120 
121     buf = (char *)cmd_text.data + cmd_text.cursize;
122     maxlen = cmd_text.maxsize - cmd_text.cursize;
123     va_start(ap, fmt);
124     len = vsnprintf(buf, maxlen, fmt, ap);
125     va_end(ap);
126 
127     if (cmd_text.cursize + len < cmd_text.maxsize)
128 	cmd_text.cursize += len;
129     else
130 	Con_Printf("%s: overflow\n", __func__);
131 }
132 
133 
134 /*
135 ============
136 Cbuf_InsertText
137 
138 Adds command text immediately after the current command (i.e. at the start
139 of the command buffer). Adds a \n to the text.
140 ============
141 */
142 void
Cbuf_InsertText(const char * text)143 Cbuf_InsertText(const char *text)
144 {
145     int len;
146 
147     len = strlen(text);
148     if (cmd_text.cursize) {
149 	if (cmd_text.cursize + len + 1 > cmd_text.maxsize)
150 	    Sys_Error("%s: overflow", __func__);
151 
152 	/* move any commands still remaining in the exec buffer */
153 	memmove(cmd_text.data + len + 1, cmd_text.data, cmd_text.cursize);
154 	memcpy(cmd_text.data, text, len);
155 	cmd_text.data[len] = '\n';
156 	cmd_text.cursize += len + 1;
157     } else {
158 	Cbuf_AddText("%s\n", text);
159     }
160 }
161 
162 /*
163 ============
164 Cbuf_Execute
165 ============
166 */
Cbuf_Execute(void)167 void Cbuf_Execute(void)
168 {
169    int len;
170    char line[1024];
171 
172    while (cmd_text.cursize)
173    {
174       /* find a \n or ; line break */
175       char *text = (char *)cmd_text.data;
176       int quotes = 0;
177       int maxlen = qmin(cmd_text.cursize, (int)sizeof(line));
178 
179       for (len = 0; len < maxlen; len++)
180       {
181          if (text[len] == '"')
182             quotes++;
183          if (!(quotes & 1) && text[len] == ';')
184             break;		/* don't break if inside a quoted string */
185          if (text[len] == '\n')
186             break;
187       }
188       if (len == sizeof(line)) {
189          Con_Printf("%s: command truncated\n", __func__);
190          len--;
191       }
192       memcpy(line, text, len);
193       line[len] = 0;
194 
195       /*
196        * delete the text from the command buffer and move remaining commands
197        * down this is necessary because commands (exec, alias) can insert
198        * data at the beginning of the text buffer
199        */
200       if (len == cmd_text.cursize)
201          cmd_text.cursize = 0;
202       else {
203          len++; /* skip the terminating character */
204          cmd_text.cursize -= len;
205          memmove(text, text + len, cmd_text.cursize);
206       }
207 
208       /* execute the command line */
209 #ifdef NQ_HACK
210       Cmd_ExecuteString(line, src_command);
211 #endif
212 #ifdef QW_HACK
213       Cmd_ExecuteString(line);
214 #endif
215 
216       if (cmd_wait)
217       {
218          /*
219           * skip out while text still remains in buffer, leaving it for
220           * next frame
221           */
222          cmd_wait = false;
223          break;
224       }
225    }
226 }
227 
228 /*
229 ==============================================================================
230 
231 				SCRIPT COMMANDS
232 
233 ==============================================================================
234 */
235 
236 /*
237 ===============
238 Cmd_StuffCmds_f
239 
240 Adds command line parameters as script statements
241 Commands lead with a +, and continue until a - or another +
242 quake +prog jctest.qp +cmd amlev1
243 quake -nosound +cmd amlev1
244 ===============
245 */
246 void
Cmd_StuffCmds_f(void)247 Cmd_StuffCmds_f(void)
248 {
249     int i, j;
250     int s;
251     char *text, *build, c;
252 
253     if (Cmd_Argc() != 1) {
254 	Con_Printf("stuffcmds : execute command line parameters\n");
255 	return;
256     }
257 // build the combined string to parse from
258     s = 0;
259     for (i = 1; i < com_argc; i++) {
260 	if (!com_argv[i])
261 	    continue;		// NEXTSTEP nulls out -NXHost
262 	s += strlen(com_argv[i]) + 1;
263     }
264     if (!s)
265 	return;
266 
267     text = (char*)Z_Malloc(s + 1);
268     text[0] = 0;
269     for (i = 1; i < com_argc; i++) {
270 	if (!com_argv[i])
271 	    continue;		// NEXTSTEP nulls out -NXHost
272 	strcat(text, com_argv[i]);
273 	if (i != com_argc - 1)
274 	    strcat(text, " ");
275     }
276 
277 // pull out the commands
278     build = (char*)Z_Malloc(s + 1);
279     build[0] = 0;
280 
281     for (i = 0; i < s - 1; i++) {
282 	if (text[i] == '+') {
283 	    i++;
284 
285 	    for (j = i;
286 		 (text[j] != '+') && (text[j] != '-') && (text[j] != 0); j++);
287 
288 	    c = text[j];
289 	    text[j] = 0;
290 
291 	    strcat(build, text + i);
292 	    strcat(build, "\n");
293 	    text[j] = c;
294 	    i = j - 1;
295 	}
296     }
297 
298     if (build[0])
299 	Cbuf_InsertText(build);
300 
301     Z_Free(text);
302     Z_Free(build);
303 }
304 
305 
306 /*
307 ===============
308 Cmd_Exec_f
309 ===============
310 */
311 void
Cmd_Exec_f(void)312 Cmd_Exec_f(void)
313 {
314     char *f;
315     int mark;
316 
317     if (Cmd_Argc() != 2) {
318 	Con_Printf("exec <filename> : execute a script file\n");
319 	return;
320     }
321     // FIXME: is this safe freeing the hunk here???
322     mark = Hunk_LowMark();
323     f = (char*)COM_LoadHunkFile(Cmd_Argv(1));
324     if (!f) {
325 	Con_Printf("couldn't exec %s\n", Cmd_Argv(1));
326 	return;
327     }
328     if (cl_warncmd.value || developer.value)
329 	Con_Printf("execing %s\n", Cmd_Argv(1));
330 
331     Cbuf_InsertText(f);
332     Hunk_FreeToLowMark(mark);
333 }
334 
335 
336 /*
337 ===============
338 Cmd_Echo_f
339 
340 Just prints the rest of the line to the console
341 ===============
342 */
343 void
Cmd_Echo_f(void)344 Cmd_Echo_f(void)
345 {
346     int i;
347 
348     for (i = 1; i < Cmd_Argc(); i++)
349 	Con_Printf("%s ", Cmd_Argv(i));
350     Con_Printf("\n");
351 }
352 
353 /*
354 ===============
355 Cmd_Alias_f
356 
357 Creates a new command that executes a command string (possibly ; seperated)
358 ===============
359 */
360 
361 static struct cmdalias_s *
Cmd_Alias_Find(const char * name)362 Cmd_Alias_Find(const char *name)
363 {
364     struct cmdalias_s *ret = NULL;
365     struct stree_node *n;
366 
367     n = STree_Find(&cmdalias_tree, name);
368     if (n)
369 	ret = cmdalias_entry(n);
370 
371     return ret;
372 }
373 
374 void
Cmd_Alias_f(void)375 Cmd_Alias_f(void)
376 {
377     cmdalias_t *a;
378     char cmd[1024];
379     int i, c;
380     const char *s;
381     char *newval;
382     size_t cmd_len;
383     struct stree_node *node;
384 
385     if (Cmd_Argc() == 1) {
386 	Con_Printf("Current alias commands:\n");
387 	STree_ForEach_After_NullStr(&cmdalias_tree, node) {
388 	    a = cmdalias_entry(node);
389 	    Con_Printf("%s : %s\n", a->name, a->value);
390 	}
391 	return;
392     }
393 
394     s = Cmd_Argv(1);
395     if (strlen(s) >= MAX_ALIAS_NAME) {
396 	Con_Printf("Alias name is too long\n");
397 	return;
398     }
399 
400     // if the alias already exists, reuse it
401     a = Cmd_Alias_Find(s);
402     if (a)
403 	Z_Free(a->value);
404 
405     if (!a) {
406 	a = (cmdalias_t*)Z_Malloc(sizeof(cmdalias_t));
407 	strcpy(a->name, s);
408 	a->stree.string = a->name;
409 	STree_Insert(&cmdalias_tree, &a->stree);
410     }
411 
412 // copy the rest of the command line
413     cmd[0] = 0;			// start out with a null string
414     c = Cmd_Argc();
415     cmd_len = 1;
416     for (i = 2; i < c; i++) {
417 	cmd_len += strlen(Cmd_Argv(i));
418 	if (i != c - 1)
419 		cmd_len++;
420 	if (cmd_len >= sizeof(cmd)) {
421 	    Con_Printf("Alias value is too long\n");
422 	    cmd[0] = 0;	// nullify the string
423 	    break;
424 	}
425 	strcat(cmd, Cmd_Argv(i));
426 	if (i != c - 1)
427 	    strcat(cmd, " ");
428     }
429     strcat(cmd, "\n");
430 
431     newval = (char*)Z_Malloc(strlen(cmd) + 1);
432     strcpy(newval, cmd);
433     a->value = newval;
434 }
435 
436 /*
437 =============================================================================
438 
439 				COMMAND EXECUTION
440 
441 =============================================================================
442 */
443 
444 typedef struct cmd_function_s {
445     const char *name;
446     xcommand_t function;
447     cmd_arg_f completion;
448     struct stree_node stree;
449 } cmd_function_t;
450 
451 #define cmd_entry(ptr) container_of(ptr, struct cmd_function_s, stree)
452 static DECLARE_STREE_ROOT(cmd_tree);
453 
454 #define	MAX_ARGS		80
455 static int cmd_argc;
456 static const char *cmd_argv[MAX_ARGS];
457 static const char *cmd_null_string = "";
458 static const char *cmd_args = NULL;
459 
460 #ifdef NQ_HACK
461 cmd_source_t cmd_source;
462 #endif
463 
464 /*
465 ============
466 Cmd_Init
467 ============
468 */
469 void
Cmd_Init(void)470 Cmd_Init(void)
471 {
472 //
473 // register our commands
474 //
475     Cmd_AddCommand("stuffcmds", Cmd_StuffCmds_f);
476     Cmd_AddCommand("exec", Cmd_Exec_f);
477     Cmd_AddCommand("echo", Cmd_Echo_f);
478     Cmd_AddCommand("alias", Cmd_Alias_f);
479     Cmd_AddCommand("wait", Cmd_Wait_f);
480 #ifdef NQ_HACK
481     Cmd_AddCommand("cmd", Cmd_ForwardToServer);
482 #elif defined(QW_HACK) && !defined(SERVERONLY)
483     Cmd_AddCommand("cmd", Cmd_ForwardToServer_f);
484 #endif
485 }
486 
487 /*
488 ============
489 Cmd_Argc
490 ============
491 */
492 int
Cmd_Argc(void)493 Cmd_Argc(void)
494 {
495     return cmd_argc;
496 }
497 
498 /*
499 ============
500 Cmd_Argv
501 ============
502 */
503 const char *
Cmd_Argv(int arg)504 Cmd_Argv(int arg)
505 {
506     if (arg >= cmd_argc)
507 	return cmd_null_string;
508     return cmd_argv[arg];
509 }
510 
511 /*
512 ============
513 Cmd_Args
514 
515 Returns a single string containing argv(1) to argv(argc()-1)
516 ============
517 */
518 const char *
Cmd_Args(void)519 Cmd_Args(void)
520 {
521     // FIXME - check necessary?
522     if (!cmd_args)
523 	return "";
524     return cmd_args;
525 }
526 
527 
528 /*
529 ============
530 Cmd_TokenizeString
531 
532 Parses the given string into command line tokens.
533 ============
534 */
535 void
Cmd_TokenizeString(const char * text)536 Cmd_TokenizeString(const char *text)
537 {
538     int i;
539     char *arg;
540 
541 // clear the args from the last string
542     for (i = 0; i < cmd_argc; i++)
543 	Z_Free(cmd_argv[i]);
544 
545     cmd_argc = 0;
546     cmd_args = NULL;
547 
548     while (1) {
549 // skip whitespace up to a /n
550 	while (*text && *text <= ' ' && *text != '\n') {
551 	    text++;
552 	}
553 
554 	if (*text == '\n') {	// a newline seperates commands in the buffer
555 	    text++;
556 	    break;
557 	}
558 
559 	if (!*text)
560 	    return;
561 
562 	if (cmd_argc == 1)
563 	    cmd_args = text;
564 
565 	text = COM_Parse(text);
566 	if (!text)
567 	    return;
568 
569 	if (cmd_argc < MAX_ARGS) {
570 	    arg = (char*)Z_Malloc(strlen(com_token) + 1);
571 	    strcpy(arg, com_token);
572 	    cmd_argv[cmd_argc] = arg;
573 	    cmd_argc++;
574 	}
575     }
576 }
577 
578 static struct cmd_function_s *
Cmd_FindCommand(const char * name)579 Cmd_FindCommand(const char *name)
580 {
581     struct cmd_function_s *ret = NULL;
582     struct stree_node *n;
583 
584     n = STree_Find(&cmd_tree, name);
585     if (n)
586 	ret = cmd_entry(n);
587 
588     return ret;
589 }
590 
591 /*
592 ============
593 Cmd_AddCommand
594 ============
595 */
596 void
Cmd_AddCommand(const char * cmd_name,xcommand_t function)597 Cmd_AddCommand(const char *cmd_name, xcommand_t function)
598 {
599     cmd_function_t *cmd;
600 
601     if (host_initialized)	// because hunk allocation would get stomped
602 	Sys_Error("%s: called after host_initialized", __func__);
603 
604 // fail if the command is a variable name
605     if (Cvar_VariableString(cmd_name)[0]) {
606 	Con_Printf("%s: %s already defined as a var\n", __func__, cmd_name);
607 	return;
608     }
609 // fail if the command already exists
610     cmd = Cmd_FindCommand(cmd_name);
611     if (cmd) {
612 	Con_Printf("%s: %s already defined\n", __func__, cmd_name);
613 	return;
614     }
615 
616     cmd = (cmd_function_t*)Hunk_Alloc(sizeof(cmd_function_t));
617     cmd->name = cmd_name;
618     cmd->function = function;
619     cmd->completion = NULL;
620     cmd->stree.string = cmd->name;
621     STree_Insert(&cmd_tree, &cmd->stree);
622 }
623 
624 void
Cmd_SetCompletion(const char * cmd_name,cmd_arg_f completion)625 Cmd_SetCompletion(const char *cmd_name, cmd_arg_f completion)
626 {
627     cmd_function_t *cmd;
628 
629     cmd = Cmd_FindCommand(cmd_name);
630     if (cmd)
631 	cmd->completion = completion;
632     else
633 	Sys_Error("%s: no such command - %s", __func__, cmd_name);
634 }
635 
636 /*
637 ============
638 Cmd_Exists
639 ============
640 */
641 qboolean
Cmd_Exists(const char * cmd_name)642 Cmd_Exists(const char *cmd_name)
643 {
644     return Cmd_FindCommand(cmd_name) != NULL;
645 }
646 
647 qboolean
Cmd_Alias_Exists(const char * cmd_name)648 Cmd_Alias_Exists(const char *cmd_name)
649 {
650     return Cmd_Alias_Find(cmd_name) != NULL;
651 }
652 
653 
654 #ifdef NQ_HACK
655 /*
656 ===================
657 Cmd_ForwardToServer
658 
659 Sends the entire command line over to the server
660 ===================
661 */
662 void
Cmd_ForwardToServer(void)663 Cmd_ForwardToServer(void)
664 {
665     if (cls.state < ca_connected) {
666 	Con_Printf("Can't \"%s\", not connected\n", Cmd_Argv(0));
667 	return;
668     }
669 
670     if (cls.demoplayback)
671 	return;			// not really connected
672 
673     MSG_WriteByte(&cls.message, clc_stringcmd);
674     if (strcasecmp(Cmd_Argv(0), "cmd") != 0) {
675 	SZ_Print(&cls.message, Cmd_Argv(0));
676 	SZ_Print(&cls.message, " ");
677     }
678     if (Cmd_Argc() > 1)
679 	SZ_Print(&cls.message, Cmd_Args());
680     else
681 	SZ_Print(&cls.message, "\n");
682 }
683 #endif
684 #ifdef QW_HACK
685 #ifndef SERVERONLY
686 /*
687 ===================
688 Cmd_ForwardToServer
689 
690 adds the current command line as a clc_stringcmd to the client message.
691 things like godmode, noclip, etc, are commands directed to the server,
692 so when they are typed in at the console, they will need to be forwarded.
693 ===================
694 */
695 void
Cmd_ForwardToServer(void)696 Cmd_ForwardToServer(void)
697 {
698     if (cls.state == ca_disconnected) {
699 	Con_Printf("Can't \"%s\", not connected\n", Cmd_Argv(0));
700 	return;
701     }
702 
703     if (cls.demoplayback)
704 	return;			// not really connected
705 
706     MSG_WriteByte(&cls.netchan.message, clc_stringcmd);
707     SZ_Print(&cls.netchan.message, Cmd_Argv(0));
708     if (Cmd_Argc() > 1) {
709 	SZ_Print(&cls.netchan.message, " ");
710 	SZ_Print(&cls.netchan.message, Cmd_Args());
711     }
712 }
713 
714 // don't forward the first argument
715 static void
Cmd_ForwardToServer_f(void)716 Cmd_ForwardToServer_f(void)
717 {
718     if (cls.state == ca_disconnected) {
719 	Con_Printf("Can't \"%s\", not connected\n", Cmd_Argv(0));
720 	return;
721     }
722 
723     if (strcasecmp(Cmd_Argv(1), "snap") == 0) {
724 	Cbuf_InsertText("snap\n");
725 	return;
726     }
727 
728     if (cls.demoplayback)
729 	return;			// not really connected
730 
731     if (Cmd_Argc() > 1) {
732 	MSG_WriteByte(&cls.netchan.message, clc_stringcmd);
733 	SZ_Print(&cls.netchan.message, Cmd_Args());
734     }
735 }
736 #else
737 void
Cmd_ForwardToServer(void)738 Cmd_ForwardToServer(void)
739 {
740 }
741 #endif /* SERVERONLY */
742 #endif /* QW_HACK */
743 
744 /*
745 ============
746 Cmd_ExecuteString
747 
748 A complete command line has been parsed, so try to execute it
749 FIXME: lookupnoadd the token to speed search?
750 ============
751 */
752 void
753 #ifdef NQ_HACK
Cmd_ExecuteString(const char * text,cmd_source_t src)754 Cmd_ExecuteString(const char *text, cmd_source_t src)
755 #endif
756 #ifdef QW_HACK
757 Cmd_ExecuteString(const char *text)
758 #endif
759 {
760     cmd_function_t *cmd;
761     cmdalias_t *a;
762 
763 #ifdef NQ_HACK
764     cmd_source = src;
765 #endif
766     Cmd_TokenizeString(text);
767 
768 // execute the command line
769     if (!Cmd_Argc())
770 	return;			// no tokens
771 
772 // check functions
773     cmd = Cmd_FindCommand(cmd_argv[0]);
774     if (cmd) {
775 	if (cmd->function)
776 	    cmd->function();
777 #ifdef QW_HACK
778 	else
779 	    Cmd_ForwardToServer();
780 #endif
781 	return;
782     }
783 
784 // check alias
785     a = Cmd_Alias_Find(cmd_argv[0]);
786     if (a) {
787 	Cbuf_InsertText(a->value);
788 	return;
789     }
790 
791 // check cvars
792     if (!Cvar_Command() && (cl_warncmd.value || developer.value))
793 	Con_Printf("Unknown command \"%s\"\n", Cmd_Argv(0));
794 }
795 
796 /*
797  * Return a string tree with all possible argument completions of the given
798  * buffer for the given command.
799  */
800 struct stree_root *
Cmd_ArgCompletions(const char * name,const char * buf)801 Cmd_ArgCompletions(const char *name, const char *buf)
802 {
803     cmd_function_t *cmd;
804     struct stree_root *root = NULL;
805 
806     cmd = Cmd_FindCommand(name);
807     if (cmd && cmd->completion)
808 	root = cmd->completion(buf);
809 
810     return root;
811 }
812 
813 /*
814  * Call the argument completion function for cmd "name".
815  * Returned result should be Z_Free'd after use.
816  */
817 const char *
Cmd_ArgComplete(const char * name,const char * buf)818 Cmd_ArgComplete(const char *name, const char *buf)
819 {
820     char *result = NULL;
821     struct stree_root *root;
822 
823     root = Cmd_ArgCompletions(name, buf);
824     if (root) {
825 	result = STree_MaxMatch(root, buf);
826 	Z_Free(root);
827     }
828 
829     return result;
830 }
831 
832 
833 /*
834 ================
835 Cmd_CheckParm
836 
837 Returns the position (1 to argc-1) in the command's argument list
838 where the given parameter apears, or 0 if not present
839 ================
840 */
841 int
Cmd_CheckParm(const char * parm)842 Cmd_CheckParm(const char *parm)
843 {
844     int i;
845 
846     if (!parm)
847 	Sys_Error("Cmd_CheckParm: NULL");
848 
849     for (i = 1; i < Cmd_Argc(); i++)
850 	if (!strcasecmp(parm, Cmd_Argv(i)))
851 	    return i;
852 
853     return 0;
854 }
855 
Cmd_CommandCompletions(const char * buf)856 struct stree_root *Cmd_CommandCompletions(const char *buf)
857 {
858     struct stree_root *root_tree;
859 
860     root_tree = (struct stree_root*)Z_Malloc(sizeof(struct stree_root));
861 
862     root_tree->entries = 0;
863     root_tree->maxlen = 0;
864     root_tree->minlen = -1;
865     //root_tree->root = {NULL};
866     root_tree->stack = NULL;
867 
868     STree_AllocInit();
869 
870     STree_Completions(root_tree, &cmd_tree, buf);
871     STree_Completions(root_tree, &cmdalias_tree, buf);
872     STree_Completions(root_tree, &cvar_tree, buf);
873 
874     return root_tree;
875 }
876 
Cmd_CommandComplete(const char * buf)877 const char *Cmd_CommandComplete(const char *buf)
878 {
879     struct stree_root *root;
880     char *ret;
881 
882     root = Cmd_CommandCompletions(buf);
883     ret = STree_MaxMatch(root, buf);
884     Z_Free(root);
885 
886     return ret;
887 }
888