341 lines
7.4 KiB
341 lines
7.4 KiB
/* |
|
cfgscript.c - "Valve script" parsing routines |
|
Copyright (C) 2016 mittorn |
|
|
|
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 3 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. |
|
*/ |
|
|
|
#include "common.h" |
|
|
|
typedef enum |
|
{ |
|
T_NONE = 0, |
|
T_BOOL, |
|
T_NUMBER, |
|
T_LIST, |
|
T_STRING, |
|
T_COUNT |
|
} cvartype_t; |
|
|
|
char *cvartypes[] = { NULL, "BOOL" , "NUMBER", "LIST", "STRING" }; |
|
|
|
typedef struct parserstate_s |
|
{ |
|
char *buf; |
|
char token[MAX_STRING]; |
|
const char *filename; |
|
} parserstate_t; |
|
|
|
typedef struct scrvardef_s |
|
{ |
|
char name[MAX_STRING]; |
|
char value[MAX_STRING]; |
|
char desc[MAX_STRING]; |
|
float fMin, fMax; |
|
cvartype_t type; |
|
int flags; |
|
qboolean fHandled; |
|
} scrvardef_t; |
|
|
|
/* |
|
=================== |
|
CSCR_ExpectString |
|
|
|
Return true if next token is pExpext and skip it |
|
=================== |
|
*/ |
|
qboolean CSCR_ExpectString( parserstate_t *ps, const char *pExpect, qboolean skip, qboolean error ) |
|
{ |
|
char *tmp = COM_ParseFile( ps->buf, ps->token ); |
|
|
|
if( !Q_stricmp( ps->token, pExpect ) ) |
|
{ |
|
ps->buf = tmp; |
|
return true; |
|
} |
|
|
|
if( skip ) ps->buf = tmp; |
|
if( error ) MsgDev( D_ERROR, "Syntax error in %s: got \"%s\" instead of \"%s\"\n", ps->filename, ps->token, pExpect ); |
|
|
|
return false; |
|
} |
|
|
|
/* |
|
=================== |
|
CSCR_ParseType |
|
|
|
Determine script variable type |
|
=================== |
|
*/ |
|
cvartype_t CSCR_ParseType( parserstate_t *ps ) |
|
{ |
|
int i; |
|
|
|
for( i = 1; i < T_COUNT; i++ ) |
|
{ |
|
if( CSCR_ExpectString( ps, cvartypes[i], false, false )) |
|
return i; |
|
} |
|
|
|
MsgDev( D_ERROR, "Cannot parse %s: Bad type %s\n", ps->filename, ps->token ); |
|
return T_NONE; |
|
} |
|
|
|
|
|
|
|
/* |
|
========================= |
|
CSCR_ParseSingleCvar |
|
========================= |
|
*/ |
|
qboolean CSCR_ParseSingleCvar( parserstate_t *ps, scrvardef_t *result ) |
|
{ |
|
// read the name |
|
ps->buf = COM_ParseFile( ps->buf, result->name ); |
|
|
|
if( !CSCR_ExpectString( ps, "{", false, true )) |
|
return false; |
|
|
|
// read description |
|
ps->buf = COM_ParseFile( ps->buf, result->desc ); |
|
|
|
if( !CSCR_ExpectString( ps, "{", false, true )) |
|
return false; |
|
|
|
result->type = CSCR_ParseType( ps ); |
|
|
|
switch( result->type ) |
|
{ |
|
case T_BOOL: |
|
// bool only has description |
|
if( !CSCR_ExpectString( ps, "}", false, true )) |
|
return false; |
|
break; |
|
case T_NUMBER: |
|
// min |
|
ps->buf = COM_ParseFile( ps->buf, ps->token ); |
|
result->fMin = Q_atof( ps->token ); |
|
|
|
// max |
|
ps->buf = COM_ParseFile( ps->buf, ps->token ); |
|
result->fMax = Q_atof( ps->token ); |
|
|
|
if( !CSCR_ExpectString( ps, "}", false, true )) |
|
return false; |
|
break; |
|
case T_STRING: |
|
if( !CSCR_ExpectString( ps, "}", false, true )) |
|
return false; |
|
break; |
|
case T_LIST: |
|
while( !CSCR_ExpectString( ps, "}", true, false )) |
|
{ |
|
// read token for each item here |
|
} |
|
break; |
|
default: |
|
return false; |
|
} |
|
|
|
if( !CSCR_ExpectString( ps, "{", false, true )) |
|
return false; |
|
|
|
// default value |
|
ps->buf = COM_ParseFile( ps->buf, result->value ); |
|
|
|
if( !CSCR_ExpectString( ps, "}", false, true )) |
|
return false; |
|
|
|
if( CSCR_ExpectString( ps, "SetInfo", false, false )) |
|
result->flags |= FCVAR_USERINFO; |
|
|
|
if( !CSCR_ExpectString( ps, "}", false, true )) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
/* |
|
====================== |
|
CSCR_ParseHeader |
|
|
|
Check version and seek to first cvar name |
|
====================== |
|
*/ |
|
qboolean CSCR_ParseHeader( parserstate_t *ps ) |
|
{ |
|
if( !CSCR_ExpectString( ps, "VERSION", false, true )) |
|
return false; |
|
|
|
// Parse in the version # |
|
// Get the first token. |
|
ps->buf = COM_ParseFile( ps->buf, ps->token ); |
|
|
|
if( Q_atof( ps->token ) != 1 ) |
|
{ |
|
MsgDev( D_ERROR, "File %s has wrong version %s!\n", ps->filename, ps->token ); |
|
return false; |
|
} |
|
|
|
if( !CSCR_ExpectString( ps, "DESCRIPTION", false, true )) |
|
return false; |
|
|
|
ps->buf = COM_ParseFile( ps->buf, ps->token ); |
|
|
|
if( Q_stricmp( ps->token, "INFO_OPTIONS") && Q_stricmp( ps->token, "SERVER_OPTIONS" )) |
|
{ |
|
MsgDev( D_ERROR, "DESCRIPTION must be INFO_OPTIONS or SERVER_OPTIONS\n"); |
|
return false; |
|
} |
|
|
|
if( !CSCR_ExpectString( ps, "{", false, true )) |
|
return false; |
|
|
|
return true; |
|
} |
|
|
|
/* |
|
====================== |
|
CSCR_WriteGameCVars |
|
|
|
Print all cvars declared in script to game.cfg file |
|
====================== |
|
*/ |
|
int CSCR_WriteGameCVars( file_t *cfg, const char *scriptfilename ) |
|
{ |
|
parserstate_t state = { 0 }; |
|
qboolean success = false; |
|
int count = 0; |
|
long length = 0; |
|
char *start; |
|
|
|
state.filename = scriptfilename; |
|
state.buf = start = (char *)FS_LoadFile( scriptfilename, &length, true ); |
|
|
|
if( !state.buf || !length ) |
|
return 0; |
|
|
|
MsgDev( D_INFO, "Reading config script file %s\n", scriptfilename ); |
|
|
|
if( !CSCR_ParseHeader( &state )) |
|
{ |
|
MsgDev( D_ERROR, "Failed to parse header!\n" ); |
|
goto finish; |
|
} |
|
|
|
FS_Printf( cfg, "// declared in %s:\n", scriptfilename ); |
|
|
|
while( !CSCR_ExpectString( &state, "}", false, false )) |
|
{ |
|
scrvardef_t var = { 0 }; |
|
|
|
if( CSCR_ParseSingleCvar( &state, &var ) ) |
|
{ |
|
convar_t *cvar = Cvar_FindVar( var.name ); |
|
|
|
if( cvar && !FBitSet( cvar->flags, FCVAR_SERVER|FCVAR_ARCHIVE )) |
|
{ |
|
// cvars will be placed in game.cfg and restored on map start |
|
if( var.flags & FCVAR_USERINFO ) |
|
FS_Printf( cfg, "// %s ( %s )\nsetu %s \"%s\"\n", var.desc, var.value, var.name, cvar->string ); |
|
else FS_Printf( cfg, "// %s ( %s )\nset %s \"%s\"\n", var.desc, var.value, var.name, cvar->string ); |
|
} |
|
count++; |
|
} |
|
else |
|
{ |
|
break; |
|
} |
|
|
|
if( count > 1024 ) |
|
break; |
|
} |
|
|
|
if( COM_ParseFile( state.buf, state.token )) |
|
MsgDev( D_ERROR, "Got extra tokens!\n" ); |
|
else success = true; |
|
finish: |
|
if( !success ) |
|
{ |
|
state.token[sizeof( state.token ) - 1] = 0; |
|
|
|
if( start && state.buf ) |
|
MsgDev( D_ERROR, "Parse error in %s, byte %d, token %s\n", scriptfilename, (int)( state.buf - start ), state.token ); |
|
else MsgDev( D_ERROR, "Parse error in %s, token %s\n", scriptfilename, state.token ); |
|
} |
|
|
|
if( start ) Mem_Free( start ); |
|
|
|
return count; |
|
} |
|
|
|
/* |
|
====================== |
|
CSCR_LoadDefaultCVars |
|
|
|
Register all cvars declared in config file and set default values |
|
====================== |
|
*/ |
|
int CSCR_LoadDefaultCVars( const char *scriptfilename ) |
|
{ |
|
parserstate_t state = { 0 }; |
|
qboolean success = false; |
|
int count = 0; |
|
long length = 0; |
|
char *start; |
|
|
|
state.filename = scriptfilename; |
|
state.buf = start = (char *)FS_LoadFile( scriptfilename, &length, true ); |
|
|
|
if( !state.buf || !length ) |
|
return 0; |
|
|
|
MsgDev( D_INFO, "Reading config script file %s\n", scriptfilename ); |
|
|
|
if( !CSCR_ParseHeader( &state )) |
|
{ |
|
MsgDev( D_ERROR, "Failed to parse header!\n" ); |
|
goto finish; |
|
} |
|
|
|
while( !CSCR_ExpectString( &state, "}", false, false )) |
|
{ |
|
scrvardef_t var = { 0 }; |
|
|
|
// Create a new object |
|
if( CSCR_ParseSingleCvar( &state, &var ) ) |
|
{ |
|
Cvar_Get( var.name, var.value, var.flags|FCVAR_TEMPORARY, var.desc ); |
|
count++; |
|
} |
|
else |
|
break; |
|
|
|
if( count > 1024 ) |
|
break; |
|
} |
|
|
|
if( COM_ParseFile( state.buf, state.token )) |
|
MsgDev( D_ERROR, "Got extra tokens!\n" ); |
|
else success = true; |
|
finish: |
|
if( !success ) |
|
{ |
|
state.token[sizeof( state.token ) - 1] = 0; |
|
if( start && state.buf ) |
|
MsgDev( D_ERROR, "Parse error in %s, byte %d, token %s\n", scriptfilename, (int)( state.buf - start ), state.token ); |
|
else MsgDev( D_ERROR, "Parse error in %s, token %s\n", scriptfilename, state.token ); |
|
} |
|
|
|
if( start ) Mem_Free( start ); |
|
|
|
return count; |
|
}
|
|
|