This repository has been archived on 2022-06-27. You can view files and clone it, but cannot push or open issues or pull requests.
Xash3DArchive/launch/common/cmd.c

881 lines
20 KiB
C

/*
Copyright (C) 1997-2001 Id Software, Inc.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
// cmd.c -- Quake script command processing module
#include "engine.h"
void Cmd_ForwardToServer (void);
//=============================================================================
#define MAX_CMD_BUFFER 16384
#define MAX_CMD_LINE 1024
typedef struct
{
byte *data;
int maxsize;
int cursize;
} cmd_t;
int cmd_wait;
cmd_t cmd_text;
byte cmd_text_buf[MAX_CMD_BUFFER];
char **fs_argv;
int fs_argc;
/*
=============================================================================
COMMAND BUFFER
=============================================================================
*/
/*
============
Cbuf_Init
============
*/
void Cbuf_Init (int argc, char **argv )
{
cmd_text.data = cmd_text_buf;
cmd_text.maxsize = MAX_CMD_BUFFER;
cmd_text.cursize = 0;
fs_argc = argc;
fs_argv = argv;
}
/*
============
Cbuf_AddText
Adds command text at the end of the buffer
============
*/
void Cbuf_AddText(const char *text)
{
int len;
len = strlen(text);
if (cmd_text.cursize + len >= cmd_text.maxsize)
{
MsgDev(D_WARN, "Cbuf_AddText: overflow\n");
return;
}
Mem_Copy(&cmd_text.data[cmd_text.cursize], (char *)text, len);
cmd_text.cursize += len;
}
/*
============
Cbuf_InsertText
Adds command text immediately after the current command
Adds a \n to the text
FIXME: actually change the command buffer to do less copying
============
*/
void Cbuf_InsertText (const char *text)
{
int i, len;
len = strlen( text ) + 1;
if ( len + cmd_text.cursize > cmd_text.maxsize )
{
MsgDev(D_WARN,"Cbuf_InsertText overflowed\n" );
return;
}
// move the existing command text
for( i = cmd_text.cursize - 1; i >= 0; i-- )
{
cmd_text.data[i + len] = cmd_text.data[i];
}
// copy the new text in
Mem_Copy( cmd_text.data, (char *)text, len - 1 );
cmd_text.data[ len - 1 ] = '\n'; // add a \n
cmd_text.cursize += len;
}
/*
============
Cbuf_ExecuteText
============
*/
void Cbuf_ExecuteText (int exec_when, const char *text)
{
switch (exec_when)
{
case EXEC_NOW:
if (text && strlen(text))
Cmd_ExecuteString(text);
else Cbuf_Execute();
break;
case EXEC_INSERT:
Cbuf_InsertText (text);
break;
case EXEC_APPEND:
Cbuf_AddText (text);
break;
default:
MsgWarn("Cbuf_ExecuteText: bad execute target\n");
break;
}
}
/*
============
Cbuf_Execute
============
*/
void Cbuf_Execute( void )
{
int i;
char *text;
char line[MAX_CMD_LINE];
int quotes;
while (cmd_text.cursize)
{
if( cmd_wait )
{
// skip out while text still remains in buffer, leaving it for next frame
cmd_wait--;
break;
}
// find a \n or ; line break
text = (char *)cmd_text.data;
quotes = 0;
for (i = 0; i < cmd_text.cursize; i++)
{
if (text[i] == '"') quotes++;
if ( !(quotes&1) && text[i] == ';')
break; // don't break if inside a quoted string
if (text[i] == '\n' || text[i] == '\r' ) break;
}
if( i >= (MAX_CMD_LINE - 1)) i = MAX_CMD_LINE - 1;
Mem_Copy (line, text, i);
line[i] = 0;
// delete the text from the command buffer and move remaining commands down
// this is necessary because commands (exec) can insert data at the
// beginning of the text buffer
if (i == cmd_text.cursize) cmd_text.cursize = 0;
else
{
i++;
cmd_text.cursize -= i;
memmove (text, text+i, cmd_text.cursize);
}
// execute the command line
Cmd_ExecuteString (line);
}
}
/*
==============================================================================
SCRIPT COMMANDS
==============================================================================
*/
/*
===============
Cmd_StuffCmds_f
Adds command line parameters as script statements
Commands lead with a +, and continue until a - or another +
quake +prog jctest.qp +cmd amlev1
quake -nosound +cmd amlev1
===============
*/
void Cmd_StuffCmds_f( void )
{
int i, j, l = 0;
char build[MAX_INPUTLINE]; // this is for all commandline options combined (and is bounds checked)
if(Cmd_Argc() != 1)
{
Msg("stuffcmds : execute command line parameters\n");
return;
}
// no reason to run the commandline arguments twice
if(host.stuffcmdsrun) return;
host.stuffcmdsrun = true;
build[0] = 0;
for (i = 0; i < fs_argc; i++)
{
if (fs_argv[i] && fs_argv[i][0] == '+' && (fs_argv[i][1] < '0' || fs_argv[i][1] > '9') && l + strlen(fs_argv[i]) - 1 <= sizeof(build) - 1)
{
j = 1;
while (fs_argv[i][j]) build[l++] = fs_argv[i][j++];
i++;
for ( ; i < fs_argc; i++)
{
if (!fs_argv[i]) continue;
if ((fs_argv[i][0] == '+' || fs_argv[i][0] == '-') && (fs_argv[i][1] < '0' || fs_argv[i][1] > '9'))
break;
if (l + strlen(fs_argv[i]) + 4 > sizeof(build) - 1)
break;
build[l++] = ' ';
if (strchr(fs_argv[i], ' ')) build[l++] = '\"';
for (j = 0; fs_argv[i][j]; j++) build[l++] = fs_argv[i][j];
if (strchr(fs_argv[i], ' ')) build[l++] = '\"';
}
build[l++] = '\n';
i--;
}
}
// now terminate the combined string and prepend it to the command buffer
// we already reserved space for the terminator
build[l++] = 0;
Cbuf_InsertText(build);
}
/*
============
Cmd_Wait_f
Causes execution of the remainder of the command buffer to be delayed until
next frame. This allows commands like:
bind g "cmd use rocket ; +attack ; wait ; -attack ; cmd use blaster"
============
*/
void Cmd_Wait_f (void)
{
if(Cmd_Argc() == 1) cmd_wait = 1;
else cmd_wait = atoi(Cmd_Argv( 1 ));
}
/*
===============
Cmd_Exec_f
===============
*/
void Cmd_Exec_f (void)
{
char *f, rcpath[MAX_QPATH];
int len;
if (Cmd_Argc () != 2)
{
Msg("exec <filename> : execute a script file\n");
return;
}
sprintf(rcpath, "scripts/config/%s", Cmd_Argv(1));
FS_DefaultExtension(rcpath, ".rc" ); // append as default
f = FS_LoadFile(rcpath, &len );
if (!f)
{
MsgWarn("couldn't exec %s\n", Cmd_Argv(1));
return;
}
MsgDev(D_INFO, "execing %s\n",Cmd_Argv(1));
Cbuf_InsertText(f);
Z_Free (f);
}
/*
===============
Cmd_Echo_f
Just prints the rest of the line to the console
===============
*/
void Cmd_Echo_f (void)
{
int i;
for(i = 1; i < Cmd_Argc(); i++)
Msg ("%s ",Cmd_Argv(i));
Msg ("\n");
}
/*
=====================================
Cmd_GetMapList
Prints or complete map filename
=====================================
*/
bool Cmd_GetMapList (const char *s, char *completedname, int length )
{
search_t *t;
file_t *f;
char message[MAX_QPATH];
char matchbuf[MAX_QPATH];
byte buf[MAX_SYSPATH]; // 1 kb
int i, nummaps;
t = FS_Search(va("maps/%s*.bsp", s), true );
if( !t ) return false;
FS_FileBase(t->filenames[0], matchbuf );
strncpy( completedname, matchbuf, length );
if(t->numfilenames == 1) return true;
for(i = 0, nummaps = 0; i < t->numfilenames; i++)
{
const char *data = NULL;
char *entities = NULL;
char entfilename[MAX_QPATH];
int ver = -1, lumpofs = 0, lumplen = 0;
const char *ext = FS_FileExtension( t->filenames[i] );
if( std.stricmp(ext, "bsp" )) continue;
strncpy(message, "^1error^7", sizeof(message));
f = FS_Open(t->filenames[i], "rb" );
if( f )
{
memset(buf, 0, 1024);
FS_Read(f, buf, 1024);
if(!memcmp(buf, "IBSP", 4))
{
dheader_t *header = (dheader_t *)buf;
ver = LittleLong(((int *)buf)[1]);
switch(ver)
{
case 38: // quake2 (xash)
case 46: // quake3
case 47: // return to castle wolfenstein
lumpofs = LittleLong(header->lumps[LUMP_ENTITIES].fileofs);
lumplen = LittleLong(header->lumps[LUMP_ENTITIES].filelen);
break;
}
}
else
{
lump_t ents; // quake1 entity lump
memcpy(&ents, buf + 4, sizeof(lump_t)); // skip first four bytes (version)
ver = LittleLong(((int *)buf)[0]);
switch( ver )
{
case 28: // quake 1 beta
case 29: // quake 1 regular
case 30: // Half-Life regular
lumpofs = LittleLong(ents.fileofs);
lumplen = LittleLong(ents.filelen);
break;
default:
ver = 0;
break;
}
}
std.strncpy(entfilename, t->filenames[i], sizeof(entfilename));
FS_StripExtension( entfilename );
FS_DefaultExtension( entfilename, ".ent" );
entities = (char *)FS_LoadFile(entfilename, NULL);
if( !entities && lumplen >= 10 )
{
FS_Seek(f, lumpofs, SEEK_SET);
entities = (char *)Z_Malloc(lumplen + 1);
FS_Read(f, entities, lumplen);
}
if( entities )
{
// if there are entities to parse, a missing message key just
// means there is no title, so clear the message string now
message[0] = 0;
data = entities;
while(Com_ParseToken(&data))
{
if(!strcmp(com_token, "{" )) continue;
else if(!strcmp(com_token, "}" )) break;
else if(!strcmp(com_token, "message" ))
{
// get the message contents
Com_ParseToken(&data);
strncpy(message, com_token, sizeof(message));
}
else if(!strcmp(com_token, "mapversion" ))
{
// get map version
Com_ParseToken(&data);
// old xash maps are Half-Life, so don't overwrite version
if(ver > 30) ver = atoi(com_token);
}
}
}
}
if( entities )Z_Free(entities);
if( f )FS_Close(f);
FS_FileBase(t->filenames[i], matchbuf );
switch(ver)
{
case 28: strncpy((char *)buf, "Quake1 beta", sizeof(buf)); break;
case 29: strncpy((char *)buf, "Quake1", sizeof(buf)); break;
case 30: strncpy((char *)buf, "Half-Life", sizeof(buf)); break;
case 38: strncpy((char *)buf, "Quake 2", sizeof(buf)); break;
case 46: strncpy((char *)buf, "Quake 3", sizeof(buf)); break;
case 47: strncpy((char *)buf, "RTCW", sizeof(buf)); break;
case 220: strncpy((char *)buf, "Xash 3D", sizeof(buf)); break;
default: strncpy((char *)buf, "??", sizeof(buf)); break;
}
Msg("%16s (%s) ^3%s^7\n", matchbuf, buf, message);
nummaps++;
}
Msg("\n^3 %i maps found.\n", nummaps );
Z_Free( t );
// cut shortestMatch to the amount common with s
for( i = 0; matchbuf[i]; i++ )
{
if(tolower(completedname[i]) != tolower(matchbuf[i]))
completedname[i] = 0;
}
return true;
}
/*
=====================================
Cmd_GetDemoList
Prints or complete demo filename
=====================================
*/
bool Cmd_GetDemoList (const char *s, char *completedname, int length )
{
search_t *t;
char matchbuf[MAX_QPATH];
int i, numdems;
t = FS_Search(va("demos/%s*.dem", s ), true);
if(!t) return false;
FS_FileBase(t->filenames[0], matchbuf );
if(completedname && length) strncpy( completedname, matchbuf, length );
if(t->numfilenames == 1) return true;
for(i = 0, numdems = 0; i < t->numfilenames; i++)
{
const char *ext = FS_FileExtension( t->filenames[i] );
if( std.stricmp(ext, "dem" )) continue;
FS_FileBase(t->filenames[i], matchbuf );
Msg("%16s\n", matchbuf );
numdems++;
}
Msg("\n^3 %i demos found.\n", numdems );
Z_Free(t);
// cut shortestMatch to the amount common with s
if(completedname && length)
{
for( i = 0; matchbuf[i]; i++ )
{
if(tolower(completedname[i]) != tolower(matchbuf[i]))
completedname[i] = 0;
}
}
return true;
}
/*
=====================================
Cmd_GetMovieList
Prints or complete movie filename
=====================================
*/
bool Cmd_GetMovieList (const char *s, char *completedname, int length )
{
search_t *t;
char matchbuf[MAX_QPATH];
int i, nummovies;
t = FS_Search(va("video/%s*.roq", s ), true);
if(!t) return false;
FS_FileBase(t->filenames[0], matchbuf );
if(completedname && length) strncpy( completedname, matchbuf, length );
if(t->numfilenames == 1) return true;
for(i = 0, nummovies = 0; i < t->numfilenames; i++)
{
const char *ext = FS_FileExtension( t->filenames[i] );
if( std.stricmp(ext, "roq" )) continue;
FS_FileBase(t->filenames[i], matchbuf );
Msg("%16s\n", matchbuf );
nummovies++;
}
Msg("\n^3 %i movies found.\n", nummovies );
Z_Free(t);
// cut shortestMatch to the amount common with s
if(completedname && length)
{
for( i = 0; matchbuf[i]; i++ )
{
if(tolower(completedname[i]) != tolower(matchbuf[i]))
completedname[i] = 0;
}
}
return true;
}
/*
=============================================================================
COMMAND EXECUTION
=============================================================================
*/
typedef struct cmd_function_s
{
struct cmd_function_s *next;
char *name;
char *desc;
xcommand_t function;
} cmd_function_t;
static int cmd_argc;
static char *cmd_argv[MAX_STRING_TOKENS];
static char cmd_tokenized[MAX_INPUTLINE+MAX_STRING_TOKENS]; // will have 0 bytes inserted
static cmd_function_t *cmd_functions; // possible commands to execute
/*
============
Cmd_Argc
============
*/
int Cmd_Argc (void)
{
return cmd_argc;
}
/*
============
Cmd_Argv
============
*/
char *Cmd_Argv (int arg)
{
if((uint)arg >= cmd_argc )
return "";
return cmd_argv[arg];
}
/*
============
Cmd_Args
Returns a single string containing argv(1) to argv(argc()-1)
============
*/
char *Cmd_Args (void)
{
static char cmd_args[MAX_STRING_CHARS];
int i;
cmd_args[0] = 0;
// build only for current call
for ( i = 1; i < cmd_argc; i++ )
{
strcat( cmd_args, cmd_argv[i] );
if ( i != cmd_argc-1 )
strcat( cmd_args, " " );
}
return cmd_args;
}
/*
============
Cmd_TokenizeString
Parses the given string into command line tokens.
The text is copied to a seperate buffer and 0 characters
are inserted in the apropriate place, The argv array
will point into this temporary buffer.
============
*/
void Cmd_TokenizeString (const char *text_in)
{
const char *text;
char *textOut;
cmd_argc = 0; // clear previous args
if(!text_in ) return;
text = text_in;
textOut = cmd_tokenized;
while( 1 )
{
// this is usually something malicious
if ( cmd_argc == MAX_STRING_TOKENS ) return;
while ( 1 )
{
// skip whitespace
while ( *text && *text <= ' ' ) text++;
if ( !*text ) return; // all tokens parsed
// skip // comments
if ( text[0] == '/' && text[1] == '/' ) return; // all tokens parsed
// skip /* */ comments
if ( text[0] == '/' && text[1] =='*' )
{
while(*text && ( text[0] != '*' || text[1] != '/' )) text++;
if ( !*text ) return; // all tokens parsed
text += 2;
}
else break; // we are ready to parse a token
}
// handle quoted strings
if ( *text == '"' )
{
cmd_argv[cmd_argc] = textOut;
cmd_argc++;
text++;
while ( *text && *text != '"' ) *textOut++ = *text++;
*textOut++ = 0;
if ( !*text ) return; // all tokens parsed
text++;
continue;
}
// regular token
cmd_argv[cmd_argc] = textOut;
cmd_argc++;
// skip until whitespace, quote, or command
while ( *text > ' ' )
{
if ( text[0] == '"' ) break;
if ( text[0] == '/' && text[1] == '/' ) break;
// skip /* */ comments
if ( text[0] == '/' && text[1] =='*' ) break;
*textOut++ = *text++;
}
*textOut++ = 0;
if( !*text ) return; // all tokens parsed
}
}
/*
============
Cmd_AddCommand
============
*/
void Cmd_AddCommand (const char *cmd_name, xcommand_t function, const char *cmd_desc)
{
cmd_function_t *cmd;
// fail if the command already exists
if(Cmd_Exists( cmd_name ))
{
MsgDev(D_INFO, "Cmd_AddCommand: %s already defined\n", cmd_name);
return;
}
// use a small malloc to avoid zone fragmentation
cmd = Z_Malloc (sizeof(cmd_function_t));
cmd->name = copystring( cmd_name );
cmd->desc = copystring( cmd_desc );
cmd->function = function;
cmd->next = cmd_functions;
cmd_functions = cmd;
}
/*
============
Cmd_RemoveCommand
============
*/
void Cmd_RemoveCommand (const char *cmd_name)
{
cmd_function_t *cmd, **back;
back = &cmd_functions;
while( 1 )
{
cmd = *back;
if (!cmd ) return;
if (!strcmp( cmd_name, cmd->name ))
{
*back = cmd->next;
if(cmd->name) Z_Free(cmd->name);
if(cmd->desc) Z_Free(cmd->desc);
Z_Free(cmd);
return;
}
back = &cmd->next;
}
}
/*
============
Cmd_CommandCompletion
============
*/
void Cmd_CommandCompletion( void(*callback)(const char *s, const char *m))
{
cmd_function_t *cmd;
for (cmd = cmd_functions; cmd; cmd = cmd->next)
callback( cmd->name, cmd->desc );
}
/*
============
Cmd_Exists
============
*/
bool Cmd_Exists (const char *cmd_name)
{
cmd_function_t *cmd;
for (cmd=cmd_functions ; cmd ; cmd=cmd->next)
{
if (!strcmp (cmd_name,cmd->name))
return true;
}
return false;
}
/*
============
Cmd_ExecuteString
A complete command line has been parsed, so try to execute it
============
*/
void Cmd_ExecuteString( const char *text )
{
cmd_function_t *cmd, **prev;
// execute the command line
Cmd_TokenizeString( text );
if( !Cmd_Argc()) return; // no tokens
// check registered command functions
for ( prev = &cmd_functions; *prev; prev = &cmd->next )
{
cmd = *prev;
if(!stricmp( cmd_argv[0], cmd->name ))
{
// rearrange the links so that the command will be
// near the head of the list next time it is used
*prev = cmd->next;
cmd->next = cmd_functions;
cmd_functions = cmd;
// perform the action
if(!cmd->function )
{ // forward to server command
Cmd_ExecuteString(va("cmd %s", text));
}
else cmd->function();
return;
}
}
// check cvars
if(Cvar_Command()) return;
// send it as a server command if we are connected
Cmd_ForwardToServer();
}
/*
============
Cmd_List_f
============
*/
void Cmd_List_f (void)
{
cmd_function_t *cmd;
int i = 0;
char *match;
if(Cmd_Argc() > 1) match = Cmd_Argv( 1 );
else match = NULL;
for (cmd = cmd_functions; cmd; cmd = cmd->next)
{
if (match && !Com_Filter(match, cmd->name, false))
continue;
Msg("%s\n", cmd->name);
i++;
}
Msg("%i commands\n", i);
}
/*
============
Cmd_Init
============
*/
void Cmd_Init( int argc, char **argv )
{
char dev_level[4];
Cbuf_Init( argc, argv );
// register our commands
Cmd_AddCommand ("exec", Cmd_Exec_f, "execute a script file" );
Cmd_AddCommand ("echo", Cmd_Echo_f, "print a message to the console (useful in scripts)" );
Cmd_AddCommand ("wait", Cmd_Wait_f, "make script execution wait for some rendered frames" );
Cmd_AddCommand ("cmdlist", Cmd_List_f, "display all console commands beginning with the specified prefix" );
Cmd_AddCommand ("stuffcmds", Cmd_StuffCmds_f, "execute commandline parameters (must be present in init.rc script)" );
// determine debug and developer mode
if(FS_CheckParm ("-debug")) host.debug = true;
if(FS_GetParmFromCmdLine("-dev", dev_level ))
host.developer = atoi(dev_level);
}