1 /*
2 ** c_dispatch.cpp
3 ** Functions for executing console commands and aliases
4 **
5 **---------------------------------------------------------------------------
6 ** Copyright 1998-2007 Randy Heit
7 ** All rights reserved.
8 **
9 ** Redistribution and use in source and binary forms, with or without
10 ** modification, are permitted provided that the following conditions
11 ** are met:
12 **
13 ** 1. Redistributions of source code must retain the above copyright
14 ** notice, this list of conditions and the following disclaimer.
15 ** 2. Redistributions in binary form must reproduce the above copyright
16 ** notice, this list of conditions and the following disclaimer in the
17 ** documentation and/or other materials provided with the distribution.
18 ** 3. The name of the author may not be used to endorse or promote products
19 ** derived from this software without specific prior written permission.
20 **
21 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 **---------------------------------------------------------------------------
32 **
33 */
34
35 // HEADER FILES ------------------------------------------------------------
36
37 #include <stdlib.h>
38 #include <stdio.h>
39 #include <string.h>
40 #include <ctype.h>
41 #include <assert.h>
42
43 #include "templates.h"
44 #include "doomtype.h"
45 #include "cmdlib.h"
46 #include "c_console.h"
47 #include "c_dispatch.h"
48 #include "m_argv.h"
49 #include "doomstat.h"
50 #include "d_player.h"
51 #include "configfile.h"
52 #include "m_crc32.h"
53 #include "v_text.h"
54 #include "d_net.h"
55 #include "d_main.h"
56 #include "farchive.h"
57
58 // MACROS ------------------------------------------------------------------
59
60 // TYPES -------------------------------------------------------------------
61
62 class DWaitingCommand : public DThinker
63 {
64 DECLARE_CLASS (DWaitingCommand, DThinker)
65 public:
66 DWaitingCommand (const char *cmd, int tics);
67 ~DWaitingCommand ();
68 void Serialize (FArchive &arc);
69 void Tick ();
70
71 private:
72 DWaitingCommand ();
73
74 char *Command;
75 int TicsLeft;
76 };
77
78 class DStoredCommand : public DThinker
79 {
80 DECLARE_CLASS (DStoredCommand, DThinker)
81 public:
82 DStoredCommand (FConsoleCommand *com, const char *cmd);
83 ~DStoredCommand ();
84 void Tick ();
85
86 private:
87 DStoredCommand ();
88
89 FConsoleCommand *Command;
90 char *Text;
91 };
92
93 struct FActionMap
94 {
95 FButtonStatus *Button;
96 unsigned int Key; // value from passing Name to MakeKey()
97 char Name[12];
98 };
99
100 // EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
101
102 // PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
103
104 // PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
105
106 static long ParseCommandLine (const char *args, int *argc, char **argv, bool no_escapes);
107 static FConsoleCommand *FindNameInHashTable (FConsoleCommand **table, const char *name, size_t namelen);
108 static FConsoleCommand *ScanChainForName (FConsoleCommand *start, const char *name, size_t namelen, FConsoleCommand **prev);
109
110 // EXTERNAL DATA DECLARATIONS ----------------------------------------------
111
112 // PUBLIC DATA DEFINITIONS -------------------------------------------------
113
114 CVAR (Bool, lookspring, true, CVAR_ARCHIVE); // Generate centerview when -mlook encountered?
115
116 FConsoleCommand *Commands[FConsoleCommand::HASH_SIZE];
117 FButtonStatus Button_Mlook, Button_Klook, Button_Use, Button_AltAttack,
118 Button_Attack, Button_Speed, Button_MoveRight, Button_MoveLeft,
119 Button_Strafe, Button_LookDown, Button_LookUp, Button_Back,
120 Button_Forward, Button_Right, Button_Left, Button_MoveDown,
121 Button_MoveUp, Button_Jump, Button_ShowScores, Button_Crouch,
122 Button_Zoom, Button_Reload,
123 Button_User1, Button_User2, Button_User3, Button_User4,
124 Button_AM_PanLeft, Button_AM_PanRight, Button_AM_PanDown, Button_AM_PanUp,
125 Button_AM_ZoomIn, Button_AM_ZoomOut;
126
127 bool ParsingKeyConf;
128
129 // To add new actions, go to the console and type "key <action name>".
130 // This will give you the key value to use in the first column. Then
131 // insert your new action into this list so that the keys remain sorted
132 // in ascending order. No two keys can be identical. If yours matches
133 // an existing key, change the name of your action.
134
135 FActionMap ActionMaps[] =
136 {
137 { &Button_AM_PanLeft, 0x0d52d67b, "am_panleft"},
138 { &Button_User2, 0x125f5226, "user2" },
139 { &Button_Jump, 0x1eefa611, "jump" },
140 { &Button_Right, 0x201f1c55, "right" },
141 { &Button_Zoom, 0x20ccc4d5, "zoom" },
142 { &Button_Back, 0x23a99cd7, "back" },
143 { &Button_AM_ZoomIn, 0x41df90c2, "am_zoomin"},
144 { &Button_Reload, 0x426b69e7, "reload" },
145 { &Button_LookDown, 0x4463f43a, "lookdown" },
146 { &Button_AM_ZoomOut, 0x51f7a334, "am_zoomout"},
147 { &Button_User4, 0x534c30ee, "user4" },
148 { &Button_Attack, 0x5622bf42, "attack" },
149 { &Button_User1, 0x577712d0, "user1" },
150 { &Button_Klook, 0x57c25cb2, "klook" },
151 { &Button_Forward, 0x59f3e907, "forward" },
152 { &Button_MoveDown, 0x6167ce99, "movedown" },
153 { &Button_AltAttack, 0x676885b8, "altattack" },
154 { &Button_MoveLeft, 0x6fa41b84, "moveleft" },
155 { &Button_MoveRight, 0x818f08e6, "moveright" },
156 { &Button_AM_PanRight, 0x8197097b, "am_panright"},
157 { &Button_AM_PanUp, 0x8d89955e, "am_panup"} ,
158 { &Button_Mlook, 0xa2b62d8b, "mlook" },
159 { &Button_Crouch, 0xab2c3e71, "crouch" },
160 { &Button_Left, 0xb000b483, "left" },
161 { &Button_LookUp, 0xb62b1e49, "lookup" },
162 { &Button_User3, 0xb6f8fe92, "user3" },
163 { &Button_Strafe, 0xb7e6a54b, "strafe" },
164 { &Button_AM_PanDown, 0xce301c81, "am_pandown"},
165 { &Button_ShowScores, 0xd5897c73, "showscores" },
166 { &Button_Speed, 0xe0ccb317, "speed" },
167 { &Button_Use, 0xe0cfc260, "use" },
168 { &Button_MoveUp, 0xfdd701c7, "moveup" },
169 };
170 #define NUM_ACTIONS countof(ActionMaps)
171
172
173 // PRIVATE DATA DEFINITIONS ------------------------------------------------
174
175 static const char *KeyConfCommands[] =
176 {
177 "alias",
178 "defaultbind",
179 "addkeysection",
180 "addmenukey",
181 "addslotdefault",
182 "weaponsection",
183 "setslot",
184 "addplayerclass",
185 "clearplayerclasses"
186 };
187
188 // CODE --------------------------------------------------------------------
189
IMPLEMENT_CLASS(DWaitingCommand)190 IMPLEMENT_CLASS (DWaitingCommand)
191
192 void DWaitingCommand::Serialize (FArchive &arc)
193 {
194 Super::Serialize (arc);
195 arc << Command << TicsLeft;
196 }
197
DWaitingCommand()198 DWaitingCommand::DWaitingCommand ()
199 {
200 Command = NULL;
201 TicsLeft = 1;
202 }
203
DWaitingCommand(const char * cmd,int tics)204 DWaitingCommand::DWaitingCommand (const char *cmd, int tics)
205 {
206 Command = copystring (cmd);
207 TicsLeft = tics+1;
208 }
209
~DWaitingCommand()210 DWaitingCommand::~DWaitingCommand ()
211 {
212 if (Command != NULL)
213 {
214 delete[] Command;
215 }
216 }
217
Tick()218 void DWaitingCommand::Tick ()
219 {
220 if (--TicsLeft == 0)
221 {
222 AddCommandString (Command);
223 Destroy ();
224 }
225 }
226
IMPLEMENT_CLASS(DStoredCommand)227 IMPLEMENT_CLASS (DStoredCommand)
228
229 DStoredCommand::DStoredCommand ()
230 {
231 Text = NULL;
232 Destroy ();
233 }
234
DStoredCommand(FConsoleCommand * command,const char * args)235 DStoredCommand::DStoredCommand (FConsoleCommand *command, const char *args)
236 {
237 Command = command;
238 Text = copystring (args);
239 }
240
~DStoredCommand()241 DStoredCommand::~DStoredCommand ()
242 {
243 if (Text != NULL)
244 {
245 delete[] Text;
246 }
247 }
248
Tick()249 void DStoredCommand::Tick ()
250 {
251 if (Text != NULL && Command != NULL)
252 {
253 FCommandLine args (Text);
254 Command->Run (args, players[consoleplayer].mo, 0);
255 }
256 Destroy ();
257 }
258
ListActionCommands(const char * pattern)259 static int ListActionCommands (const char *pattern)
260 {
261 char matcher[16];
262 unsigned int i;
263 int count = 0;
264
265 for (i = 0; i < NUM_ACTIONS; ++i)
266 {
267 if (pattern == NULL || CheckWildcards (pattern,
268 (mysnprintf (matcher, countof(matcher), "+%s", ActionMaps[i].Name), matcher)))
269 {
270 Printf ("+%s\n", ActionMaps[i].Name);
271 count++;
272 }
273 if (pattern == NULL || CheckWildcards (pattern,
274 (mysnprintf (matcher, countof(matcher), "-%s", ActionMaps[i].Name), matcher)))
275 {
276 Printf ("-%s\n", ActionMaps[i].Name);
277 count++;
278 }
279 }
280 return count;
281 }
282
283 /* ======================================================================== */
284
285 /* By Paul Hsieh (C) 2004, 2005. Covered under the Paul Hsieh derivative
286 license. See:
287 http://www.azillionmonkeys.com/qed/weblicense.html for license details.
288
289 http://www.azillionmonkeys.com/qed/hash.html */
290
291 #undef get16bits
292 #if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__) \
293 || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__)
294 #define get16bits(d) (*((const WORD *) (d)))
295 #endif
296
297 #if !defined (get16bits)
298 #define get16bits(d) ((((DWORD)(((const BYTE *)(d))[1])) << 8)\
299 +(DWORD)(((const BYTE *)(d))[0]) )
300 #endif
301
SuperFastHash(const char * data,size_t len)302 DWORD SuperFastHash (const char *data, size_t len)
303 {
304 DWORD hash = 0, tmp;
305 size_t rem;
306
307 if (len == 0 || data == NULL) return 0;
308
309 rem = len & 3;
310 len >>= 2;
311
312 /* Main loop */
313 for (;len > 0; len--)
314 {
315 hash += get16bits (data);
316 tmp = (get16bits (data+2) << 11) ^ hash;
317 hash = (hash << 16) ^ tmp;
318 data += 2*sizeof (WORD);
319 hash += hash >> 11;
320 }
321
322 /* Handle end cases */
323 switch (rem)
324 {
325 case 3: hash += get16bits (data);
326 hash ^= hash << 16;
327 hash ^= data[sizeof (WORD)] << 18;
328 hash += hash >> 11;
329 break;
330 case 2: hash += get16bits (data);
331 hash ^= hash << 11;
332 hash += hash >> 17;
333 break;
334 case 1: hash += *data;
335 hash ^= hash << 10;
336 hash += hash >> 1;
337 }
338
339 /* Force "avalanching" of final 127 bits */
340 hash ^= hash << 3;
341 hash += hash >> 5;
342 hash ^= hash << 4;
343 hash += hash >> 17;
344 hash ^= hash << 25;
345 hash += hash >> 6;
346
347 return hash;
348 }
349
350 /* A modified version to do a case-insensitive hash */
351
352 #undef get16bits
353 #define get16bits(d) ((((DWORD)tolower(((const BYTE *)(d))[1])) << 8)\
354 +(DWORD)tolower(((const BYTE *)(d))[0]) )
355
SuperFastHashI(const char * data,size_t len)356 DWORD SuperFastHashI (const char *data, size_t len)
357 {
358 DWORD hash = 0, tmp;
359 size_t rem;
360
361 if (len <= 0 || data == NULL) return 0;
362
363 rem = len & 3;
364 len >>= 2;
365
366 /* Main loop */
367 for (;len > 0; len--)
368 {
369 hash += get16bits (data);
370 tmp = (get16bits (data+2) << 11) ^ hash;
371 hash = (hash << 16) ^ tmp;
372 data += 2*sizeof (WORD);
373 hash += hash >> 11;
374 }
375
376 /* Handle end cases */
377 switch (rem)
378 {
379 case 3: hash += get16bits (data);
380 hash ^= hash << 16;
381 hash ^= tolower(data[sizeof (WORD)]) << 18;
382 hash += hash >> 11;
383 break;
384 case 2: hash += get16bits (data);
385 hash ^= hash << 11;
386 hash += hash >> 17;
387 break;
388 case 1: hash += tolower(*data);
389 hash ^= hash << 10;
390 hash += hash >> 1;
391 }
392
393 /* Force "avalanching" of final 127 bits */
394 hash ^= hash << 3;
395 hash += hash >> 5;
396 hash ^= hash << 4;
397 hash += hash >> 17;
398 hash ^= hash << 25;
399 hash += hash >> 6;
400
401 return hash;
402 }
403
404 /* ======================================================================== */
405
MakeKey(const char * s)406 unsigned int MakeKey (const char *s)
407 {
408 if (s == NULL)
409 {
410 return 0;
411 }
412 return SuperFastHashI (s, strlen (s));
413 }
414
MakeKey(const char * s,size_t len)415 unsigned int MakeKey (const char *s, size_t len)
416 {
417 return SuperFastHashI (s, len);
418 }
419
420 // FindButton scans through the actionbits[] array
421 // for a matching key and returns an index or -1 if
422 // the key could not be found. This uses binary search,
423 // so actionbits[] must be sorted in ascending order.
424
FindButton(unsigned int key)425 FButtonStatus *FindButton (unsigned int key)
426 {
427 const FActionMap *bit;
428
429 bit = BinarySearch<FActionMap, unsigned int>
430 (ActionMaps, NUM_ACTIONS, &FActionMap::Key, key);
431 return bit ? bit->Button : NULL;
432 }
433
PressKey(int keynum)434 bool FButtonStatus::PressKey (int keynum)
435 {
436 int i, open;
437
438 keynum &= KEY_DBLCLICKED-1;
439
440 if (keynum == 0)
441 { // Issued from console instead of a key, so force on
442 Keys[0] = 0xffff;
443 for (i = MAX_KEYS-1; i > 0; --i)
444 {
445 Keys[i] = 0;
446 }
447 }
448 else
449 {
450 for (i = MAX_KEYS-1, open = -1; i >= 0; --i)
451 {
452 if (Keys[i] == 0)
453 {
454 open = i;
455 }
456 else if (Keys[i] == keynum)
457 { // Key is already down; do nothing
458 return false;
459 }
460 }
461 if (open < 0)
462 { // No free key slots, so do nothing
463 Printf ("More than %u keys pressed for a single action!\n", MAX_KEYS);
464 return false;
465 }
466 Keys[open] = keynum;
467 }
468 BYTE wasdown = bDown;
469 bDown = bWentDown = true;
470 // Returns true if this key caused the button to go down.
471 return !wasdown;
472 }
473
ReleaseKey(int keynum)474 bool FButtonStatus::ReleaseKey (int keynum)
475 {
476 int i, numdown, match;
477 BYTE wasdown = bDown;
478
479 keynum &= KEY_DBLCLICKED-1;
480
481 if (keynum == 0)
482 { // Issued from console instead of a key, so force off
483 for (i = MAX_KEYS-1; i >= 0; --i)
484 {
485 Keys[i] = 0;
486 }
487 bWentUp = true;
488 bDown = false;
489 }
490 else
491 {
492 for (i = MAX_KEYS-1, numdown = 0, match = -1; i >= 0; --i)
493 {
494 if (Keys[i] != 0)
495 {
496 ++numdown;
497 if (Keys[i] == keynum)
498 {
499 match = i;
500 }
501 }
502 }
503 if (match < 0)
504 { // Key was not down; do nothing
505 return false;
506 }
507 Keys[match] = 0;
508 bWentUp = true;
509 if (--numdown == 0)
510 {
511 bDown = false;
512 }
513 }
514 // Returns true if releasing this key caused the button to go up.
515 return wasdown && !bDown;
516 }
517
ResetButtonTriggers()518 void ResetButtonTriggers ()
519 {
520 for (int i = NUM_ACTIONS-1; i >= 0; --i)
521 {
522 ActionMaps[i].Button->ResetTriggers ();
523 }
524 }
525
ResetButtonStates()526 void ResetButtonStates ()
527 {
528 for (int i = NUM_ACTIONS-1; i >= 0; --i)
529 {
530 FButtonStatus *button = ActionMaps[i].Button;
531
532 if (button != &Button_Mlook && button != &Button_Klook)
533 {
534 button->ReleaseKey (0);
535 }
536 button->ResetTriggers ();
537 }
538 }
539
C_DoCommand(const char * cmd,int keynum)540 void C_DoCommand (const char *cmd, int keynum)
541 {
542 FConsoleCommand *com;
543 const char *end;
544 const char *beg;
545
546 // Skip any beginning whitespace
547 while (*cmd && *cmd <= ' ')
548 cmd++;
549
550 // Find end of the command name
551 if (*cmd == '\"')
552 {
553 for (end = beg = cmd+1; *end && *end != '\"'; ++end)
554 ;
555 }
556 else
557 {
558 beg = cmd;
559 for (end = cmd+1; *end > ' '; ++end)
560 ;
561 }
562
563 const size_t len = end - beg;
564
565 if (ParsingKeyConf)
566 {
567 int i;
568
569 for (i = countof(KeyConfCommands)-1; i >= 0; --i)
570 {
571 if (strnicmp (beg, KeyConfCommands[i], len) == 0 &&
572 KeyConfCommands[i][len] == 0)
573 {
574 break;
575 }
576 }
577 if (i < 0)
578 {
579 Printf ("Invalid command for KEYCONF: %s\n", beg);
580 return;
581 }
582 }
583
584 // Check if this is an action
585 if (*beg == '+' || *beg == '-')
586 {
587 FButtonStatus *button;
588
589 button = FindButton (MakeKey (beg + 1, end - beg - 1));
590 if (button != NULL)
591 {
592 if (*beg == '+')
593 {
594 button->PressKey (keynum);
595 }
596 else
597 {
598 button->ReleaseKey (keynum);
599 if (button == &Button_Mlook && lookspring)
600 {
601 Net_WriteByte (DEM_CENTERVIEW);
602 }
603 }
604 return;
605 }
606 }
607
608 // Parse it as a normal command
609 // Checking for matching commands follows this search order:
610 // 1. Check the Commands[] hash table
611 // 2. Check the CVars list
612
613 if ( (com = FindNameInHashTable (Commands, beg, len)) )
614 {
615 if (gamestate != GS_STARTUP || ParsingKeyConf ||
616 (len == 3 && strnicmp (beg, "set", 3) == 0) ||
617 (len == 7 && strnicmp (beg, "logfile", 7) == 0) ||
618 (len == 9 && strnicmp (beg, "unbindall", 9) == 0) ||
619 (len == 4 && strnicmp (beg, "bind", 4) == 0) ||
620 (len == 4 && strnicmp (beg, "exec", 4) == 0) ||
621 (len ==10 && strnicmp (beg, "doublebind", 10) == 0) ||
622 (len == 6 && strnicmp (beg, "pullin", 6) == 0)
623 )
624 {
625 FCommandLine args (beg);
626 com->Run (args, players[consoleplayer].mo, keynum);
627 }
628 else
629 {
630 if (len == 4 && strnicmp(beg, "warp", 4) == 0)
631 {
632 StoredWarp = beg;
633 }
634 else
635 {
636 new DStoredCommand (com, beg);
637 }
638 }
639 }
640 else
641 { // Check for any console vars that match the command
642 FBaseCVar *var = FindCVarSub (beg, int(len));
643
644 if (var != NULL)
645 {
646 FCommandLine args (beg);
647
648 if (args.argc() >= 2)
649 { // Set the variable
650 var->CmdSet (args[1]);
651 }
652 else
653 { // Get the variable's value
654 UCVarValue val = var->GetGenericRep (CVAR_String);
655 Printf ("\"%s\" is \"%s\"\n", var->GetName(), val.String);
656 }
657 }
658 else
659 { // We don't know how to handle this command
660 Printf ("Unknown command \"%.*s\"\n", (int)len, beg);
661 }
662 }
663 }
664
AddCommandString(char * cmd,int keynum)665 void AddCommandString (char *cmd, int keynum)
666 {
667 char *brkpt;
668 int more;
669
670 if (cmd)
671 {
672 while (*cmd)
673 {
674 brkpt = cmd;
675 while (*brkpt != ';' && *brkpt != '\0')
676 {
677 if (*brkpt == '\"')
678 {
679 brkpt++;
680 while (*brkpt != '\0' && (*brkpt != '\"' || *(brkpt-1) == '\\'))
681 brkpt++;
682 }
683 if (*brkpt != '\0')
684 brkpt++;
685 }
686 if (*brkpt == ';')
687 {
688 *brkpt = '\0';
689 more = 1;
690 }
691 else
692 {
693 more = 0;
694 }
695 // Intercept wait commands here. Note: wait must be lowercase
696 while (*cmd && *cmd <= ' ')
697 cmd++;
698 if (*cmd)
699 {
700 if (!ParsingKeyConf &&
701 cmd[0] == 'w' && cmd[1] == 'a' && cmd[2] == 'i' && cmd[3] == 't' &&
702 (cmd[4] == 0 || cmd[4] == ' '))
703 {
704 int tics;
705
706 if (cmd[4] == ' ')
707 {
708 tics = strtol (cmd + 5, NULL, 0);
709 }
710 else
711 {
712 tics = 1;
713 }
714 if (tics > 0)
715 {
716 if (more)
717 { // The remainder of the command will be executed later
718 // Note that deferred commands lose track of which key
719 // (if any) they were pressed from.
720 *brkpt = ';';
721 new DWaitingCommand (brkpt, tics);
722 }
723 return;
724 }
725 }
726 else
727 {
728 C_DoCommand (cmd, keynum);
729 }
730 }
731 if (more)
732 {
733 *brkpt = ';';
734 }
735 cmd = brkpt + more;
736 }
737 }
738 }
739
740 // ParseCommandLine
741 //
742 // Parse a command line (passed in args). If argc is non-NULL, it will
743 // be set to the number of arguments. If argv is non-NULL, it will be
744 // filled with pointers to each argument; argv[0] should be initialized
745 // to point to a buffer large enough to hold all the arguments. The
746 // return value is the necessary size of this buffer.
747 //
748 // Special processing:
749 // Inside quoted strings, \" becomes just "
750 // \\ becomes just a single backslash
751 // \c becomes just TEXTCOLOR_ESCAPE
752 // $<cvar> is replaced by the contents of <cvar>
753
ParseCommandLine(const char * args,int * argc,char ** argv,bool no_escapes)754 static long ParseCommandLine (const char *args, int *argc, char **argv, bool no_escapes)
755 {
756 int count;
757 char *buffplace;
758
759 count = 0;
760 buffplace = NULL;
761 if (argv != NULL)
762 {
763 buffplace = argv[0];
764 }
765
766 for (;;)
767 {
768 while (*args <= ' ' && *args)
769 { // skip white space
770 args++;
771 }
772 if (*args == 0)
773 {
774 break;
775 }
776 else if (*args == '\"')
777 { // read quoted string
778 char stuff;
779 if (argv != NULL)
780 {
781 argv[count] = buffplace;
782 }
783 count++;
784 args++;
785 do
786 {
787 stuff = *args++;
788 if (!no_escapes && stuff == '\\' && *args == '\"')
789 {
790 stuff = '\"', args++;
791 }
792 else if (!no_escapes && stuff == '\\' && *args == '\\')
793 {
794 args++;
795 }
796 else if (!no_escapes && stuff == '\\' && *args == 'c')
797 {
798 stuff = TEXTCOLOR_ESCAPE, args++;
799 }
800 else if (stuff == '\"')
801 {
802 stuff = 0;
803 }
804 else if (stuff == 0)
805 {
806 args--;
807 }
808 if (argv != NULL)
809 {
810 *buffplace = stuff;
811 }
812 buffplace++;
813 } while (stuff);
814 }
815 else
816 { // read unquoted string
817 const char *start = args++, *end;
818 FBaseCVar *var;
819 UCVarValue val;
820
821 while (*args && *args > ' ' && *args != '\"')
822 args++;
823 if (*start == '$' && (var = FindCVarSub (start+1, int(args-start-1))))
824 {
825 val = var->GetGenericRep (CVAR_String);
826 start = val.String;
827 end = start + strlen (start);
828 }
829 else
830 {
831 end = args;
832 }
833 if (argv != NULL)
834 {
835 argv[count] = buffplace;
836 while (start < end)
837 *buffplace++ = *start++;
838 *buffplace++ = 0;
839 }
840 else
841 {
842 buffplace += end - start + 1;
843 }
844 count++;
845 }
846 }
847 if (argc != NULL)
848 {
849 *argc = count;
850 }
851 return (long)(buffplace - (char *)0);
852 }
853
FCommandLine(const char * commandline,bool no_escapes)854 FCommandLine::FCommandLine (const char *commandline, bool no_escapes)
855 {
856 cmd = commandline;
857 _argc = -1;
858 _argv = NULL;
859 noescapes = no_escapes;
860 }
861
~FCommandLine()862 FCommandLine::~FCommandLine ()
863 {
864 if (_argv != NULL)
865 {
866 delete[] _argv;
867 }
868 }
869
Shift()870 void FCommandLine::Shift()
871 {
872 // Only valid after _argv has been filled.
873 for (int i = 1; i < _argc; ++i)
874 {
875 _argv[i - 1] = _argv[i];
876 }
877 }
878
argc()879 int FCommandLine::argc ()
880 {
881 if (_argc == -1)
882 {
883 argsize = ParseCommandLine (cmd, &_argc, NULL, noescapes);
884 }
885 return _argc;
886 }
887
operator [](int i)888 char *FCommandLine::operator[] (int i)
889 {
890 if (_argv == NULL)
891 {
892 int count = argc();
893 _argv = new char *[count + (argsize+sizeof(char*)-1)/sizeof(char*)];
894 _argv[0] = (char *)_argv + count*sizeof(char *);
895 ParseCommandLine (cmd, NULL, _argv, noescapes);
896 }
897 return _argv[i];
898 }
899
ScanChainForName(FConsoleCommand * start,const char * name,size_t namelen,FConsoleCommand ** prev)900 static FConsoleCommand *ScanChainForName (FConsoleCommand *start, const char *name, size_t namelen, FConsoleCommand **prev)
901 {
902 int comp;
903
904 *prev = NULL;
905 while (start)
906 {
907 comp = strnicmp (start->m_Name, name, namelen);
908 if (comp > 0)
909 return NULL;
910 else if (comp == 0 && start->m_Name[namelen] == 0)
911 return start;
912
913 *prev = start;
914 start = start->m_Next;
915 }
916 return NULL;
917 }
918
FindNameInHashTable(FConsoleCommand ** table,const char * name,size_t namelen)919 static FConsoleCommand *FindNameInHashTable (FConsoleCommand **table, const char *name, size_t namelen)
920 {
921 FConsoleCommand *dummy;
922
923 return ScanChainForName (table[MakeKey (name, namelen) % FConsoleCommand::HASH_SIZE], name, namelen, &dummy);
924 }
925
AddToHash(FConsoleCommand ** table)926 bool FConsoleCommand::AddToHash (FConsoleCommand **table)
927 {
928 unsigned int key;
929 FConsoleCommand *insert, **bucket;
930
931 key = MakeKey (m_Name);
932 bucket = &table[key % HASH_SIZE];
933
934 if (ScanChainForName (*bucket, m_Name, strlen (m_Name), &insert))
935 {
936 return false;
937 }
938 else
939 {
940 if (insert)
941 {
942 m_Next = insert->m_Next;
943 if (m_Next)
944 m_Next->m_Prev = &m_Next;
945 insert->m_Next = this;
946 m_Prev = &insert->m_Next;
947 }
948 else
949 {
950 m_Next = *bucket;
951 *bucket = this;
952 m_Prev = bucket;
953 if (m_Next)
954 m_Next->m_Prev = &m_Next;
955 }
956 }
957 return true;
958 }
959
FindByName(const char * name)960 FConsoleCommand* FConsoleCommand::FindByName (const char* name)
961 {
962 return FindNameInHashTable (Commands, name, strlen (name));
963 }
964
FConsoleCommand(const char * name,CCmdRun runFunc)965 FConsoleCommand::FConsoleCommand (const char *name, CCmdRun runFunc)
966 : m_RunFunc (runFunc)
967 {
968 static bool firstTime = true;
969
970 if (firstTime)
971 {
972 char tname[16];
973 unsigned int i;
974
975 firstTime = false;
976
977 // Add all the action commands for tab completion
978 for (i = 0; i < NUM_ACTIONS; i++)
979 {
980 strcpy (&tname[1], ActionMaps[i].Name);
981 tname[0] = '+';
982 C_AddTabCommand (tname);
983 tname[0] = '-';
984 C_AddTabCommand (tname);
985 }
986 }
987
988 int ag = strcmp (name, "kill");
989 if (ag == 0)
990 ag=0;
991 m_Name = copystring (name);
992
993 if (!AddToHash (Commands))
994 Printf ("FConsoleCommand c'tor: %s exists\n", name);
995 else
996 C_AddTabCommand (name);
997 }
998
~FConsoleCommand()999 FConsoleCommand::~FConsoleCommand ()
1000 {
1001 *m_Prev = m_Next;
1002 if (m_Next)
1003 m_Next->m_Prev = m_Prev;
1004 C_RemoveTabCommand (m_Name);
1005 delete[] m_Name;
1006 }
1007
Run(FCommandLine & argv,APlayerPawn * who,int key)1008 void FConsoleCommand::Run (FCommandLine &argv, APlayerPawn *who, int key)
1009 {
1010 m_RunFunc (argv, who, key);
1011 }
1012
FConsoleAlias(const char * name,const char * command,bool noSave)1013 FConsoleAlias::FConsoleAlias (const char *name, const char *command, bool noSave)
1014 : FConsoleCommand (name, NULL),
1015 bRunning(false), bKill(false)
1016 {
1017 m_Command[noSave] = command;
1018 m_Command[!noSave] = FString();
1019 // If the command contains % characters, assume they are parameter markers
1020 // for substitution when the command is executed.
1021 bDoSubstitution = (strchr (command, '%') != NULL);
1022 }
1023
~FConsoleAlias()1024 FConsoleAlias::~FConsoleAlias ()
1025 {
1026 m_Command[1] = m_Command[0] = FString();
1027 }
1028
1029 // Given an argument vector, reconstitute the command line it could have been produced from.
BuildString(int argc,FString * argv)1030 FString BuildString (int argc, FString *argv)
1031 {
1032 if (argc == 1)
1033 {
1034 return *argv;
1035 }
1036 else
1037 {
1038 FString buf;
1039 int arg;
1040
1041 for (arg = 0; arg < argc; arg++)
1042 {
1043 if (strchr(argv[arg], '"'))
1044 { // If it contains one or more quotes, we need to escape them.
1045 buf << '"';
1046 long substr_start = 0, quotepos;
1047 while ((quotepos = argv[arg].IndexOf('"', substr_start)) >= 0)
1048 {
1049 if (substr_start < quotepos)
1050 {
1051 buf << argv[arg].Mid(substr_start, quotepos - substr_start);
1052 }
1053 buf << "\\\"";
1054 substr_start = quotepos + 1;
1055 }
1056 buf << argv[arg].Mid(substr_start) << "\" ";
1057 }
1058 else if (strchr(argv[arg], ' '))
1059 { // If it contains a space, it needs to be quoted.
1060 buf << '"' << argv[arg] << "\" ";
1061 }
1062 else
1063 {
1064 buf << argv[arg] << ' ';
1065 }
1066 }
1067 return buf;
1068 }
1069 }
1070
1071 //===========================================================================
1072 //
1073 // SubstituteAliasParams
1074 //
1075 // Given an command line and a set of arguments, replace instances of
1076 // %x or %{x} in the command line with argument x. If argument x does not
1077 // exist, then the empty string is substituted in its place.
1078 //
1079 // Substitution is not done inside of quoted strings, unless that string is
1080 // prepended with a % character.
1081 //
1082 // To avoid a substitution, use %%. The %% will be replaced by a single %.
1083 //
1084 //===========================================================================
1085
SubstituteAliasParams(FString & command,FCommandLine & args)1086 FString SubstituteAliasParams (FString &command, FCommandLine &args)
1087 {
1088 // Do substitution by replacing %x with the argument x.
1089 // If there is no argument x, then %x is simply removed.
1090
1091 // For some reason, strtoul's stop parameter is non-const.
1092 char *p = command.LockBuffer(), *start = p;
1093 unsigned long argnum;
1094 FString buf;
1095 bool inquote = false;
1096
1097 while (*p != '\0')
1098 {
1099 if (p[0] == '%' && ((p[1] >= '0' && p[1] <= '9') || p[1] == '{'))
1100 {
1101 // Do a substitution. Output what came before this.
1102 buf.AppendCStrPart (start, p - start);
1103
1104 // Extract the argument number and substitute the corresponding argument.
1105 argnum = strtoul (p + 1 + (p[1] == '{'), &start, 10);
1106 if ((p[1] != '{' || *start == '}') && argnum < (unsigned long)args.argc())
1107 {
1108 buf += args[argnum];
1109 }
1110 p = (start += (p[1] == '{' && *start == '}'));
1111 }
1112 else if (p[0] == '%' && p[1] == '%')
1113 {
1114 // Do not substitute. Just collapse to a single %.
1115 buf.AppendCStrPart (start, p - start + 1);
1116 start = p = p + 2;
1117 continue;
1118 }
1119 else if (p[0] == '%' && p[1] == '"')
1120 {
1121 // Collapse %" to " and remember that we're in a quote so when we
1122 // see a " character again, we don't start skipping below.
1123 if (!inquote)
1124 {
1125 inquote = true;
1126 buf.AppendCStrPart(start, p - start);
1127 start = p + 1;
1128 }
1129 else
1130 {
1131 inquote = false;
1132 }
1133 p += 2;
1134 }
1135 else if (p[0] == '\\' && p[1] == '"')
1136 {
1137 p += 2;
1138 }
1139 else if (p[0] == '"')
1140 {
1141 // Don't substitute inside quoted strings if it didn't start
1142 // with a %"
1143 if (!inquote)
1144 {
1145 p++;
1146 while (*p != '\0' && (*p != '"' || *(p-1) == '\\'))
1147 p++;
1148 if (*p != '\0')
1149 p++;
1150 }
1151 else
1152 {
1153 inquote = false;
1154 p++;
1155 }
1156 }
1157 else
1158 {
1159 p++;
1160 }
1161 }
1162 // Return whatever was after the final substitution.
1163 if (p > start)
1164 {
1165 buf.AppendCStrPart (start, p - start);
1166 }
1167 command.UnlockBuffer();
1168
1169 return buf;
1170 }
1171
DumpHash(FConsoleCommand ** table,bool aliases,const char * pattern=NULL)1172 static int DumpHash (FConsoleCommand **table, bool aliases, const char *pattern=NULL)
1173 {
1174 int bucket, count;
1175 FConsoleCommand *cmd;
1176
1177 for (bucket = count = 0; bucket < FConsoleCommand::HASH_SIZE; bucket++)
1178 {
1179 cmd = table[bucket];
1180 while (cmd)
1181 {
1182 if (CheckWildcards (pattern, cmd->m_Name))
1183 {
1184 if (cmd->IsAlias())
1185 {
1186 if (aliases)
1187 {
1188 ++count;
1189 static_cast<FConsoleAlias *>(cmd)->PrintAlias ();
1190 }
1191 }
1192 else if (!aliases)
1193 {
1194 ++count;
1195 cmd->PrintCommand ();
1196 }
1197 }
1198 cmd = cmd->m_Next;
1199 }
1200 }
1201 return count;
1202 }
1203
PrintAlias()1204 void FConsoleAlias::PrintAlias ()
1205 {
1206 if (m_Command[0])
1207 {
1208 Printf (TEXTCOLOR_YELLOW "%s : %s\n", m_Name, m_Command[0].GetChars());
1209 }
1210 if (m_Command[1])
1211 {
1212 Printf (TEXTCOLOR_ORANGE "%s : %s\n", m_Name, m_Command[1].GetChars());
1213 }
1214 }
1215
Archive(FConfigFile * f)1216 void FConsoleAlias::Archive (FConfigFile *f)
1217 {
1218 if (f != NULL && !m_Command[0].IsEmpty())
1219 {
1220 f->SetValueForKey ("Name", m_Name, true);
1221 f->SetValueForKey ("Command", m_Command[0], true);
1222 }
1223 }
1224
C_ArchiveAliases(FConfigFile * f)1225 void C_ArchiveAliases (FConfigFile *f)
1226 {
1227 int bucket;
1228 FConsoleCommand *alias;
1229
1230 for (bucket = 0; bucket < FConsoleCommand::HASH_SIZE; bucket++)
1231 {
1232 alias = Commands[bucket];
1233 while (alias)
1234 {
1235 if (alias->IsAlias())
1236 static_cast<FConsoleAlias *>(alias)->Archive (f);
1237 alias = alias->m_Next;
1238 }
1239 }
1240 }
1241
C_ClearAliases()1242 void C_ClearAliases ()
1243 {
1244 int bucket;
1245 FConsoleCommand *alias;
1246
1247 for (bucket = 0; bucket < FConsoleCommand::HASH_SIZE; bucket++)
1248 {
1249 alias = Commands[bucket];
1250 while (alias)
1251 {
1252 FConsoleCommand *next = alias->m_Next;
1253 if (alias->IsAlias())
1254 static_cast<FConsoleAlias *>(alias)->SafeDelete();
1255 alias = next;
1256 }
1257 }
1258 }
1259
CCMD(clearaliases)1260 CCMD(clearaliases)
1261 {
1262 C_ClearAliases();
1263 }
1264
1265
1266 // This is called only by the ini parser.
C_SetAlias(const char * name,const char * cmd)1267 void C_SetAlias (const char *name, const char *cmd)
1268 {
1269 FConsoleCommand *prev, *alias, **chain;
1270
1271 chain = &Commands[MakeKey (name) % FConsoleCommand::HASH_SIZE];
1272 alias = ScanChainForName (*chain, name, strlen (name), &prev);
1273 if (alias != NULL)
1274 {
1275 if (!alias->IsAlias ())
1276 {
1277 //Printf (PRINT_BOLD, "%s is a command and cannot be an alias.\n", name);
1278 return;
1279 }
1280 delete alias;
1281 }
1282 new FConsoleAlias (name, cmd, false);
1283 }
1284
CCMD(alias)1285 CCMD (alias)
1286 {
1287 FConsoleCommand *prev, *alias, **chain;
1288
1289 if (argv.argc() == 1)
1290 {
1291 Printf ("Current alias commands:\n");
1292 DumpHash (Commands, true);
1293 }
1294 else
1295 {
1296 chain = &Commands[MakeKey (argv[1]) % FConsoleCommand::HASH_SIZE];
1297
1298 if (argv.argc() == 2)
1299 { // Remove the alias
1300
1301 if ( (alias = ScanChainForName (*chain, argv[1], strlen (argv[1]), &prev)))
1302 {
1303 if (alias->IsAlias ())
1304 {
1305 static_cast<FConsoleAlias *> (alias)->SafeDelete ();
1306 }
1307 else
1308 {
1309 Printf ("%s is a normal command\n", alias->m_Name);
1310 }
1311 }
1312 }
1313 else
1314 { // Add/change the alias
1315
1316 alias = ScanChainForName (*chain, argv[1], strlen (argv[1]), &prev);
1317 if (alias != NULL)
1318 {
1319 if (alias->IsAlias ())
1320 {
1321 static_cast<FConsoleAlias *> (alias)->Realias (argv[2], ParsingKeyConf);
1322 }
1323 else
1324 {
1325 Printf ("%s is a normal command\n", alias->m_Name);
1326 alias = NULL;
1327 }
1328 }
1329 else
1330 {
1331 new FConsoleAlias (argv[1], argv[2], ParsingKeyConf);
1332 }
1333 }
1334 }
1335 }
1336
CCMD(cmdlist)1337 CCMD (cmdlist)
1338 {
1339 int count;
1340 const char *filter = (argv.argc() == 1 ? NULL : argv[1]);
1341
1342 count = ListActionCommands (filter);
1343 count += DumpHash (Commands, false, filter);
1344 Printf ("%d commands\n", count);
1345 }
1346
CCMD(key)1347 CCMD (key)
1348 {
1349 if (argv.argc() > 1)
1350 {
1351 int i;
1352
1353 for (i = 1; i < argv.argc(); ++i)
1354 {
1355 unsigned int key = MakeKey (argv[i]);
1356 Printf (" 0x%08x\n", key);
1357 }
1358 }
1359 }
1360
1361 // Execute any console commands specified on the command line.
1362 // These all begin with '+' as opposed to '-'.
C_ParseCmdLineParams(FExecList * exec)1363 FExecList *C_ParseCmdLineParams(FExecList *exec)
1364 {
1365 for (int currArg = 1; currArg < Args->NumArgs(); )
1366 {
1367 if (*Args->GetArg (currArg++) == '+')
1368 {
1369 FString cmdString;
1370 int cmdlen = 1;
1371 int argstart = currArg - 1;
1372
1373 while (currArg < Args->NumArgs())
1374 {
1375 if (*Args->GetArg (currArg) == '-' || *Args->GetArg (currArg) == '+')
1376 break;
1377 currArg++;
1378 cmdlen++;
1379 }
1380
1381 cmdString = BuildString (cmdlen, Args->GetArgList (argstart));
1382 if (!cmdString.IsEmpty())
1383 {
1384 if (exec == NULL)
1385 {
1386 exec = new FExecList;
1387 }
1388 exec->AddCommand(&cmdString[1]);
1389 }
1390 }
1391 }
1392 return exec;
1393 }
1394
IsAlias()1395 bool FConsoleCommand::IsAlias ()
1396 {
1397 return false;
1398 }
1399
IsAlias()1400 bool FConsoleAlias::IsAlias ()
1401 {
1402 return true;
1403 }
1404
Run(FCommandLine & args,APlayerPawn * who,int key)1405 void FConsoleAlias::Run (FCommandLine &args, APlayerPawn *who, int key)
1406 {
1407 if (bRunning)
1408 {
1409 Printf ("Alias %s tried to recurse.\n", m_Name);
1410 return;
1411 }
1412
1413 int index = !m_Command[1].IsEmpty();
1414 FString savedcommand = m_Command[index], mycommand;
1415 m_Command[index] = FString();
1416
1417 if (bDoSubstitution)
1418 {
1419 mycommand = SubstituteAliasParams (savedcommand, args);
1420 }
1421 else
1422 {
1423 mycommand = savedcommand;
1424 }
1425
1426 bRunning = true;
1427 AddCommandString (mycommand.LockBuffer(), key);
1428 mycommand.UnlockBuffer();
1429 bRunning = false;
1430 if (m_Command[index].IsEmpty())
1431 { // The alias is unchanged, so put the command back so it can be used again.
1432 // If the command had been non-empty, then that means that executing this
1433 // alias caused it to realias itself, so the old command will be forgotten
1434 // once this function returns.
1435 m_Command[index] = savedcommand;
1436 }
1437 if (bKill)
1438 { // The alias wants to remove itself
1439 delete this;
1440 }
1441 }
1442
Realias(const char * command,bool noSave)1443 void FConsoleAlias::Realias (const char *command, bool noSave)
1444 {
1445 if (!noSave && !m_Command[1].IsEmpty())
1446 {
1447 noSave = true;
1448 }
1449 m_Command[noSave] = command;
1450
1451 // If the command contains % characters, assume they are parameter markers
1452 // for substitution when the command is executed.
1453 bDoSubstitution = (strchr (command, '%') != NULL);
1454 bKill = false;
1455 }
1456
SafeDelete()1457 void FConsoleAlias::SafeDelete ()
1458 {
1459 if (!bRunning)
1460 {
1461 delete this;
1462 }
1463 else
1464 {
1465 bKill = true;
1466 }
1467 }
1468
AddCommand(const char * cmd,const char * file)1469 void FExecList::AddCommand(const char *cmd, const char *file)
1470 {
1471 // Pullins are special and need to be separated from general commands.
1472 // They also turned out to be a really bad idea, since they make things
1473 // more complicated. :(
1474 if (file != NULL && strnicmp(cmd, "pullin", 6) == 0 && isspace(cmd[6]))
1475 {
1476 FCommandLine line(cmd);
1477 C_SearchForPullins(this, file, line);
1478 }
1479 // Recursive exec: Parse this file now.
1480 else if (strnicmp(cmd, "exec", 4) == 0 && isspace(cmd[4]))
1481 {
1482 FCommandLine argv(cmd);
1483 for (int i = 1; i < argv.argc(); ++i)
1484 {
1485 C_ParseExecFile(argv[i], this);
1486 }
1487 }
1488 else
1489 {
1490 Commands.Push(cmd);
1491 }
1492 }
1493
ExecCommands() const1494 void FExecList::ExecCommands() const
1495 {
1496 for (unsigned i = 0; i < Commands.Size(); ++i)
1497 {
1498 AddCommandString(Commands[i].LockBuffer());
1499 Commands[i].UnlockBuffer();
1500 }
1501 }
1502
AddPullins(TArray<FString> & wads) const1503 void FExecList::AddPullins(TArray<FString> &wads) const
1504 {
1505 for (unsigned i = 0; i < Pullins.Size(); ++i)
1506 {
1507 D_AddFile(wads, Pullins[i]);
1508 }
1509 }
1510
C_ParseExecFile(const char * file,FExecList * exec)1511 FExecList *C_ParseExecFile(const char *file, FExecList *exec)
1512 {
1513 FILE *f;
1514 char cmd[4096];
1515 int retval = 0;
1516
1517 if ( (f = fopen (file, "r")) )
1518 {
1519 while (fgets(cmd, countof(cmd)-1, f))
1520 {
1521 // Comments begin with //
1522 char *stop = cmd + strlen(cmd) - 1;
1523 char *comment = cmd;
1524 int inQuote = 0;
1525
1526 if (*stop == '\n')
1527 *stop-- = 0;
1528
1529 while (comment < stop)
1530 {
1531 if (*comment == '\"')
1532 {
1533 inQuote ^= 1;
1534 }
1535 else if (!inQuote && *comment == '/' && *(comment + 1) == '/')
1536 {
1537 break;
1538 }
1539 comment++;
1540 }
1541 if (comment == cmd)
1542 { // Comment at line beginning
1543 continue;
1544 }
1545 else if (comment < stop)
1546 { // Comment in middle of line
1547 *comment = 0;
1548 }
1549 if (exec == NULL)
1550 {
1551 exec = new FExecList;
1552 }
1553 exec->AddCommand(cmd, file);
1554 }
1555 if (!feof(f))
1556 {
1557 Printf("Error parsing \"%s\"\n", file);
1558 }
1559 fclose(f);
1560 }
1561 else
1562 {
1563 Printf ("Could not open \"%s\"\n", file);
1564 }
1565 return exec;
1566 }
1567
C_ExecFile(const char * file)1568 bool C_ExecFile (const char *file)
1569 {
1570 FExecList *exec = C_ParseExecFile(file, NULL);
1571 if (exec != NULL)
1572 {
1573 exec->ExecCommands();
1574 if (exec->Pullins.Size() > 0)
1575 {
1576 Printf(TEXTCOLOR_BOLD "Notice: Pullin files were ignored.\n");
1577 }
1578 delete exec;
1579 }
1580 return exec != NULL;
1581 }
1582
C_SearchForPullins(FExecList * exec,const char * file,FCommandLine & argv)1583 void C_SearchForPullins(FExecList *exec, const char *file, FCommandLine &argv)
1584 {
1585 const char *lastSlash;
1586
1587 assert(exec != NULL);
1588 assert(file != NULL);
1589 #ifdef __unix__
1590 lastSlash = strrchr(file, '/');
1591 #else
1592 const char *lastSlash1, *lastSlash2;
1593
1594 lastSlash1 = strrchr(file, '/');
1595 lastSlash2 = strrchr(file, '\\');
1596 lastSlash = MAX(lastSlash1, lastSlash2);
1597 #endif
1598
1599 for (int i = 1; i < argv.argc(); ++i)
1600 {
1601 // Try looking for the wad in the same directory as the .cfg
1602 // before looking for it in the current directory.
1603 if (lastSlash != NULL)
1604 {
1605 FString path(file, (lastSlash - file) + 1);
1606 path += argv[i];
1607 if (FileExists(path))
1608 {
1609 exec->Pullins.Push(path);
1610 continue;
1611 }
1612 }
1613 exec->Pullins.Push(argv[i]);
1614 }
1615 }
1616
CCMD(pullin)1617 CCMD (pullin)
1618 {
1619 // Actual handling for pullin is now completely special-cased above
1620 Printf (TEXTCOLOR_BOLD "Pullin" TEXTCOLOR_NORMAL " is only valid from .cfg\n"
1621 "files and only when used at startup.\n");
1622 }
1623
1624