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