/* 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; } 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\"\n", var.name, cvar->string ); else FS_Printf( cfg, "%s \"%s\"\n", 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; }