1 /*
2 Copyright (C) 1997-2001 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 "qcommon.h"
23 #include "q_list.h"
24
25 #define Cmd_Malloc( size ) Z_TagMalloc( size, TAG_CMD )
26 #define Cmd_CopyString( string ) Z_TagCopyString( string, TAG_CMD )
27
28 cmdAPI_t cmd;
29
30 /*
31 =============================================================================
32
33 COMMAND BUFFER
34
35 =============================================================================
36 */
37
38 byte cmd_buffer_text[CMD_BUFFER_SIZE];
39 cmdbuf_t cmd_buffer;
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 ============
49 */
Cmd_Wait_f(void)50 static void Cmd_Wait_f( void ) {
51 if( Cmd_Argc() > 1 ) {
52 cmd_buffer.waitCount = atoi( Cmd_Argv( 1 ) );
53 } else {
54 cmd_buffer.waitCount = 1;
55 }
56 }
57
58 /*
59 ============
60 Cbuf_Init
61 ============
62 */
Cbuf_Init(void)63 void Cbuf_Init( void ) {
64 SZ_Init( &cmd_buffer.text, cmd_buffer_text, sizeof( cmd_buffer_text ) );
65 cmd_buffer.exec = Cmd_ExecuteString;
66 }
67
68 /*
69 ============
70 Cbuf_AddText
71
72 Adds command text at the end of the buffer
73 ============
74 */
Cbuf_AddTextEx(cmdbuf_t * buf,const char * text)75 void Cbuf_AddTextEx( cmdbuf_t *buf, const char *text ) {
76 int l;
77
78 l = strlen( text );
79
80 if( buf->text.cursize + l >= buf->text.maxsize ) {
81 Com_WPrintf( "Cbuf_AddText: overflow\n" );
82 return;
83 }
84 SZ_Write( &buf->text, text, l );
85 }
86
87
88 /*
89 ============
90 Cbuf_InsertText
91
92 Adds command text immediately after the current command.
93 Adds a \n to the text.
94 ============
95 */
Cbuf_InsertTextEx(cmdbuf_t * buf,const char * text)96 void Cbuf_InsertTextEx( cmdbuf_t *buf, const char *text ) {
97 char temp[CMD_BUFFER_SIZE];
98 int templen, l;
99
100 if( *text == 0 ) {
101 return;
102 }
103
104 // copy off any commands still remaining in the exec buffer
105 templen = buf->text.cursize;
106 if( templen ) {
107 memcpy( temp, buf->text.data, templen );
108 SZ_Clear( &buf->text );
109 }
110
111 // add the entire text of the file
112 l = strlen( text );
113 if( templen + l >= buf->text.maxsize ) {
114 Com_WPrintf( "Cbuf_InsertText: overflow\n" );
115 return;
116 }
117
118 SZ_Write( &buf->text, text, l );
119
120 // add '\n' to the text
121 if( text[l - 1] != '\n' ) {
122 buf->text.data[buf->text.cursize++] = '\n';
123 }
124
125 // add the copied off data
126 if( templen ) {
127 SZ_Write( &buf->text, temp, templen );
128 }
129 }
130
131 /*
132 ============
133 Cbuf_ExecuteText
134 ============
135 */
Cbuf_ExecuteText(cbufExecWhen_t exec_when,const char * text)136 void Cbuf_ExecuteText( cbufExecWhen_t exec_when, const char *text ) {
137 switch( exec_when ) {
138 case EXEC_NOW:
139 Cmd_ExecuteString( text );
140 break;
141 case EXEC_INSERT:
142 Cbuf_InsertText( text );
143 break;
144 case EXEC_APPEND:
145 Cbuf_AddText( text );
146 break;
147 default:
148 Com_Error( ERR_FATAL, "Cbuf_ExecuteText: bad exec_when" );
149 }
150 }
151
152 /*
153 ============
154 Cbuf_Execute
155 ============
156 */
Cbuf_ExecuteEx(cmdbuf_t * buf)157 void Cbuf_ExecuteEx( cmdbuf_t *buf ) {
158 int i, bytes;
159 char *text;
160 char line[MAX_STRING_CHARS];
161 int quotes;
162
163 while( buf->text.cursize ) {
164 if( buf->waitCount > 0 ) {
165 // skip out while text still remains in buffer, leaving it
166 // for next frame
167 buf->waitCount--;
168 return;
169 }
170
171 // find a \n or ; line break
172 text = ( char * )buf->text.data;
173
174 quotes = 0;
175 for( i = 0; i < buf->text.cursize; i++ ) {
176 if( text[i] == '"' )
177 quotes++;
178 if( !( quotes & 1 ) && text[i] == ';' )
179 break; // don't break if inside a quoted string
180 if( text[i] == '\n' )
181 break;
182 }
183
184 bytes = i;
185
186 // strip trailing CR
187 if( bytes && text[ bytes - 1 ] == '\r' ) {
188 bytes--;
189 }
190
191 // check for overflow
192 if( bytes > sizeof( line ) - 1 ) {
193 bytes = sizeof( line ) - 1;
194 }
195
196 memcpy( line, text, bytes );
197 line[bytes] = 0;
198
199 // delete the text from the command buffer and move remaining commands down
200 // this is necessary because commands (exec, alias) can insert data at the
201 // beginning of the text buffer
202 if( i == buf->text.cursize ) {
203 buf->text.cursize = 0;
204 } else {
205 i++;
206 buf->text.cursize -= i;
207 memmove( text, text + i, buf->text.cursize );
208 }
209
210 // execute the command line
211 buf->exec( line );
212
213 }
214
215 buf->aliasCount = 0; // don't allow infinite alias loops
216 }
217
218 /*
219 ==============================================================================
220
221 SCRIPT COMMANDS
222
223 ==============================================================================
224 */
225
226 typedef struct cmdalias_s {
227 listElem_t elem[2];
228
229 char *value;
230 char name[1];
231 } cmdalias_t;
232
233 #define ALIAS_HASH_SIZE 64
234
235 static list_t cmd_alias;
236 static list_t cmd_aliasHash[ALIAS_HASH_SIZE];
237
238 /*
239 ===============
240 Cmd_AliasFind
241 ===============
242 */
Cmd_AliasFind(const char * name)243 cmdalias_t *Cmd_AliasFind( const char *name ) {
244 uint32 hash;
245 cmdalias_t *alias;
246 listElem_t *elem;
247
248 hash = Com_HashString( name, ALIAS_HASH_SIZE );
249 for( elem = cmd_aliasHash[hash].first; elem; elem = elem->next ) {
250 alias = ( cmdalias_t * )( elem - 1 );
251 if( !strcmp( name, alias->name ) ) {
252 return alias;
253 }
254 }
255
256 return NULL;
257 }
258
Cmd_AliasCommand(const char * name)259 char *Cmd_AliasCommand( const char *name ) {
260 cmdalias_t *a;
261
262 a = Cmd_AliasFind( name );
263 if( !a ) {
264 return NULL;
265 }
266
267 return a->value;
268 }
269
Cmd_AliasSet(const char * name,const char * cmd)270 void Cmd_AliasSet( const char *name, const char *cmd ) {
271 cmdalias_t *a;
272 uint32 hash;
273
274 // if the alias already exists, reuse it
275 a = Cmd_AliasFind( name );
276 if( a ) {
277 Z_Free( a->value );
278 a->value = Cmd_CopyString( cmd );
279 return;
280 }
281
282 a = Cmd_Malloc( sizeof( cmdalias_t ) + strlen( name ) );
283 strcpy( a->name, name );
284 a->value = Cmd_CopyString( cmd );
285
286 List_Append( &cmd_alias, &a->elem[0] );
287
288 hash = Com_HashString( name, ALIAS_HASH_SIZE );
289 List_Append( &cmd_aliasHash[hash], &a->elem[1] );
290 }
291
292 /*
293 ===============
294 Cmd_Alias_f
295
296 Creates a new command that executes a command string (possibly ; seperated)
297 ===============
298 */
Cmd_Alias_f(void)299 void Cmd_Alias_f( void ) {
300 cmdalias_t *a;
301 listElem_t *elem;
302 char *s, *cmd;
303
304 if( Cmd_Argc() < 2 ) {
305 if( !cmd_alias.first ) {
306 Com_Printf( "No alias commands registered\n" );
307 return;
308 }
309 Com_Printf( "Current alias commands:\n" );
310 for( elem = cmd_alias.first ; elem; elem = elem->next ) {
311 a = ( cmdalias_t * )elem;
312 Com_Printf( "\"%s\" = \"%s\"\n", a->name, a->value );
313 }
314 return;
315 }
316
317 s = Cmd_Argv( 1 );
318 if( Cmd_Exists( s ) ) {
319 Com_Printf( "\"%s\" already defined as a command\n", s );
320 return;
321 }
322
323 if( Cvar_Exists( s ) ) {
324 Com_Printf( "\"%s\" already defined as a cvar\n", s );
325 return;
326 }
327
328 if( Cmd_Argc() < 3 ) {
329 a = Cmd_AliasFind( s );
330 if( a ) {
331 Com_Printf( "\"%s\" = \"%s\"\n", a->name, a->value );
332 } else {
333 Com_Printf( "\"%s\" is undefined\n", s );
334 }
335 return;
336 }
337
338 // copy the rest of the command line
339 cmd = Cmd_ArgsFrom( 2 );
340 Cmd_AliasSet( s, cmd );
341 }
342
Cmd_UnAlias_f(void)343 static void Cmd_UnAlias_f( void ) {
344 char *s;
345 cmdalias_t *a;
346 listElem_t *elem, *next;
347 uint32 hash;
348
349 if( Cmd_CheckParam( "-h", "--help" ) ) {
350 usage:
351 Com_Printf( "Usage: %s [-h] [-a] [alias_name]\n"
352 "-h|--help : display this message\n"
353 "-a|--all : delete everything\n"
354 "Either -a or alias_name should be given\n", Cmd_Argv( 0 ) );
355 return;
356 }
357
358 if( Cmd_CheckParam( "a", "all" ) ) {
359 for( elem = cmd_alias.first; elem; elem = next ) {
360 next = elem->next;
361 a = ( cmdalias_t * )elem;
362 Z_Free( a->value );
363 Z_Free( a );
364 }
365 for( hash = 0; hash < ALIAS_HASH_SIZE; hash++ ) {
366 List_Clear( &cmd_aliasHash[hash] );
367 }
368 List_Clear( &cmd_alias );
369 Com_Printf( "Removed all aliases\n" );
370 return;
371 }
372
373 if( Cmd_Argc() < 2 ) {
374 goto usage;
375 }
376 s = Cmd_Argv( 1 );
377 a = Cmd_AliasFind( s );
378 if( !a ) {
379 Com_Printf( "\"%s\" is undefined\n", s );
380 return;
381 }
382
383 Z_Free( a->value );
384
385 List_DeleteElem( &a->elem[0] );
386 List_DeleteElem( &a->elem[1] );
387
388 Z_Free( a );
389 }
390
391 /*
392 =============================================================================
393
394 MESSAGE TRIGGERS
395
396 =============================================================================
397 */
398
399 typedef struct cmd_trigger_s {
400 listElem_t elem;
401
402 trigChannel_t chan;
403 char *match;
404 char *command;
405 } cmd_trigger_t;
406
407 static list_t cmd_triggers;
408
409 static struct {
410 const char *name;
411 const char *desc;
412 trigChannel_t chan;
413 } triggers[] = {
414 { "client", "misc client events", TRIG_CLIENT_SYSTEM },
415 { "chat", "client chat messages", TRIG_CLIENT_CHAT },
416 { "print", "client print messages (default)", TRIG_CLIENT_PRINT },
417 { "cprint", "client centerprint messages", TRIG_CLIENT_CENTERPRINT },
418 { "server", "misc server events", TRIG_SERVER_SYSTEM },
419 { "bprint", "game DLL broadcast messages", TRIG_SERVER_BPRINT },
420 { "dprint", "game DLL console messages", TRIG_SERVER_DPRINT },
421 { NULL, 0 }
422 };
423
chan2string(trigChannel_t chan)424 static const char *chan2string( trigChannel_t chan ) {
425 int i;
426
427 for( i = 0; triggers[i].name; i++ ) {
428 if( triggers[i].chan == chan ) {
429 return triggers[i].name;
430 }
431 }
432 return "unknown";
433 }
434
435 /*
436 ============
437 Cmd_Trigger_f
438 ============
439 */
Cmd_Trigger_f(void)440 static void Cmd_Trigger_f( void ) {
441 cmd_trigger_t *trigger;
442 listElem_t *elem;
443 char *command, *match, *name;
444 int cmdLength, matchLength;
445 trigChannel_t chan;
446 int i;
447
448 if( Cmd_Argc() == 1 ) {
449 if( !cmd_triggers.first ) {
450 Com_Printf( "No triggers registered\n" );
451 return;
452 }
453 Com_Printf( "Current message triggers:\n" );
454 for( elem = cmd_triggers.first; elem; elem = elem->next ) {
455 trigger = ( cmd_trigger_t * )elem;
456 Com_Printf( "\"%s\" = \"%s\" [%s]\n",
457 trigger->match, trigger->command,
458 chan2string( trigger->chan ) );
459 }
460 return;
461 }
462
463 if( Cmd_Argc() < 3 ) {
464 usage:
465 Com_Printf( "Usage: %s <command> <match> [channel]\n", Cmd_Argv( 0 ) );
466 Com_Printf( "Valid channels:\n" );
467 for( i = 0; triggers[i].name; i++ ) {
468 Com_Printf( "%6s - %s\n", triggers[i].name, triggers[i].desc );
469 }
470 return;
471 }
472
473 command = Cmd_Argv( 1 );
474 match = Cmd_Argv( 2 );
475
476 chan = TRIG_CLIENT_PRINT;
477 if( Cmd_Argc() > 3 ) {
478 name = Cmd_Argv( 3 );
479 for( i = 0; triggers[i].name; i++ ) {
480 if( !strcmp( name, triggers[i].name ) ) {
481 chan = triggers[i].chan;
482 break;
483 }
484 }
485 if( !triggers[i].name ) {
486 Com_Printf( "Unknown channel '%s'\n", name );
487 goto usage;
488 }
489 }
490
491 // don't create the same trigger twice
492 for( elem = cmd_triggers.first; elem; elem = elem->next ) {
493 trigger = ( cmd_trigger_t * )elem;
494 if( trigger->chan == chan &&
495 !strcmp( trigger->command, command ) &&
496 !strcmp( trigger->match, match ) )
497 {
498 return;
499 }
500 }
501
502 cmdLength = strlen( command ) + 1;
503 matchLength = strlen( match ) + 1;
504
505 trigger = Cmd_Malloc( sizeof( cmd_trigger_t ) +
506 cmdLength + matchLength );
507 trigger->chan = chan;
508 trigger->command = ( char * )( trigger + 1 );
509 trigger->match = trigger->command + cmdLength;
510 List_Append( &cmd_triggers, trigger );
511
512 strcpy( trigger->command, command );
513 strcpy( trigger->match, match );
514 }
515
516 /*
517 ============
518 Cmd_UnTrigger_f
519 ============
520 */
Cmd_UnTrigger_f(void)521 static void Cmd_UnTrigger_f( void ) {
522 cmd_trigger_t *trigger;
523 listElem_t *elem, *next;
524 char *command, *match;
525
526 if( Cmd_CheckParam( "-h", "--help" ) ) {
527 usage:
528 Com_Printf( "Usage: %s [-h] [-a] [-c <command>] [-m <match>]\n"
529 "-h|--help : display this message\n"
530 "-a|--all : delete everything\n"
531 "-c|--command : delete by command\n"
532 "-m|--match : delete by match\n"
533 "Either -a or combination of -c and -m should be given\n",
534 Cmd_Argv( 0 ) );
535 return;
536 }
537
538 if( Cmd_CheckParam( "a", "all" ) ) {
539 for( elem = cmd_triggers.first; elem; elem = next ) {
540 next = elem->next;
541 Z_Free( elem );
542 }
543 List_Clear( &cmd_triggers );
544 Com_Printf( "Removed all triggers\n" );
545 return;
546 }
547
548 command = Cmd_FindParam( "c", "command" );
549 match = Cmd_FindParam( "m", "match" );
550 if( !command && !match ) {
551 goto usage;
552 }
553
554 for( elem = cmd_triggers.first; elem; elem = next ) {
555 next = elem->next;
556 trigger = ( cmd_trigger_t * )elem;
557 if( command && strcmp( trigger->command, command ) ) {
558 continue;
559 }
560 if( match && strcmp( trigger->match, match ) ) {
561 continue;
562 }
563
564 Com_Printf( "Removed \"%s\" = \"%s\" [%s]\n",
565 trigger->match, trigger->command,
566 chan2string( trigger->chan ) );
567
568 List_DeleteElem( elem );
569 Z_Free( elem );
570 }
571 }
572
573 /*
574 ============
575 Cmd_ExecTrigger
576 ============
577 */
Cmd_ExecTrigger(trigChannel_t chan,const char * string)578 void Cmd_ExecTrigger( trigChannel_t chan, const char *string ) {
579 cmd_trigger_t *trigger;
580 listElem_t *elem;
581 char *text;
582
583 // execute matching triggers
584 for( elem = cmd_triggers.first; elem; elem = elem->next ) {
585 trigger = ( cmd_trigger_t * )elem;
586 if( trigger->chan != chan ) {
587 continue;
588 }
589 text = Cmd_MacroExpandString( trigger->match, qfalse );
590 if( text && Com_WildCmp( text, string, qtrue ) ) {
591 Cbuf_AddText( trigger->command );
592 Cbuf_AddText( "\n" );
593 }
594 }
595 }
596
597 /*
598 =============================================================================
599
600 MACRO EXECUTION
601
602 =============================================================================
603 */
604
605 typedef struct cmd_macro_s {
606 struct cmd_macro_s *next;
607 struct cmd_macro_s *hashNext;
608
609 const char *name;
610 xmacro_t function;
611 } cmd_macro_t;
612
613 #define MACRO_HASH_SIZE 64
614
615 static cmd_macro_t *cmd_macros;
616 static cmd_macro_t *cmd_macroHash[MACRO_HASH_SIZE];
617
618 /*
619 ============
620 Cmd_MacroFind
621 ============
622 */
Cmd_MacroFind(const char * name)623 static cmd_macro_t *Cmd_MacroFind( const char *name ) {
624 cmd_macro_t *macro;
625 int hash;
626
627 hash = Com_HashString( name, MACRO_HASH_SIZE );
628 for( macro=cmd_macroHash[hash] ; macro ; macro=macro->hashNext ) {
629 if( !strcmp( macro->name, name ) ) {
630 return macro;
631 }
632 }
633
634 return NULL;
635 }
636
Cmd_FindMacroFunction(const char * name)637 xmacro_t Cmd_FindMacroFunction( const char *name ) {
638 cmd_macro_t *macro;
639
640 macro = Cmd_MacroFind( name );
641 if( !macro ) {
642 return NULL;
643 }
644
645 return macro->function;
646 }
647
648 /*
649 ============
650 Cmd_AddMacro
651 ============
652 */
Cmd_AddMacro(const char * name,xmacro_t function)653 void Cmd_AddMacro( const char *name, xmacro_t function ) {
654 cmd_macro_t *macro;
655 int hash;
656
657 if( Cvar_Exists( name ) ) {
658 Com_WPrintf( "Cmd_AddMacro: %s already defined as a cvar\n", name );
659 return;
660 }
661
662 // fail if the macro already exists
663 if( Cmd_MacroFind( name ) ) {
664 Com_WPrintf( "Cmd_AddMacro: %s already defined\n", name );
665 return;
666 }
667
668 hash = Com_HashString( name, MACRO_HASH_SIZE );
669
670 macro = Cmd_Malloc( sizeof( cmd_macro_t ) );
671 macro->name = name;
672 macro->function = function;
673 macro->next = cmd_macros;
674 cmd_macros = macro;
675 macro->hashNext = cmd_macroHash[hash];
676 cmd_macroHash[hash] = macro;
677 }
678
679
680
681
682 /*
683 =============================================================================
684
685 COMMAND EXECUTION
686
687 =============================================================================
688 */
689
690 #define CMD_HASH_SIZE 128
691
692 typedef struct cmd_function_s {
693 listElem_t elem[2];
694
695 xcommand_t function;
696 xgenerator_t generator;
697 const char *name;
698 } cmd_function_t;
699
700 static list_t cmd_functions; /* possible commands to execute */
701 static list_t cmd_hash[CMD_HASH_SIZE];
702
703 static int cmd_argc;
704 static char *cmd_argv[MAX_STRING_TOKENS];
705 static char *cmd_null_string = "";
706
707 /* complete command string, quotes preserved */
708 static char cmd_args[MAX_STRING_CHARS];
709
710 /* offsets of individual tokens in cmd_args */
711 static int cmd_offsets[MAX_STRING_TOKENS];
712
713 /* sequence of NULL-terminated tokens, each cmd_argv[] points here */
714 static char cmd_data[MAX_STRING_CHARS];
715
Cmd_ArgOffset(int arg)716 int Cmd_ArgOffset( int arg ) {
717 if( arg < 0 ) {
718 return 0;
719 }
720 if( arg >= cmd_argc ) {
721 return strlen( cmd_args );
722 }
723 return cmd_offsets[arg];
724 }
725
Cmd_FindArgForOffset(int offset)726 int Cmd_FindArgForOffset( int offset ) {
727 int i;
728
729 for( i = 1; i < cmd_argc; i++ ) {
730 if( offset < cmd_offsets[i] ) {
731 return i - 1;
732 }
733 }
734 return i - 1;
735 }
736
737 /*
738 ============
739 Cmd_Argc
740 ============
741 */
Cmd_Argc(void)742 int Cmd_Argc( void ) {
743 return cmd_argc;
744 }
745
746 /*
747 ============
748 Cmd_Argv
749 ============
750 */
Cmd_Argv(int arg)751 char *Cmd_Argv( int arg ) {
752 if( arg < 0 || arg >= cmd_argc ) {
753 return cmd_null_string;
754 }
755 return cmd_argv[arg];
756 }
757
758 /*
759 ============
760 Cmd_ArgvBuffer
761 ============
762 */
Cmd_ArgvBuffer(int arg,char * buffer,int bufferSize)763 void Cmd_ArgvBuffer( int arg, char *buffer, int bufferSize ) {
764 char *s;
765
766 if( arg < 0 || arg >= cmd_argc ) {
767 s = cmd_null_string;
768 } else {
769 s = cmd_argv[arg];
770 }
771
772 Q_strncpyz( buffer, s, bufferSize );
773 }
774
775
776 /*
777 ============
778 Cmd_Args
779
780 Returns a single string containing argv(1) to argv(argc()-1)
781 ============
782 */
Cmd_Args(void)783 char *Cmd_Args( void ) {
784 static char args[MAX_STRING_CHARS];
785 int i;
786
787 if( cmd_argc < 2 ) {
788 return cmd_null_string;
789 }
790
791 args[0] = 0;
792 for( i = 1; i < cmd_argc - 1; i++ ) {
793 strcat( args, cmd_argv[i] );
794 strcat( args, " " );
795 }
796 strcat( args, cmd_argv[i] );
797
798 return args;
799 }
800
Cmd_RawArgs(void)801 char *Cmd_RawArgs( void ) {
802 if( cmd_argc < 2 ) {
803 return cmd_null_string;
804 }
805 return cmd_args + cmd_offsets[1];
806 }
807
Cmd_RawString(void)808 char *Cmd_RawString( void ) {
809 return cmd_args;
810 }
811
812
813
814 /*
815 ============
816 Cmd_ArgsBuffer
817 ============
818 */
Cmd_ArgsBuffer(char * buffer,int bufferSize)819 void Cmd_ArgsBuffer( char *buffer, int bufferSize ) {
820 Q_strncpyz( buffer, Cmd_Args(), bufferSize );
821 }
822
823 /*
824 ============
825 Cmd_ArgsFrom
826
827 Returns a single string containing argv(1) to argv(from-1)
828 ============
829 */
Cmd_ArgsFrom(int from)830 char *Cmd_ArgsFrom( int from ) {
831 static char args[MAX_STRING_CHARS];
832 int i;
833
834 if( from < 0 || from >= cmd_argc ) {
835 return cmd_null_string;
836 }
837
838 args[0] = 0;
839 for( i = from; i < cmd_argc - 1; i++ ) {
840 strcat( args, cmd_argv[i] );
841 strcat( args, " " );
842 }
843 strcat( args, cmd_argv[i] );
844
845 return args;
846 }
847
Cmd_RawArgsFrom(int from)848 char *Cmd_RawArgsFrom( int from ) {
849 int offset;
850
851 if( from < 0 || from >= cmd_argc ) {
852 return cmd_null_string;
853 }
854
855 offset = cmd_offsets[from];
856
857 return cmd_args + offset;
858 }
859
860 /*
861 ============
862 Cmd_EnumParam
863 ============
864 */
Cmd_EnumParam(int start,const char * sp,const char * lp)865 int Cmd_EnumParam( int start, const char *sp, const char *lp ) {
866 int i;
867 char *s;
868
869 if( start < 0 || start >= cmd_argc ) {
870 return 0;
871 }
872 for( i = start; i < cmd_argc; i++ ) {
873 if( *( s = cmd_argv[i] ) == '-' ) {
874 if( *( ++s ) == '-' ) {
875 if( !strcmp( s + 1, lp ) ) {
876 return i;
877 }
878 } else if( !strcmp( s, sp ) ) {
879 return i;
880 }
881 }
882 }
883
884 return 0;
885 }
886
887 /*
888 ============
889 Cmd_CheckParam
890 ============
891 */
Cmd_CheckParam(const char * sp,const char * lp)892 int Cmd_CheckParam( const char *sp, const char *lp ) {
893 return Cmd_EnumParam( 1, sp, lp );
894 }
895
896 /*
897 ============
898 Cmd_FindParam
899 ============
900 */
Cmd_FindParam(const char * sp,const char * lp)901 char *Cmd_FindParam( const char *sp, const char *lp ) {
902 int i;
903
904 if( ( i = Cmd_EnumParam( 1, sp, lp ) ) && ++i != cmd_argc ) {
905 return cmd_argv[i];
906 }
907
908 return NULL;
909 }
910
911 /*
912 ======================
913 Cmd_MacroExpandString
914 ======================
915 */
Cmd_MacroExpandString(const char * text,qboolean aliasHack)916 char *Cmd_MacroExpandString( const char *text, qboolean aliasHack ) {
917 int i, j, count, len;
918 qboolean inquote;
919 char *scan, *start;
920 static char expanded[MAX_STRING_CHARS];
921 char temporary[MAX_STRING_CHARS];
922 char buffer[MAX_TOKEN_CHARS];
923 char *token;
924 cmd_macro_t *macro;
925 cvar_t *var;
926 qboolean rescan;
927
928 len = strlen( text );
929 if( len >= MAX_STRING_CHARS ) {
930 Com_Printf( "Line exceeded %i chars, discarded.\n", MAX_STRING_CHARS );
931 return NULL;
932 }
933
934 strcpy( expanded, text );
935 scan = expanded;
936
937 inquote = qfalse;
938 count = 0;
939
940 for( i = 0; i < len; i++ ) {
941 if( !scan[i] ) {
942 break;
943 }
944 if( scan[i] == '"' ) {
945 inquote ^= 1;
946 }
947 if( inquote ) {
948 continue; /* don't expand inside quotes */
949 }
950 if( scan[i] != '$' ) {
951 continue;
952 }
953
954 /* scan out the complete macro */
955 start = scan + i + 1;
956
957 if( !start[0] ) {
958 break; /* end of string */
959 }
960
961 /* allow escape syntax */
962 if( i && scan[i-1] == '\\' ) {
963 memmove( scan + i - 1, scan + i, len - i + 1 );
964 i--;
965 continue;
966 }
967
968 /* fix from jitspoe - skip leading spaces */
969 while( *start && *start <= 32 ) {
970 start++;
971 }
972
973 token = temporary;
974
975 if( *start == '{' ) {
976 /* allow ${variable} syntax */
977 start++;
978 if( *start == '$' ) {
979 start++;
980 }
981 while( *start ) {
982 if( *start == '}' ) {
983 start++;
984 break;
985 }
986 *token++ = *start++;
987 }
988 } else {
989 /* parse single word */
990 while( *start > 32 ) {
991 *token++ = *start++;
992 }
993 }
994
995 *token = 0;
996
997 if( token == temporary ) {
998 continue;
999 }
1000
1001 rescan = qfalse;
1002
1003 if( aliasHack ) {
1004 /* expand positional parameters only */
1005 if( temporary[1] ) {
1006 continue;
1007 }
1008 if( Q_isdigit( temporary[0] ) ) {
1009 token = Cmd_Argv( temporary[0] - '0' );
1010 } else if( temporary[0] == '@' ) {
1011 token = Cmd_Args();
1012 } else {
1013 continue;
1014 }
1015 } else {
1016 /* check for macros first */
1017 macro = Cmd_MacroFind( temporary );
1018 if( macro ) {
1019 macro->function( buffer, sizeof( buffer ) );
1020 token = buffer;
1021 } else {
1022 var = Cvar_FindVar( temporary );
1023 if( var && !( var->flags & CVAR_PRIVATE ) ) {
1024 token = var->string;
1025 rescan = qtrue;
1026 } else if( !strcmp( temporary, "qt" ) ) {
1027 token = "\"";
1028 } else if( !strcmp( temporary, "sc" ) ) {
1029 token = ";";
1030 } else {
1031 token = "";
1032 }
1033 }
1034 }
1035
1036 j = strlen( token );
1037 len += j;
1038 if( len >= MAX_STRING_CHARS ) {
1039 Com_Printf( "Expanded line exceeded %i chars, discarded.\n",
1040 MAX_STRING_CHARS );
1041 return NULL;
1042 }
1043
1044 strncpy( temporary, scan, i );
1045 strcpy( temporary + i, token );
1046 strcpy( temporary + i + j, start );
1047
1048 strcpy( expanded, temporary );
1049 scan = expanded;
1050 if( !rescan ) {
1051 i += j;
1052 }
1053 i--;
1054
1055 if( ++count == 100 ) {
1056 Com_Printf( "Macro expansion loop, discarded.\n" );
1057 return NULL;
1058 }
1059 }
1060
1061 if( inquote ) {
1062 Com_Printf( "Line has unmatched quote, discarded.\n" );
1063 return NULL;
1064 }
1065
1066 return scan;
1067 }
1068
1069 /*
1070 ============
1071 Cmd_TokenizeString
1072
1073 Parses the given string into command line tokens.
1074 $Cvars will be expanded unless they are in a quoted token
1075 ============
1076 */
Cmd_TokenizeString(const char * text,qboolean macroExpand)1077 void Cmd_TokenizeString( const char *text, qboolean macroExpand ) {
1078 int i;
1079 char *data, *start, *dest;
1080
1081 // clear the args from the last string
1082 for( i = 0; i < cmd_argc; i++ ) {
1083 cmd_argv[i] = NULL;
1084 cmd_offsets[i] = 0;
1085 }
1086
1087 cmd_argc = 0;
1088 cmd_args[0] = 0;
1089
1090 if( !text[0] ) {
1091 return;
1092 }
1093
1094 // macro expand the text
1095 if( macroExpand ) {
1096 text = Cmd_MacroExpandString( text, qfalse );
1097 if( !text ) {
1098 return;
1099 }
1100 }
1101
1102 Q_strncpyz( cmd_args, text, sizeof( cmd_args ) );
1103
1104 dest = cmd_data;
1105 start = data = cmd_args;
1106 do {
1107 // skip whitespace up to a /n
1108 while( *data <= 32 ) {
1109 if( *data == 0 ) {
1110 return; // end of text
1111 }
1112 if( *data == '\n' ) {
1113 return; // a newline seperates commands in the buffer
1114 }
1115 data++;
1116 }
1117
1118 // skip C++ style comments
1119 if( data[0] == '/' && data[1] == '/' ) {
1120 return; // skipped to newline
1121 }
1122
1123 // add new argument
1124 cmd_offsets[cmd_argc] = data - start;
1125 cmd_argv[cmd_argc] = dest;
1126 cmd_argc++;
1127
1128 if( *data == ';' ) {
1129 data++;
1130 *dest++ = ';';
1131 *dest++ = 0;
1132 continue;
1133 }
1134
1135 // parse quoted string
1136 if( *data == '\"' ) {
1137 data++;
1138 while( *data != '\"' ) {
1139 if( *data == 0 ) {
1140 return; // end of data
1141 }
1142 *dest++ = *data++;
1143 }
1144 data++;
1145 *dest++ = 0;
1146 continue;
1147 }
1148
1149 // parse reqular token
1150 while( *data > 32 ) {
1151 if( *data == '\"' ) {
1152 break;
1153 }
1154 if( *data == ';' ) {
1155 break;
1156 }
1157 if( data[0] == '/' && data[1] == '/' ) {
1158 break; // skipped to newline
1159 }
1160 *dest++ = *data++;
1161 }
1162 *dest++ = 0;
1163
1164 if( *data == 0 ) {
1165 return; // end of text
1166 }
1167 } while( cmd_argc != MAX_STRING_TOKENS );
1168
1169 }
1170
1171 /*
1172 ============
1173 Cmd_Find
1174 ============
1175 */
Cmd_Find(const char * name)1176 cmd_function_t *Cmd_Find( const char *name ) {
1177 cmd_function_t *cmd;
1178 listElem_t *elem;
1179 uint32 hash;
1180
1181 hash = Com_HashString( name, CMD_HASH_SIZE );
1182 for( elem = cmd_hash[hash].first; elem; elem = elem->next ) {
1183 cmd = ( cmd_function_t * )( elem - 1 );
1184 if( !strcmp( cmd->name, name ) ) {
1185 return cmd;
1186 }
1187 }
1188
1189 return NULL;
1190 }
1191
1192 /*
1193 ============
1194 Cmd_AddCommand
1195 ============
1196 */
Cmd_AddCommandEx(const char * cmd_name,xcommand_t function,xgenerator_t generator)1197 void Cmd_AddCommandEx( const char *cmd_name, xcommand_t function,
1198 xgenerator_t generator )
1199 {
1200 cmd_function_t *cmd;
1201 uint32 hash;
1202
1203 // fail if the command is a variable name
1204 if( Cvar_FindVar( cmd_name ) ) {
1205 Com_WPrintf( "Cmd_AddCommand: %s already defined as a cvar\n",
1206 cmd_name );
1207 return;
1208 }
1209
1210 // fail if the command already exists
1211 cmd = Cmd_Find( cmd_name );
1212 if( cmd ) {
1213 Com_WPrintf( "Cmd_AddCommand: %s already defined\n", cmd_name );
1214 return;
1215 }
1216
1217 cmd = Cmd_Malloc( sizeof( *cmd ) );
1218 cmd->name = cmd_name;
1219 cmd->function = function;
1220 cmd->generator = generator;
1221
1222 List_Append( &cmd_functions, &cmd->elem[0] );
1223
1224 hash = Com_HashString( cmd_name, CMD_HASH_SIZE );
1225 List_Append( &cmd_hash[hash], &cmd->elem[1] );
1226
1227 }
1228
Cmd_AddCommand(const char * cmd_name,xcommand_t function)1229 void Cmd_AddCommand( const char *cmd_name, xcommand_t function ) {
1230 Cmd_AddCommandEx( cmd_name, function, NULL );
1231 }
1232
1233 /*
1234 ============
1235 Cmd_RemoveCommand
1236
1237 Can't just free the command since lua still may have a pointer to it.
1238 ============
1239 */
Cmd_RemoveCommand(const char * name)1240 void Cmd_RemoveCommand( const char *name ) {
1241 cmd_function_t *cmd;
1242
1243 cmd = Cmd_Find( name );
1244 if( !cmd ) {
1245 Com_WPrintf( "Cmd_RemoveCommand: %s not added\n", name );
1246 return;
1247 }
1248
1249 List_DeleteElem( &cmd->elem[0] );
1250 List_DeleteElem( &cmd->elem[1] );
1251 Z_Free( cmd );
1252
1253 }
1254
1255 /*
1256 ============
1257 Cmd_Exists
1258 ============
1259 */
Cmd_Exists(const char * name)1260 qboolean Cmd_Exists( const char *name ) {
1261 cmd_function_t *cmd;
1262
1263 cmd = Cmd_Find( name );
1264 if( !cmd ) {
1265 return qfalse;
1266 }
1267
1268 return qtrue;
1269 }
1270
Cmd_FindFunction(const char * name)1271 xcommand_t Cmd_FindFunction( const char *name ) {
1272 cmd_function_t *cmd;
1273
1274 cmd = Cmd_Find( name );
1275 if( !cmd ) {
1276 return NULL;
1277 }
1278
1279 return cmd->function;
1280 }
1281
Cmd_FindGenerator(const char * name)1282 xgenerator_t Cmd_FindGenerator( const char *name ) {
1283 cmd_function_t *cmd;
1284
1285 cmd = Cmd_Find( name );
1286 if( !cmd ) {
1287 return NULL;
1288 }
1289
1290 return cmd->generator;
1291 }
1292
Cmd_CommandGenerator(const char * partial,int state)1293 const char *Cmd_CommandGenerator( const char *partial, int state ) {
1294 static int length;
1295 static cmd_function_t *cmd;
1296 const char *name;
1297
1298 if( !state ) {
1299 length = strlen( partial );
1300 cmd = ( cmd_function_t * )cmd_functions.first;
1301 }
1302
1303 while( cmd ) {
1304 name = cmd->name;
1305 cmd = ( cmd_function_t * )cmd->elem[0].next;
1306 if( !strncmp( partial, name, length ) ) {
1307 return name;
1308 }
1309 }
1310
1311 return NULL;
1312 }
1313
Cmd_AliasGenerator(const char * partial,int state)1314 const char *Cmd_AliasGenerator( const char *partial, int state ) {
1315 static int length;
1316 static cmdalias_t *alias;
1317 const char *name;
1318
1319 if( !state ) {
1320 length = strlen( partial );
1321 alias = ( cmdalias_t * )cmd_alias.first;
1322 }
1323
1324 while( alias ) {
1325 name = alias->name;
1326 alias = ( cmdalias_t * )alias->elem[0].next;
1327 if( !strncmp( partial, name, length ) ) {
1328 return name;
1329 }
1330 }
1331
1332 return NULL;
1333 }
1334
1335 /*
1336 ============
1337 Cmd_ExecuteString
1338
1339 A complete command line has been parsed, so try to execute it
1340 ============
1341 */
Cmd_ExecuteString(const char * text)1342 void Cmd_ExecuteString( const char *text ) {
1343 cmd_function_t *cmd;
1344 cmdalias_t *a;
1345
1346 Cmd_TokenizeString( text, qtrue );
1347
1348 // execute the command line
1349 if( !cmd_argc ) {
1350 return; // no tokens
1351 }
1352
1353 // check functions
1354 cmd = Cmd_Find( cmd_argv[0] );
1355 if( cmd ) {
1356 if( cmd->function ) {
1357 cmd->function();
1358 } else {
1359 Cmd_ForwardToServer();
1360 }
1361 return;
1362 }
1363
1364 // check aliases
1365 a = Cmd_AliasFind( cmd_argv[0] );
1366 if( a ) {
1367 if( ++cmd_buffer.aliasCount == ALIAS_LOOP_COUNT ) {
1368 Com_WPrintf( "Runaway alias loop\n" );
1369 return;
1370 }
1371 text = Cmd_MacroExpandString( a->value, qtrue );
1372 if( text ) {
1373 Cbuf_InsertText( text );
1374 }
1375 return;
1376 }
1377
1378 // check cvars
1379 if( Cvar_Command() ) {
1380 return;
1381 }
1382
1383 // send it as a server command if we are connected
1384 Cmd_ForwardToServer();
1385 }
1386
1387 /*
1388 ===============
1389 Cmd_Exec_f
1390 ===============
1391 */
Cmd_Exec_f(void)1392 static void Cmd_Exec_f( void ) {
1393 char buffer[MAX_QPATH];
1394 char *f, *ext;
1395 int len;
1396
1397 if( Cmd_Argc () != 2 ) {
1398 Com_Printf( "exec <filename> : execute a script file\n" );
1399 return;
1400 }
1401
1402 Q_strncpyz( buffer, Cmd_Argv( 1 ), sizeof( buffer ) );
1403
1404 len = FS_LoadFile( buffer, ( void ** )&f );
1405 if( !f ) {
1406 ext = COM_FileExtension( buffer );
1407 if( *ext == 0 ) {
1408 /* try with *.cfg extension */
1409 COM_DefaultExtension( buffer, ".cfg", sizeof( buffer ) );
1410 len = FS_LoadFile( buffer, ( void ** )&f );
1411 }
1412
1413 if( !f ) {
1414 Com_Printf( "Couldn't exec %s\n", buffer );
1415 return;
1416 }
1417 }
1418
1419 Com_Printf( "Execing %s\n", buffer );
1420
1421 Cbuf_InsertText( f );
1422
1423 FS_FreeFile( f );
1424 }
1425
Cmd_Exec_g(const char * partial,int state)1426 static const char *Cmd_Exec_g( const char *partial, int state ) {
1427 return Com_FileNameGeneratorByFilter( "", "*.cfg", partial, qtrue, state );
1428 }
1429
1430 /*
1431 ===============
1432 Cmd_Echo_f
1433
1434 Just prints the rest of the line to the console
1435 ===============
1436 */
Cmd_Echo_f(void)1437 static void Cmd_Echo_f( void ) {
1438 Com_Printf( "%s\n", Cmd_RawArgs() );
1439 }
1440
Cmd_ColoredEcho_f(void)1441 static void Cmd_ColoredEcho_f( void ) {
1442 char buffer[MAX_STRING_CHARS];
1443 char *src, *dst;
1444
1445 src = Cmd_RawArgs();
1446 dst = buffer;
1447 while( *src ) {
1448 if( src[0] == '^' && src[1] ) {
1449 if( src[1] == '^' ) {
1450 *dst++ = '^';
1451 } else {
1452 dst[0] = Q_COLOR_ESCAPE;
1453 dst[1] = src[1];
1454 dst += 2;
1455 }
1456 src += 2;
1457 } else {
1458 *dst++ = *src++;
1459 }
1460 }
1461 *dst = 0;
1462 Com_Printf( "%s\n", buffer );
1463 }
1464
1465 /*
1466 ============
1467 Cmd_List_f
1468 ============
1469 */
Cmd_List_f(void)1470 static void Cmd_List_f( void ) {
1471 cmd_function_t *cmd;
1472 listElem_t *elem;
1473 int i, total;
1474 char *filter = NULL;
1475
1476 if( cmd_argc > 1 ) {
1477 filter = cmd_argv[1];
1478 }
1479
1480 i = total = 0;
1481 for( elem = cmd_functions.first; elem; elem = elem->next ) {
1482 cmd = ( cmd_function_t * )elem;
1483 total++;
1484 if( filter && !Com_WildCmp( filter, cmd->name, qfalse ) ) {
1485 continue;
1486 }
1487 Com_Printf( "%s\n", cmd->name );
1488 i++;
1489 }
1490 Com_Printf( "%i of %i commands\n", i, total );
1491 }
1492
1493 /*
1494 ============
1495 Cmd_MacroList_f
1496 ============
1497 */
Cmd_MacroList_f(void)1498 static void Cmd_MacroList_f( void ) {
1499 cmd_macro_t *macro;
1500 int i, total;
1501 char *filter = NULL;
1502 char buffer[MAX_QPATH];
1503
1504 if( cmd_argc > 1 ) {
1505 filter = cmd_argv[1];
1506 }
1507
1508 i = 0;
1509 for( macro = cmd_macros, total = 0; macro; macro = macro->next, total++ ) {
1510 if( filter && !Com_WildCmp( filter, macro->name, qfalse ) ) {
1511 continue;
1512 }
1513 macro->function( buffer, sizeof( buffer ) );
1514 Com_Printf( "%-16s %s\n", macro->name, buffer );
1515 i++;
1516 }
1517 Com_Printf( "%i of %i macros\n", i, total );
1518 }
1519
1520 /*
1521 ============
1522 Cmd_FillAPI
1523 ============
1524 */
Cmd_FillAPI(cmdAPI_t * api)1525 void Cmd_FillAPI( cmdAPI_t *api ) {
1526 api->AddCommand = Cmd_AddCommand;
1527 api->AddCommandEx = Cmd_AddCommandEx;
1528 api->RemoveCommand = Cmd_RemoveCommand;
1529 api->Argc = Cmd_Argc;
1530 api->Argv = Cmd_Argv;
1531 api->ArgsFrom = Cmd_ArgsFrom;
1532 api->ExecuteText = Cbuf_ExecuteText;
1533 api->FindFunction = Cmd_FindFunction;
1534 api->FindMacroFunction = Cmd_FindMacroFunction;
1535 api->FindGenerator = Cmd_FindGenerator;
1536 }
1537
1538 /*
1539 ============
1540 Cmd_Init
1541 ============
1542 */
Cmd_Init(void)1543 void Cmd_Init( void ) {
1544 //
1545 // register our commands
1546 //
1547 Cmd_AddCommand( "cmdlist", Cmd_List_f );
1548 Cmd_AddCommand( "macrolist", Cmd_MacroList_f );
1549 Cmd_AddCommandEx( "exec", Cmd_Exec_f, Cmd_Exec_g );
1550 Cmd_AddCommand( "echo", Cmd_Echo_f );
1551 Cmd_AddCommand( "_echo", Cmd_ColoredEcho_f );
1552 Cmd_AddCommandEx( "alias", Cmd_Alias_f, Cmd_AliasGenerator );
1553 Cmd_AddCommandEx( "unalias", Cmd_UnAlias_f, Cmd_AliasGenerator );
1554 Cmd_AddCommand( "wait", Cmd_Wait_f );
1555 Cmd_AddCommand( "trigger", Cmd_Trigger_f );
1556 Cmd_AddCommand( "untrigger", Cmd_UnTrigger_f );
1557
1558 Cmd_FillAPI( &cmd );
1559 }
1560
1561