CScriptParser

From Valve Developer Community
Jump to: navigation, search
Wikipedia - Letter.png
This article has multiple issues. Please help improve it or discuss these issues on the talk page. (Learn how and when to remove these template messages)
Dead End - Icon.png
This article has no links to other VDC articles. Please help improve this article by adding links that are relevant to the context within the existing text.
January 2024


I really liked the implementation of weapon_parse.cpp but I needed to use it on other things besides weapons. This is part of that implementation that handles searching and loading both .txt and .ctx files with ease, and then passing off the rest to a ::Parse function that you create in your own class. You still have to come up with a way to store the values.

Where would you need to use this? In GoldenEye: Source we use this to load in all of our character data from (scripts/characters/*.X), that is then used throughout the client and server code.

Terms of use

This code is now open source, free for use without any obligation of putting my name in any credits or anything. All I ask is that you leave my name intact where it lies in the code. I also ask that if you do improve upon this code at all that you can make those changes public for every ones benefit, but you are certainly not obligated to do so.

script_parser.h

///////////// Copyright © 2006, Scott Loyd. All rights reserved. /////////////
// 
// File: script_parser.h
// Description:  This is an abstract class, that handles finding and loading
//				  keyvalue scripts, then passes off usable keyvalues to extended
//				  classes.  Reason for making this is because I really liked
//				  how valve setup weapon_parse.cpp but in order to use it
//				  for other script parsing, you had to recreate all the guts.
//
// Usage: See the test case in script_parser.cpp
//
// Created On: 6/19/2006 11:03:20 AM
// Created By: Scott Loyd <mailto:[email protected]> 
/////////////////////////////////////////////////////////////////////////////
#ifndef SCRIPT_PARSE_H
#define SCRIPT_PARSE_H
#ifdef _WIN32
#pragma once
#endif

class CScriptParser
{
public:
	CScriptParser();

	//Parser Methods to initially open and setup the KeyValues
	void InitParser(const char *pszPath, bool bAllowNonEncryptedSearch = true,
		 bool bAllowEncryptedSearch = true);

	//You will need to set this up in your own class.
	virtual void Parse( KeyValues *pKeyValuesData, bool bWildcard, const char *szFileWithoutEXT ) = 0;

	//You can override these if you need to...
	virtual const char *GetPlaceHolderEXT() { return "X"; };
	virtual const char *GetNonEncryptedEXT() { return "txt"; };
	virtual const char *GetEncryptedEXT() { return "ctx"; };

	//Search path used by IFileSystem
	virtual const char *GetFSSearchPath() { return "MOD"; };

protected:
	bool IsParsed() { return m_bParsed; };

	bool FileParser(const char *pszFilePath, bool bWildcard, bool bEncrypted);

	void SetExtension(char *pszPath, int iPathSize, const char *pszNewEXT);
	void RemoveFileFromPath(char *pszPath);

	char *ExtractFileFromPath(const char *pszPath,bool noEXT = false);

	bool ExtCMP(const char *pszPath, const char *pszExt); //Compares extension

private:
	bool m_bParsed;
};

#endif //SCRIPT_PARSE_H

script_parser.cpp

///////////// Copyright © 2006, Scott Loyd. All rights reserved. /////////////
// 
// File: script_parser.cpp
// Description:
//      see script_parser.h
//
// Created On: 6/19/2006 11:03:45 AM
// Created By: Scott Loyd <mailto:[email protected]> 
/////////////////////////////////////////////////////////////////////////////
// Update: 8-06-06, Scott
//  Fixed occurrences of "error C2660: 'V_strcat' : function does not take 2 
//   arguments"  after merging this with the new SDK code.
//--
// Update: 8-10-06, Scott
//  Previous fix was done incorrectly (usage of Q_strcat was off).
//--
#include "cbase.h"
#include <KeyValues.h>
#include "filesystem.h"
#include <tier0/mem.h>
#include "gamerules.h"

#include "script_parser.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

extern IFileSystem *filesystem;

#define FILE_PATH_MAX_LENGTH 256
char g_szSwap[FILE_PATH_MAX_LENGTH];

CScriptParser::CScriptParser()
{
	m_bParsed = false;
}

//
// This method starts the parser that will either load one file
//	or many based on pszFilePath.
// Input: pszFilePath - FS path to KeyValue file(s) for parsing.
//						extensions are defined using .X
//
void CScriptParser::InitParser(const char *pszPath,
							   bool bAllowNonEncryptedSearch,
							   bool bAllowEncryptedSearch)
{
	//If you hit this assert, check to see where your instancing 
	//CScriptParser, make sure it isn't before the filesystem inits
	Assert(filesystem);

	Assert(pszPath);

	//Already Parsed... ?
	if(m_bParsed)
		return;

	//If pszPath is a wildcarded path, f.e. "scripts/weapon_*.X"
	if(strchr(pszPath,'*'))
	{
		FileFindHandle_t findHandle;
		const char *pFileName = NULL;
		char szFileBase[FILE_PATH_MAX_LENGTH];
		char szWildcardPath[FILE_PATH_MAX_LENGTH];
		char szFullFilePath[FILE_PATH_MAX_LENGTH];

		//Copy the file path to tweak the extensions
		Q_strncpy(szWildcardPath,pszPath,FILE_PATH_MAX_LENGTH);
		Q_strncpy(szFileBase,pszPath,FILE_PATH_MAX_LENGTH);

		RemoveFileFromPath(szFileBase);

		if(bAllowNonEncryptedSearch)
		{
			SetExtension(szWildcardPath,FILE_PATH_MAX_LENGTH,GetNonEncryptedEXT());

			//Search for non-encrypted files!
			pFileName = filesystem->FindFirstEx( szWildcardPath, GetFSSearchPath(), &findHandle );
			while ( pFileName != NULL )
			{
				Q_strcpy(szFullFilePath,szFileBase);
				Q_strcat(szFullFilePath,pFileName,FILE_PATH_MAX_LENGTH);
				if(!FileParser(szFullFilePath, true, false))
				{
					DevMsg("[script_parser.cpp] Unable to find '%s' for parsing!",pszPath);
					Assert(!"[script_parser.cpp] File not found for parsing!");
				}
				pFileName = filesystem->FindNext( findHandle );
			}
			filesystem->FindClose( findHandle );
		}

		if(bAllowEncryptedSearch)
		{
			//Search for encrypted files!
			SetExtension(szWildcardPath,FILE_PATH_MAX_LENGTH,GetEncryptedEXT());

			pFileName = filesystem->FindFirstEx( szWildcardPath, GetFSSearchPath(), &findHandle );
			while ( pFileName != NULL )
			{
				Q_strcpy(szFullFilePath,szFileBase);
				Q_strcat(szFullFilePath,pFileName,FILE_PATH_MAX_LENGTH);
				if(!FileParser(szFullFilePath, true, true))
				{
					DevMsg("[script_parser.cpp] Unable to find '%s' for parsing!",pszPath);
					Assert(!"[script_parser.cpp] File not found for parsing!");
				}
				pFileName = filesystem->FindNext( findHandle );
			}
			filesystem->FindClose( findHandle );
		}
	}
	else //Only one file needs to be parsed (not a wildcard).
	{
		Q_strcpy(g_szSwap,pszPath);
		SetExtension(g_szSwap,FILE_PATH_MAX_LENGTH,GetNonEncryptedEXT());
		if(!filesystem->FileExists(g_szSwap, GetFSSearchPath())
			&& bAllowEncryptedSearch)
		{
			SetExtension(g_szSwap,FILE_PATH_MAX_LENGTH,GetEncryptedEXT());
			if(!filesystem->FileExists(g_szSwap, GetFSSearchPath()))
			{
				DevMsg("[script_parser.cpp] Unable to find '%s' for parsing!",pszPath);
				Assert(!"[script_parser.cpp] File not found for parsing!");
				return;
			}
		} else {
			DevMsg("[script_parser.cpp] Unable to find '%s' for parsing!",pszPath);
			Assert(!"[script_parser.cpp] File not found for parsing!");
			return;
		}
		if(!FileParser(g_szSwap, false,ExtCMP(g_szSwap,GetEncryptedEXT())))
		{
			DevMsg("[script_parser.cpp] ERROR: Unable to Parse Passed Script File!");
		}
	}
	m_bParsed = true;
}

//
// This method handles parsing a single file, it can be called
//  many times if a wildcard path was passed to InitParser.
// Input: pszFilePath - FS path to a KeyValue file for parsing.
//		  bWildcard - Is this file the only one to be parsed? or will more come...
//
bool CScriptParser::FileParser(const char *pszFilePath, bool bWildcard, bool bEncrypted)
{
	const unsigned char *pICEKey = g_pGameRules->GetEncryptionKey();
	KeyValues *pKV = new KeyValues( "KVDataFile" );

	//Open up the file and load it into memory!
	FileHandle_t fileHandle = filesystem->Open( pszFilePath, "rb", GetFSSearchPath() );
	if(!fileHandle)
	{
		pKV->deleteThis();
		return false;
	}
	// load file into a null-terminated buffer
	int fileSize = filesystem->Size(fileHandle);
	char *buffer = (char*)MemAllocScratch(fileSize + 1);
		
	Assert(buffer);
		
	filesystem->Read(buffer, fileSize, fileHandle); // read into local buffer
	buffer[fileSize] = NULL; // null terminate file as EOF
	filesystem->Close( fileHandle );	// close file after reading

	if(bEncrypted)
		UTIL_DecodeICE( (unsigned char*)buffer, fileSize, pICEKey );

	if(!pKV->LoadFromBuffer( pszFilePath, buffer, filesystem ))
	{
		MemFreeScratch();
		pKV->deleteThis();
		return false;
	}

	Parse(pKV,bWildcard,ExtractFileFromPath(pszFilePath,true));

	MemFreeScratch();
	pKV->deleteThis();
	return true;
}

//
// Changes the extension to whatever is pszNewEXT
//  i.e: pszPath = "/test/me.SI" pszNewEXT = "txt"
//		 pszPath would end up as "/test/me.txt"
//
void CScriptParser::SetExtension(char *pszPath,int iPathSize, const char *pszNewEXT)
{
	int mover = Q_strlen(pszPath);
	while(mover > 0)
	{
		if(pszPath[mover] == '.')
			break;
		mover--;
	}
	//No dot... Just append it to the end?
	if(mover == 0)
	{
		Q_strcat(pszPath,pszNewEXT,iPathSize);
	} 
	else
	{
		Q_strcpy(&pszPath[mover+1],pszNewEXT);
	}
}
//
// Removes anything past the last /
//  if passed "/test/this/stuff" would end up with "/test/this/"
//
void CScriptParser::RemoveFileFromPath(char *pszPath)
{
	int mover = Q_strlen(pszPath);
	while(mover > 0)
	{
		if(pszPath[mover] == '/')
			break;
		mover--;
	}
	pszPath[mover+1] = '\0';
}

//
// Takes a path, and returns just the filename
//  if noEXT = true, it removes the extension from the end.
//
char *CScriptParser::ExtractFileFromPath(const char *pszPath,bool noEXT)
{
	//Q_strncpy(g_szSwap,pszPath,FILE_PATH_MAX_LENGTH);
	int mover = Q_strlen(pszPath);
	while(mover > 0)
	{
		if(pszPath[mover] == '/')
			break;
		mover--;
	}
	Q_strncpy(g_szSwap,(pszPath + mover +1),FILE_PATH_MAX_LENGTH);
	if(noEXT)
	{
		int mover2 = Q_strlen(g_szSwap);
		while(mover2 > 0)
		{
			if(g_szSwap[mover2] == '.')
				break;
			mover2--;
		}
		g_szSwap[mover2] = '\0';
	}
	return g_szSwap;
}

//
// Takes in pszPath, and compares the extension on it to pszExt
//  if they match, returns true, otherwise false.
//
bool CScriptParser::ExtCMP(const char *pszPath, const char *pszExt)
{
	int mover = Q_strlen(pszPath);
	while(mover > 0)
	{
		if(pszPath[mover] == '.')
			break;
		mover--;
	}
	if(mover == 0)
		return false;
	const char *pszPathEXT = &pszPath[mover+1];
	return Q_strcmp(pszPathEXT,pszExt) == 0;
}

mod_test_parser.cpp - a simple example of its use

>
//--------------------------------------------------------------------------
//
// CScriptParserTest - Example/Test Case.
//	toss your parsers code in your own file (for example: ge_script_parsers.cpp)
//
#define MAX_TEST_DATA_ELEMENTS 16
struct _TestData
{
	int m_iTest;
}TestData[MAX_TEST_DATA_ELEMENTS];
int td_mover = 0;

class CScriptParserTest : public CScriptParser
{
public:
	DECLARE_CLASS_GAMEROOT( CScriptParserTest, CScriptParser );

	void Parse( KeyValues *pKeyValuesData, bool bWildcard, const char *szFileWithoutEXT )
	{
		//Keep in mind, the KeyValues are destroyed after this scope, so make
		//sure you have these copied off into your own structures for use 
		//throughout your program.
		TestData[td_mover].m_iTest = pKeyValuesData->GetInt("iTest",0);
		td_mover++;
	};
};
CScriptParserTest g_ScriptParserTest;

//
// Call this in CWorld::Precache( void ) and C_World::Precache( void )
//  so that both client and server precache these scripts before the
//  game is loaded.
//
void PrecacheScriptParsers()
{
	//Notice that .X is used as the extension, .X will be replaced with
	// .txt and .ctx to search for both encrypted(ctx) and 
	// un-encrypted(txt) files.  See the definition of InitParser
	// if you only want to load .txt or something.  Note: If your path does
	// not have a wildcard, the bool values on InitParser will not take effect.
	g_ScriptParserTest.InitParser("scripts/ScriptTest/*.X");
}