mirror of
https://github.com/DrBeef/Raze.git
synced 2024-11-22 20:21:20 +00:00
1152 lines
24 KiB
C++
1152 lines
24 KiB
C++
/*
|
|
** c_dispatch.cpp
|
|
** Functions for executing console commands and aliases
|
|
**
|
|
**---------------------------------------------------------------------------
|
|
** Copyright 1998-2007 Randy Heit
|
|
** All rights reserved.
|
|
**
|
|
** Redistribution and use in source and binary forms, with or without
|
|
** modification, are permitted provided that the following conditions
|
|
** are met:
|
|
**
|
|
** 1. Redistributions of source code must retain the above copyright
|
|
** notice, this list of conditions and the following disclaimer.
|
|
** 2. Redistributions in binary form must reproduce the above copyright
|
|
** notice, this list of conditions and the following disclaimer in the
|
|
** documentation and/or other materials provided with the distribution.
|
|
** 3. The name of the author may not be used to endorse or promote products
|
|
** derived from this software without specific prior written permission.
|
|
**
|
|
** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
|
** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
|
** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
**---------------------------------------------------------------------------
|
|
**
|
|
*/
|
|
|
|
// HEADER FILES ------------------------------------------------------------
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
|
|
|
|
#include "cmdlib.h"
|
|
#include "c_console.h"
|
|
#include "c_dispatch.h"
|
|
#include "m_argv.h"
|
|
#include "gamestate.h"
|
|
#include "configfile.h"
|
|
#include "printf.h"
|
|
#include "c_cvars.h"
|
|
#include "c_buttons.h"
|
|
#include "findfile.h"
|
|
#include "gstrings.h"
|
|
|
|
// MACROS ------------------------------------------------------------------
|
|
|
|
// TYPES -------------------------------------------------------------------
|
|
|
|
class FDelayedCommand
|
|
{
|
|
public:
|
|
virtual ~FDelayedCommand() {}
|
|
|
|
protected:
|
|
virtual bool Tick() = 0;
|
|
|
|
friend class FDelayedCommandQueue;
|
|
};
|
|
|
|
class FWaitingCommand : public FDelayedCommand
|
|
{
|
|
public:
|
|
FWaitingCommand(const char *cmd, int tics, bool unsafe)
|
|
: Command(cmd), TicsLeft(tics+1), IsUnsafe(unsafe)
|
|
{}
|
|
|
|
bool Tick() override
|
|
{
|
|
if (--TicsLeft == 0)
|
|
{
|
|
UnsafeExecutionScope scope(IsUnsafe);
|
|
AddCommandString(Command);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
FString Command;
|
|
int TicsLeft;
|
|
bool IsUnsafe;
|
|
};
|
|
|
|
class FStoredCommand : public FDelayedCommand
|
|
{
|
|
public:
|
|
FStoredCommand(FConsoleCommand *com, const char *cmd)
|
|
: Command(com), Text(cmd)
|
|
{}
|
|
|
|
bool Tick() override
|
|
{
|
|
if (Text.IsNotEmpty() && Command != nullptr)
|
|
{
|
|
FCommandLine args(Text);
|
|
Command->Run(args, 0);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
|
|
FConsoleCommand *Command;
|
|
FString Text;
|
|
};
|
|
|
|
class FDelayedCommandQueue
|
|
{
|
|
TDeletingArray<FDelayedCommand *> delayedCommands;
|
|
public:
|
|
void Run()
|
|
{
|
|
for (unsigned i = 0; i < delayedCommands.Size(); i++)
|
|
{
|
|
if (delayedCommands[i]->Tick())
|
|
{
|
|
delete delayedCommands[i];
|
|
delayedCommands.Delete(i);
|
|
i--;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Clear()
|
|
{
|
|
delayedCommands.DeleteAndClear();
|
|
}
|
|
|
|
void AddCommand(FDelayedCommand * cmd)
|
|
{
|
|
delayedCommands.Push(cmd);
|
|
}
|
|
};
|
|
|
|
static FDelayedCommandQueue delayedCommandQueue;
|
|
|
|
void C_RunDelayedCommands()
|
|
{
|
|
delayedCommandQueue.Run();
|
|
}
|
|
|
|
void C_ClearDelayedCommands()
|
|
{
|
|
delayedCommandQueue.Clear();
|
|
}
|
|
|
|
|
|
|
|
// EXTERNAL FUNCTION PROTOTYPES --------------------------------------------
|
|
|
|
// PUBLIC FUNCTION PROTOTYPES ----------------------------------------------
|
|
|
|
// PRIVATE FUNCTION PROTOTYPES ---------------------------------------------
|
|
|
|
static FConsoleCommand *FindNameInHashTable (FConsoleCommand **table, const char *name, size_t namelen);
|
|
static FConsoleCommand *ScanChainForName (FConsoleCommand *start, const char *name, size_t namelen, FConsoleCommand **prev);
|
|
|
|
// EXTERNAL DATA DECLARATIONS ----------------------------------------------
|
|
|
|
// PUBLIC DATA DEFINITIONS -------------------------------------------------
|
|
bool ParsingKeyConf, UnsafeExecutionContext;
|
|
FString StoredWarp;
|
|
|
|
FConsoleCommand* Commands[FConsoleCommand::HASH_SIZE];
|
|
|
|
|
|
// PRIVATE DATA DEFINITIONS ------------------------------------------------
|
|
|
|
static const char *KeyConfCommands[] =
|
|
{
|
|
"alias",
|
|
"defaultbind",
|
|
"addkeysection",
|
|
"addmenukey",
|
|
"addslotdefault",
|
|
"weaponsection",
|
|
"setslot",
|
|
"addplayerclass",
|
|
"clearplayerclasses"
|
|
};
|
|
|
|
// CODE --------------------------------------------------------------------
|
|
|
|
void C_DoCommand (const char *cmd, int keynum)
|
|
{
|
|
FConsoleCommand *com;
|
|
const char *end;
|
|
const char *beg;
|
|
|
|
// Skip any beginning whitespace
|
|
while (*cmd > 0 && *cmd <= ' ')
|
|
cmd++;
|
|
|
|
// Find end of the command name
|
|
if (*cmd == '\"')
|
|
{
|
|
for (end = beg = cmd+1; *end && *end != '\"'; ++end)
|
|
;
|
|
}
|
|
else
|
|
{
|
|
beg = cmd;
|
|
for (end = cmd+1; *end > ' ' || *end < 0; ++end)
|
|
;
|
|
}
|
|
|
|
const size_t len = end - beg;
|
|
|
|
if (ParsingKeyConf)
|
|
{
|
|
int i;
|
|
|
|
for (i = countof(KeyConfCommands)-1; i >= 0; --i)
|
|
{
|
|
if (strnicmp (beg, KeyConfCommands[i], len) == 0 &&
|
|
KeyConfCommands[i][len] == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
if (i < 0)
|
|
{
|
|
Printf ("Invalid command for KEYCONF: %s\n", beg);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Check if this is an action
|
|
if (*beg == '+' || *beg == '-')
|
|
{
|
|
auto button = buttonMap.FindButton(beg + 1, int(end - beg - 1));
|
|
if (button != nullptr)
|
|
{
|
|
if (*beg == '+')
|
|
{
|
|
button->PressKey (keynum);
|
|
if (button->PressHandler) button->PressHandler();
|
|
}
|
|
else
|
|
{
|
|
button->ReleaseKey (keynum);
|
|
if (button->ReleaseHandler) button->ReleaseHandler();
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Parse it as a normal command
|
|
// Checking for matching commands follows this search order:
|
|
// 1. Check the Commands[] hash table
|
|
// 2. Check the CVars list
|
|
|
|
if ( (com = FindNameInHashTable (Commands, beg, len)) )
|
|
{
|
|
if (gamestate != GS_STARTUP || ParsingKeyConf ||
|
|
(len == 3 && strnicmp (beg, "set", 3) == 0) ||
|
|
(len == 7 && strnicmp (beg, "logfile", 7) == 0) ||
|
|
(len == 9 && strnicmp (beg, "unbindall", 9) == 0) ||
|
|
(len == 4 && strnicmp (beg, "bind", 4) == 0) ||
|
|
(len == 4 && strnicmp (beg, "exec", 4) == 0) ||
|
|
(len ==10 && strnicmp (beg, "doublebind", 10) == 0) ||
|
|
(len == 6 && strnicmp (beg, "pullin", 6) == 0)
|
|
)
|
|
{
|
|
FCommandLine args (beg);
|
|
com->Run (args, keynum);
|
|
}
|
|
else
|
|
{
|
|
if (len == 4 && strnicmp(beg, "warp", 4) == 0)
|
|
{
|
|
StoredWarp = beg;
|
|
}
|
|
else
|
|
{
|
|
auto command = new FStoredCommand(com, beg);
|
|
delayedCommandQueue.AddCommand(command);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{ // Check for any console vars that match the command
|
|
FBaseCVar *var = FindCVarSub (beg, int(len));
|
|
|
|
if (var != NULL)
|
|
{
|
|
FCommandLine args (beg);
|
|
|
|
if (args.argc() >= 2)
|
|
{ // Set the variable
|
|
var->CmdSet (args[1]);
|
|
}
|
|
else
|
|
{ // Get the variable's value
|
|
if (var->GetDescription().Len()) Printf("%s\n", GStrings.localize(var->GetDescription()));
|
|
Printf ("\"%s\" is \"%s\" ", var->GetName(), var->GetHumanString());
|
|
Printf ("(default: \"%s\")\n", var->GetHumanStringDefault());
|
|
}
|
|
}
|
|
else
|
|
{ // We don't know how to handle this command
|
|
Printf ("Unknown command \"%.*s\"\n", (int)len, beg);
|
|
}
|
|
}
|
|
}
|
|
|
|
void AddCommandString (const char *text, int keynum)
|
|
{
|
|
// Operate on a local copy instead of messing around with the data that's being passed in here.
|
|
TArray<char> buffer(strlen(text) + 1, true);
|
|
memcpy(buffer.Data(), text, buffer.Size());
|
|
char *cmd = buffer.Data();
|
|
char *brkpt;
|
|
int more;
|
|
|
|
if (cmd)
|
|
{
|
|
while (*cmd)
|
|
{
|
|
brkpt = cmd;
|
|
while (*brkpt != ';' && *brkpt != '\0')
|
|
{
|
|
if (*brkpt == '\"')
|
|
{
|
|
brkpt++;
|
|
while (*brkpt != '\0' && (*brkpt != '\"' || *(brkpt-1) == '\\'))
|
|
brkpt++;
|
|
}
|
|
if (*brkpt != '\0')
|
|
brkpt++;
|
|
}
|
|
if (*brkpt == ';')
|
|
{
|
|
*brkpt = '\0';
|
|
more = 1;
|
|
}
|
|
else
|
|
{
|
|
more = 0;
|
|
}
|
|
// Intercept wait commands here. Note: wait must be lowercase
|
|
while (*cmd > 0 && *cmd <= ' ')
|
|
cmd++;
|
|
if (*cmd)
|
|
{
|
|
if (!ParsingKeyConf &&
|
|
cmd[0] == 'w' && cmd[1] == 'a' && cmd[2] == 'i' && cmd[3] == 't' &&
|
|
(cmd[4] == 0 || cmd[4] == ' '))
|
|
{
|
|
int tics;
|
|
|
|
if (cmd[4] == ' ')
|
|
{
|
|
tics = (int)strtoll (cmd + 5, NULL, 0);
|
|
}
|
|
else
|
|
{
|
|
tics = 1;
|
|
}
|
|
if (tics > 0)
|
|
{
|
|
if (more)
|
|
{ // The remainder of the command will be executed later
|
|
// Note that deferred commands lose track of which key
|
|
// (if any) they were pressed from.
|
|
*brkpt = ';';
|
|
auto command = new FWaitingCommand(brkpt, tics, UnsafeExecutionContext);
|
|
delayedCommandQueue.AddCommand(command);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
C_DoCommand (cmd, keynum);
|
|
}
|
|
}
|
|
if (more)
|
|
{
|
|
*brkpt = ';';
|
|
}
|
|
cmd = brkpt + more;
|
|
}
|
|
}
|
|
}
|
|
|
|
static FConsoleCommand *ScanChainForName (FConsoleCommand *start, const char *name, size_t namelen, FConsoleCommand **prev)
|
|
{
|
|
int comp;
|
|
|
|
*prev = NULL;
|
|
while (start)
|
|
{
|
|
comp = strnicmp (start->m_Name, name, namelen);
|
|
if (comp > 0)
|
|
return NULL;
|
|
else if (comp == 0 && start->m_Name[namelen] == 0)
|
|
return start;
|
|
|
|
*prev = start;
|
|
start = start->m_Next;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static FConsoleCommand *FindNameInHashTable (FConsoleCommand **table, const char *name, size_t namelen)
|
|
{
|
|
FConsoleCommand *dummy;
|
|
|
|
return ScanChainForName (table[MakeKey (name, namelen) % FConsoleCommand::HASH_SIZE], name, namelen, &dummy);
|
|
}
|
|
|
|
bool FConsoleCommand::AddToHash (FConsoleCommand **table)
|
|
{
|
|
unsigned int key;
|
|
FConsoleCommand *insert, **bucket;
|
|
|
|
key = MakeKey (m_Name);
|
|
bucket = &table[key % HASH_SIZE];
|
|
|
|
if (ScanChainForName (*bucket, m_Name, strlen (m_Name), &insert))
|
|
{
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (insert)
|
|
{
|
|
m_Next = insert->m_Next;
|
|
if (m_Next)
|
|
m_Next->m_Prev = &m_Next;
|
|
insert->m_Next = this;
|
|
m_Prev = &insert->m_Next;
|
|
}
|
|
else
|
|
{
|
|
m_Next = *bucket;
|
|
*bucket = this;
|
|
m_Prev = bucket;
|
|
if (m_Next)
|
|
m_Next->m_Prev = &m_Next;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
FConsoleCommand* FConsoleCommand::FindByName (const char* name)
|
|
{
|
|
return FindNameInHashTable (Commands, name, strlen (name));
|
|
}
|
|
|
|
FConsoleCommand::FConsoleCommand (const char *name, CCmdRun runFunc)
|
|
: m_RunFunc (runFunc)
|
|
{
|
|
int ag = strcmp (name, "kill");
|
|
if (ag == 0)
|
|
ag=0;
|
|
m_Name = name;
|
|
|
|
if (!AddToHash (Commands))
|
|
Printf ("Adding CCMD %s twice.\n", name);
|
|
else
|
|
C_AddTabCommand (name);
|
|
}
|
|
|
|
FConsoleCommand::~FConsoleCommand ()
|
|
{
|
|
*m_Prev = m_Next;
|
|
if (m_Next)
|
|
m_Next->m_Prev = m_Prev;
|
|
C_RemoveTabCommand (m_Name);
|
|
}
|
|
|
|
void FConsoleCommand::Run(FCommandLine &argv, int key)
|
|
{
|
|
m_RunFunc (argv, key);
|
|
}
|
|
|
|
void FUnsafeConsoleCommand::Run(FCommandLine &args, int key)
|
|
{
|
|
if (UnsafeExecutionContext)
|
|
{
|
|
Printf(TEXTCOLOR_RED "Cannot execute unsafe command " TEXTCOLOR_GOLD "%s\n", m_Name.GetChars());
|
|
return;
|
|
}
|
|
|
|
FConsoleCommand::Run (args, key);
|
|
}
|
|
|
|
FConsoleAlias::FConsoleAlias (const char *name, const char *command, bool noSave)
|
|
: FConsoleCommand (name, NULL),
|
|
bRunning(false), bKill(false)
|
|
{
|
|
m_Command[noSave] = command;
|
|
m_Command[!noSave] = FString();
|
|
// If the command contains % characters, assume they are parameter markers
|
|
// for substitution when the command is executed.
|
|
bDoSubstitution = (strchr (command, '%') != NULL);
|
|
}
|
|
|
|
FConsoleAlias::~FConsoleAlias ()
|
|
{
|
|
m_Command[1] = m_Command[0] = FString();
|
|
}
|
|
|
|
// Given an argument vector, reconstitute the command line it could have been produced from.
|
|
FString BuildString (int argc, FString *argv)
|
|
{
|
|
if (argc == 1)
|
|
{
|
|
return *argv;
|
|
}
|
|
else
|
|
{
|
|
FString buf;
|
|
int arg;
|
|
|
|
for (arg = 0; arg < argc; arg++)
|
|
{
|
|
if (argv[arg][0] == '\0')
|
|
{ // It's an empty argument, we need to convert it to '""'
|
|
buf << "\"\" ";
|
|
}
|
|
else if (strchr(argv[arg], '"'))
|
|
{ // If it contains one or more quotes, we need to escape them.
|
|
buf << '"';
|
|
ptrdiff_t substr_start = 0, quotepos;
|
|
while ((quotepos = argv[arg].IndexOf('"', substr_start)) >= 0)
|
|
{
|
|
if (substr_start < quotepos)
|
|
{
|
|
buf << argv[arg].Mid(substr_start, quotepos - substr_start);
|
|
}
|
|
buf << "\\\"";
|
|
substr_start = quotepos + 1;
|
|
}
|
|
buf << argv[arg].Mid(substr_start) << "\" ";
|
|
}
|
|
else if (strchr(argv[arg], ' '))
|
|
{ // If it contains a space, it needs to be quoted.
|
|
buf << '"' << argv[arg] << "\" ";
|
|
}
|
|
else
|
|
{
|
|
buf << argv[arg] << ' ';
|
|
}
|
|
}
|
|
return buf;
|
|
}
|
|
}
|
|
|
|
//===========================================================================
|
|
//
|
|
// SubstituteAliasParams
|
|
//
|
|
// Given an command line and a set of arguments, replace instances of
|
|
// %x or %{x} in the command line with argument x. If argument x does not
|
|
// exist, then the empty string is substituted in its place.
|
|
//
|
|
// Substitution is not done inside of quoted strings, unless that string is
|
|
// prepended with a % character.
|
|
//
|
|
// To avoid a substitution, use %%. The %% will be replaced by a single %.
|
|
//
|
|
//===========================================================================
|
|
|
|
void FConsoleCommand::PrintCommand()
|
|
{
|
|
Printf("%s\n", m_Name.GetChars());
|
|
}
|
|
|
|
FString SubstituteAliasParams (FString &command, FCommandLine &args)
|
|
{
|
|
// Do substitution by replacing %x with the argument x.
|
|
// If there is no argument x, then %x is simply removed.
|
|
|
|
// For some reason, strtoul's stop parameter is non-const.
|
|
char *p = command.LockBuffer(), *start = p;
|
|
unsigned long argnum;
|
|
FString buf;
|
|
bool inquote = false;
|
|
|
|
while (*p != '\0')
|
|
{
|
|
if (p[0] == '%' && ((p[1] >= '0' && p[1] <= '9') || p[1] == '{'))
|
|
{
|
|
// Do a substitution. Output what came before this.
|
|
buf.AppendCStrPart (start, p - start);
|
|
|
|
// Extract the argument number and substitute the corresponding argument.
|
|
argnum = strtoul (p + 1 + (p[1] == '{'), &start, 10);
|
|
if ((p[1] != '{' || *start == '}') && argnum < (unsigned long)args.argc())
|
|
{
|
|
buf += args[argnum];
|
|
}
|
|
p = (start += (p[1] == '{' && *start == '}'));
|
|
}
|
|
else if (p[0] == '%' && p[1] == '%')
|
|
{
|
|
// Do not substitute. Just collapse to a single %.
|
|
buf.AppendCStrPart (start, p - start + 1);
|
|
start = p = p + 2;
|
|
continue;
|
|
}
|
|
else if (p[0] == '%' && p[1] == '"')
|
|
{
|
|
// Collapse %" to " and remember that we're in a quote so when we
|
|
// see a " character again, we don't start skipping below.
|
|
if (!inquote)
|
|
{
|
|
inquote = true;
|
|
buf.AppendCStrPart(start, p - start);
|
|
start = p + 1;
|
|
}
|
|
else
|
|
{
|
|
inquote = false;
|
|
}
|
|
p += 2;
|
|
}
|
|
else if (p[0] == '\\' && p[1] == '"')
|
|
{
|
|
p += 2;
|
|
}
|
|
else if (p[0] == '"')
|
|
{
|
|
// Don't substitute inside quoted strings if it didn't start
|
|
// with a %"
|
|
if (!inquote)
|
|
{
|
|
p++;
|
|
while (*p != '\0' && (*p != '"' || *(p-1) == '\\'))
|
|
p++;
|
|
if (*p != '\0')
|
|
p++;
|
|
}
|
|
else
|
|
{
|
|
inquote = false;
|
|
p++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
p++;
|
|
}
|
|
}
|
|
// Return whatever was after the final substitution.
|
|
if (p > start)
|
|
{
|
|
buf.AppendCStrPart (start, p - start);
|
|
}
|
|
command.UnlockBuffer();
|
|
|
|
return buf;
|
|
}
|
|
|
|
static int DumpHash (FConsoleCommand **table, bool aliases, const char *pattern=NULL)
|
|
{
|
|
int bucket, count;
|
|
FConsoleCommand *cmd;
|
|
|
|
for (bucket = count = 0; bucket < FConsoleCommand::HASH_SIZE; bucket++)
|
|
{
|
|
cmd = table[bucket];
|
|
while (cmd)
|
|
{
|
|
if (CheckWildcards (pattern, cmd->m_Name))
|
|
{
|
|
if (cmd->IsAlias())
|
|
{
|
|
if (aliases)
|
|
{
|
|
++count;
|
|
static_cast<FConsoleAlias *>(cmd)->PrintAlias ();
|
|
}
|
|
}
|
|
else if (!aliases)
|
|
{
|
|
++count;
|
|
cmd->PrintCommand ();
|
|
}
|
|
}
|
|
cmd = cmd->m_Next;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
void FConsoleAlias::PrintAlias ()
|
|
{
|
|
if (m_Command[0].IsNotEmpty())
|
|
{
|
|
Printf (TEXTCOLOR_YELLOW "%s : %s\n", m_Name.GetChars(), m_Command[0].GetChars());
|
|
}
|
|
if (m_Command[1].IsNotEmpty())
|
|
{
|
|
Printf (TEXTCOLOR_ORANGE "%s : %s\n", m_Name.GetChars(), m_Command[1].GetChars());
|
|
}
|
|
}
|
|
|
|
void FConsoleAlias::Archive (FConfigFile *f)
|
|
{
|
|
if (f != NULL && !m_Command[0].IsEmpty())
|
|
{
|
|
f->SetValueForKey ("Name", m_Name, true);
|
|
f->SetValueForKey ("Command", m_Command[0], true);
|
|
}
|
|
}
|
|
|
|
void C_ArchiveAliases (FConfigFile *f)
|
|
{
|
|
int bucket;
|
|
FConsoleCommand *alias;
|
|
|
|
for (bucket = 0; bucket < FConsoleCommand::HASH_SIZE; bucket++)
|
|
{
|
|
alias = Commands[bucket];
|
|
while (alias)
|
|
{
|
|
if (alias->IsAlias())
|
|
static_cast<FConsoleAlias *>(alias)->Archive (f);
|
|
alias = alias->m_Next;
|
|
}
|
|
}
|
|
}
|
|
|
|
void C_ClearAliases ()
|
|
{
|
|
int bucket;
|
|
FConsoleCommand *alias;
|
|
|
|
for (bucket = 0; bucket < FConsoleCommand::HASH_SIZE; bucket++)
|
|
{
|
|
alias = Commands[bucket];
|
|
while (alias)
|
|
{
|
|
FConsoleCommand *next = alias->m_Next;
|
|
if (alias->IsAlias())
|
|
static_cast<FConsoleAlias *>(alias)->SafeDelete();
|
|
alias = next;
|
|
}
|
|
}
|
|
}
|
|
|
|
CCMD(clearaliases)
|
|
{
|
|
C_ClearAliases();
|
|
}
|
|
|
|
|
|
// This is called only by the ini parser.
|
|
void C_SetAlias (const char *name, const char *cmd)
|
|
{
|
|
FConsoleCommand *prev, *alias, **chain;
|
|
|
|
chain = &Commands[MakeKey (name) % FConsoleCommand::HASH_SIZE];
|
|
alias = ScanChainForName (*chain, name, strlen (name), &prev);
|
|
if (alias != NULL)
|
|
{
|
|
if (!alias->IsAlias ())
|
|
{
|
|
//Printf (PRINT_BOLD, "%s is a command and cannot be an alias.\n", name);
|
|
return;
|
|
}
|
|
delete alias;
|
|
}
|
|
new FConsoleAlias (name, cmd, false);
|
|
}
|
|
|
|
CCMD (alias)
|
|
{
|
|
FConsoleCommand *prev, *alias, **chain;
|
|
|
|
if (argv.argc() == 1)
|
|
{
|
|
Printf ("Current alias commands:\n");
|
|
DumpHash (Commands, true);
|
|
}
|
|
else
|
|
{
|
|
chain = &Commands[MakeKey (argv[1]) % FConsoleCommand::HASH_SIZE];
|
|
|
|
if (argv.argc() == 2)
|
|
{ // Remove the alias
|
|
|
|
if ( (alias = ScanChainForName (*chain, argv[1], strlen (argv[1]), &prev)))
|
|
{
|
|
if (alias->IsAlias ())
|
|
{
|
|
static_cast<FConsoleAlias *> (alias)->SafeDelete ();
|
|
}
|
|
else
|
|
{
|
|
Printf ("%s is a normal command\n", alias->m_Name.GetChars());
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{ // Add/change the alias
|
|
|
|
alias = ScanChainForName (*chain, argv[1], strlen (argv[1]), &prev);
|
|
if (alias != NULL)
|
|
{
|
|
if (alias->IsAlias ())
|
|
{
|
|
static_cast<FConsoleAlias *> (alias)->Realias (argv[2], ParsingKeyConf);
|
|
}
|
|
else
|
|
{
|
|
Printf ("%s is a normal command\n", alias->m_Name.GetChars());
|
|
alias = NULL;
|
|
}
|
|
}
|
|
else if (ParsingKeyConf)
|
|
{
|
|
new FUnsafeConsoleAlias (argv[1], argv[2]);
|
|
}
|
|
else
|
|
{
|
|
new FConsoleAlias (argv[1], argv[2], false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
CCMD (cmdlist)
|
|
{
|
|
int count;
|
|
const char *filter = (argv.argc() == 1 ? NULL : argv[1]);
|
|
|
|
count = buttonMap.ListActionCommands (filter);
|
|
count += DumpHash (Commands, false, filter);
|
|
Printf ("%d commands\n", count);
|
|
}
|
|
|
|
CCMD (key)
|
|
{
|
|
if (argv.argc() > 1)
|
|
{
|
|
int i;
|
|
|
|
for (i = 1; i < argv.argc(); ++i)
|
|
{
|
|
unsigned int hash = MakeKey (argv[i]);
|
|
Printf (" 0x%08x\n", hash);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Execute any console commands specified on the command line.
|
|
// These all begin with '+' as opposed to '-'.
|
|
FExecList *C_ParseCmdLineParams(FExecList *exec)
|
|
{
|
|
for (int currArg = 1; currArg < Args->NumArgs(); )
|
|
{
|
|
if (*Args->GetArg (currArg++) == '+')
|
|
{
|
|
FString cmdString;
|
|
int cmdlen = 1;
|
|
int argstart = currArg - 1;
|
|
|
|
while (currArg < Args->NumArgs())
|
|
{
|
|
if (*Args->GetArg (currArg) == '-' || *Args->GetArg (currArg) == '+')
|
|
break;
|
|
currArg++;
|
|
cmdlen++;
|
|
}
|
|
|
|
cmdString = BuildString (cmdlen, Args->GetArgList (argstart));
|
|
if (!cmdString.IsEmpty())
|
|
{
|
|
if (exec == NULL)
|
|
{
|
|
exec = new FExecList;
|
|
}
|
|
exec->AddCommand(&cmdString[1]);
|
|
}
|
|
}
|
|
}
|
|
return exec;
|
|
}
|
|
|
|
bool FConsoleCommand::IsAlias ()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool FConsoleAlias::IsAlias ()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void FConsoleAlias::Run (FCommandLine &args, int key)
|
|
{
|
|
if (bRunning)
|
|
{
|
|
Printf ("Alias %s tried to recurse.\n", m_Name.GetChars());
|
|
return;
|
|
}
|
|
|
|
int index = !m_Command[1].IsEmpty();
|
|
FString savedcommand = m_Command[index], mycommand;
|
|
m_Command[index] = FString();
|
|
|
|
if (bDoSubstitution)
|
|
{
|
|
mycommand = SubstituteAliasParams (savedcommand, args);
|
|
}
|
|
else
|
|
{
|
|
mycommand = savedcommand;
|
|
}
|
|
|
|
bRunning = true;
|
|
AddCommandString (mycommand, key);
|
|
bRunning = false;
|
|
if (m_Command[index].IsEmpty())
|
|
{ // The alias is unchanged, so put the command back so it can be used again.
|
|
// If the command had been non-empty, then that means that executing this
|
|
// alias caused it to realias itself, so the old command will be forgotten
|
|
// once this function returns.
|
|
m_Command[index] = savedcommand;
|
|
}
|
|
if (bKill)
|
|
{ // The alias wants to remove itself
|
|
delete this;
|
|
}
|
|
}
|
|
|
|
void FConsoleAlias::Realias (const char *command, bool noSave)
|
|
{
|
|
if (!noSave && !m_Command[1].IsEmpty())
|
|
{
|
|
noSave = true;
|
|
}
|
|
m_Command[noSave] = command;
|
|
|
|
// If the command contains % characters, assume they are parameter markers
|
|
// for substitution when the command is executed.
|
|
bDoSubstitution = (strchr (command, '%') != NULL);
|
|
bKill = false;
|
|
}
|
|
|
|
void FConsoleAlias::SafeDelete ()
|
|
{
|
|
if (!bRunning)
|
|
{
|
|
delete this;
|
|
}
|
|
else
|
|
{
|
|
bKill = true;
|
|
}
|
|
}
|
|
|
|
void FUnsafeConsoleAlias::Run (FCommandLine &args, int key)
|
|
{
|
|
UnsafeExecutionScope scope;
|
|
FConsoleAlias::Run(args, key);
|
|
}
|
|
|
|
void FExecList::AddCommand(const char *cmd, const char *file)
|
|
{
|
|
// Pullins are special and need to be separated from general commands.
|
|
// They also turned out to be a really bad idea, since they make things
|
|
// more complicated. :(
|
|
if (file != NULL && strnicmp(cmd, "pullin", 6) == 0 && isspace(cmd[6]))
|
|
{
|
|
FCommandLine line(cmd);
|
|
C_SearchForPullins(this, file, line);
|
|
}
|
|
// Recursive exec: Parse this file now.
|
|
else if (strnicmp(cmd, "exec", 4) == 0 && isspace(cmd[4]))
|
|
{
|
|
FCommandLine argv(cmd);
|
|
for (int i = 1; i < argv.argc(); ++i)
|
|
{
|
|
C_ParseExecFile(argv[i], this);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Commands.Push(cmd);
|
|
}
|
|
}
|
|
|
|
void FExecList::ExecCommands() const
|
|
{
|
|
for (unsigned i = 0; i < Commands.Size(); ++i)
|
|
{
|
|
AddCommandString(Commands[i]);
|
|
}
|
|
}
|
|
|
|
void FExecList::AddPullins(TArray<FString> &wads, FConfigFile *config) const
|
|
{
|
|
for (unsigned i = 0; i < Pullins.Size(); ++i)
|
|
{
|
|
D_AddFile(wads, Pullins[i], true, -1, config);
|
|
}
|
|
}
|
|
|
|
FExecList *C_ParseExecFile(const char *file, FExecList *exec)
|
|
{
|
|
char cmd[4096];
|
|
|
|
FileReader fr;
|
|
|
|
if ( (fr.OpenFile(file)) )
|
|
{
|
|
while (fr.Gets(cmd, countof(cmd)-1))
|
|
{
|
|
// Comments begin with //
|
|
char *stop = cmd + strlen(cmd) - 1;
|
|
char *comment = cmd;
|
|
int inQuote = 0;
|
|
|
|
if (*stop == '\n')
|
|
*stop-- = 0;
|
|
|
|
while (comment < stop)
|
|
{
|
|
if (*comment == '\"')
|
|
{
|
|
inQuote ^= 1;
|
|
}
|
|
else if (!inQuote && *comment == '/' && *(comment + 1) == '/')
|
|
{
|
|
break;
|
|
}
|
|
comment++;
|
|
}
|
|
if (comment == cmd)
|
|
{ // Comment at line beginning
|
|
continue;
|
|
}
|
|
else if (comment < stop)
|
|
{ // Comment in middle of line
|
|
*comment = 0;
|
|
}
|
|
if (exec == NULL)
|
|
{
|
|
exec = new FExecList;
|
|
}
|
|
exec->AddCommand(cmd, file);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Printf ("Could not open \"%s\"\n", file);
|
|
}
|
|
return exec;
|
|
}
|
|
|
|
bool C_ExecFile (const char *file)
|
|
{
|
|
FExecList *exec = C_ParseExecFile(file, NULL);
|
|
if (exec != NULL)
|
|
{
|
|
exec->ExecCommands();
|
|
if (exec->Pullins.Size() > 0)
|
|
{
|
|
Printf(TEXTCOLOR_BOLD "Notice: Pullin files were ignored.\n");
|
|
}
|
|
delete exec;
|
|
}
|
|
return exec != NULL;
|
|
}
|
|
|
|
void C_SearchForPullins(FExecList *exec, const char *file, FCommandLine &argv)
|
|
{
|
|
const char *lastSlash;
|
|
|
|
assert(exec != NULL);
|
|
assert(file != NULL);
|
|
#ifdef __unix__
|
|
lastSlash = strrchr(file, '/');
|
|
#else
|
|
const char *lastSlash1, *lastSlash2;
|
|
|
|
lastSlash1 = strrchr(file, '/');
|
|
lastSlash2 = strrchr(file, '\\');
|
|
lastSlash = max(lastSlash1, lastSlash2);
|
|
#endif
|
|
|
|
for (int i = 1; i < argv.argc(); ++i)
|
|
{
|
|
// Try looking for the wad in the same directory as the .cfg
|
|
// before looking for it in the current directory.
|
|
if (lastSlash != NULL)
|
|
{
|
|
FString path(file, (lastSlash - file) + 1);
|
|
path += argv[i];
|
|
if (FileExists(path))
|
|
{
|
|
exec->Pullins.Push(path);
|
|
continue;
|
|
}
|
|
}
|
|
exec->Pullins.Push(argv[i]);
|
|
}
|
|
}
|
|
|
|
static TArray<FConsoleCommand*> dynccmds; // This needs to be explicitly deleted before shutdown - the names in here may not be valid during the exit handler.
|
|
//
|
|
// C_RegisterFunction() -- dynamically register a CCMD.
|
|
//
|
|
int C_RegisterFunction(const char* pszName, const char* pszDesc, int (*func)(CCmdFuncPtr))
|
|
{
|
|
FString nname = pszName;
|
|
auto callback = [nname, pszDesc, func](FCommandLine& args, int key)
|
|
{
|
|
if (args.argc() > 0) args.operator[](0);
|
|
CCmdFuncParm param = { args.argc() - 1, nname.GetChars(), (const char**)args._argv + 1, args.cmd };
|
|
if (func(¶m) != CCMD_OK && pszDesc)
|
|
{
|
|
Printf("%s\n", pszDesc);
|
|
}
|
|
};
|
|
auto ccmd = new FConsoleCommand(pszName, callback);
|
|
dynccmds.Push(ccmd);
|
|
return 0;
|
|
}
|
|
|
|
|
|
void C_ClearDynCCmds()
|
|
{
|
|
for (auto ccmd : dynccmds)
|
|
{
|
|
delete ccmd;
|
|
}
|
|
dynccmds.Clear();
|
|
}
|
|
|
|
CCMD (pullin)
|
|
{
|
|
// Actual handling for pullin is now completely special-cased above
|
|
Printf (TEXTCOLOR_BOLD "Pullin" TEXTCOLOR_NORMAL " is only valid from .cfg\n"
|
|
"files and only when used at startup.\n");
|
|
}
|
|
|