diff --git a/src/common/callback.cpp b/src/common/callback.cpp index 74117ff9..796f32e9 100644 --- a/src/common/callback.cpp +++ b/src/common/callback.cpp @@ -27,6 +27,7 @@ #endif // !CLIENT_DLL #include "rtech/rtech_game.h" #include "rtech/rtech_utils.h" +#include "rtech/playlists/playlists.h" #include "filesystem/basefilesystem.h" #include "filesystem/filesystem.h" #include "vpklib/packedstore.h" @@ -231,7 +232,7 @@ Host_ReloadPlaylists_f void Host_ReloadPlaylists_f(const CCommand& args) { v__DownloadPlaylists_f(); - KeyValues::InitPlaylists(); // Re-Init playlist. + Playlists_SDKInit(); // Re-Init playlist. } /* @@ -647,7 +648,7 @@ void VPK_Pack_f(const CCommand& args) Msg(eDLL_T::FS, "*** Starting VPK build command for: '%s'\n", pair.m_DirName.Get()); timer.Start(); - g_pPackedStore->InitLzCompParams(); + g_pPackedStore->InitLzCompParams(fs_packedstore_compression_level->GetString(), fs_packedstore_max_helper_threads->GetInt()); g_pPackedStore->PackWorkspace(pair, fs_packedstore_workspace->GetString(), "vpk/"); timer.End(); diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 72124c81..a951531a 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -61,6 +61,7 @@ target_link_libraries( ${PROJECT_NAME} PRIVATE "rtech_tools" "rtech_game" + "playlists" "stryder" "libdetours" diff --git a/src/core/init.cpp b/src/core/init.cpp index a6603cbe..82f256f3 100644 --- a/src/core/init.cpp +++ b/src/core/init.cpp @@ -18,8 +18,8 @@ #include "tier0/sigcache.h" #include "tier1/cmd.h" #include "tier1/cvar.h" +#include "tier1/keyvalues_iface.h" #include "vpc/IAppSystem.h" -#include "vpc/keyvalues.h" #include "vpc/rson.h" #include "vpc/interfaces.h" #include "common/callback.h" @@ -63,6 +63,7 @@ #include "rtech/rtech_game.h" #include "rtech/rtech_utils.h" #include "rtech/stryder/stryder.h" +#include "rtech/playlists/playlists.h" #ifndef DEDICATED #include "rtech/rui/rui.h" #include "engine/client/cl_ents_parse.h" @@ -552,6 +553,7 @@ void DetourRegister() // Register detour classes to be searched and hooked. REGISTER(V_RTechGame); REGISTER(V_RTechUtils); REGISTER(VStryder); + REGISTER(VPlaylists); #ifndef DEDICATED REGISTER(V_Rui); diff --git a/src/engine/client/cdll_engine_int.cpp b/src/engine/client/cdll_engine_int.cpp index 3055e889..14c6c5b9 100644 --- a/src/engine/client/cdll_engine_int.cpp +++ b/src/engine/client/cdll_engine_int.cpp @@ -10,7 +10,7 @@ #include "engine/net_chan.h" #include "engine/client/cl_rcon.h" #include "networksystem/bansystem.h" -#include "vpc/keyvalues.h" +#include "tier1/keyvalues.h" #include "windows/id3dx.h" #include "geforce/reflex.h" #include "vengineclient_impl.h" diff --git a/src/engine/client/client.h b/src/engine/client/client.h index 4a75da0d..0c698ffb 100644 --- a/src/engine/client/client.h +++ b/src/engine/client/client.h @@ -1,5 +1,5 @@ #pragma once -#include "vpc/keyvalues.h" +#include "tier1/keyvalues.h" #include "common/protocol.h" #include "engine/net.h" #include "engine/net_chan.h" diff --git a/src/engine/client/clientstate.cpp b/src/engine/client/clientstate.cpp index ef3a1565..e22707dc 100644 --- a/src/engine/client/clientstate.cpp +++ b/src/engine/client/clientstate.cpp @@ -9,7 +9,6 @@ // ///////////////////////////////////////////////////////////////////////////////// #include "core/stdafx.h" -#include "vpc/keyvalues.h" #include "tier0/frametask.h" #include "engine/common.h" #include "engine/host.h" @@ -20,6 +19,7 @@ #include "common/callback.h" #include "cdll_engine_int.h" #include "vgui/vgui_baseui_interface.h" +#include "rtech/playlists/playlists.h" #include #include @@ -139,7 +139,7 @@ void CClientState::VConnectionClosing(CClientState* thisptr, const char* szReaso // Reload the local playlist to override the cached // one from the server we got disconnected from. v__DownloadPlaylists_f(); - KeyValues::InitPlaylists(); + Playlists_SDKInit(); }, 0); } diff --git a/src/engine/cmodel_bsp.cpp b/src/engine/cmodel_bsp.cpp index ae8e7fb9..800cc02c 100644 --- a/src/engine/cmodel_bsp.cpp +++ b/src/engine/cmodel_bsp.cpp @@ -14,7 +14,7 @@ #include "engine/cmodel_bsp.h" #include "rtech/rtech_utils.h" #include "rtech/rtech_game.h" -#include "vpc/keyvalues.h" +#include "tier1/keyvalues.h" #include "datacache/mdlcache.h" #include "filesystem/filesystem.h" #ifndef DEDICATED diff --git a/src/engine/cmodel_bsp.h b/src/engine/cmodel_bsp.h index 835fe9ff..9ab18dc8 100644 --- a/src/engine/cmodel_bsp.h +++ b/src/engine/cmodel_bsp.h @@ -25,6 +25,7 @@ inline void(*sub_140441220)(__int64 a1, __int64 a2); extern bool s_bBasePaksInitialized; extern CUtlVector g_InstalledMaps; +extern std::mutex g_InstalledMapsMutex; bool Mod_LevelHasChanged(const char* pszLevelName); void Mod_GetAllInstalledMaps(); diff --git a/src/engine/gl_model_private.h b/src/engine/gl_model_private.h index aba93d99..31db1b84 100644 --- a/src/engine/gl_model_private.h +++ b/src/engine/gl_model_private.h @@ -9,7 +9,7 @@ #ifndef GL_MODEL_PRIVATE_H #define GL_MODEL_PRIVATE_H -#include "vpc/keyvalues.h" +#include "tier1/keyvalues.h" #include "mathlib/vector.h" #include "common/qlimits.h" #include "datacache/imdlcache.h" diff --git a/src/engine/host_state.cpp b/src/engine/host_state.cpp index 2d62040f..9ae4a69e 100644 --- a/src/engine/host_state.cpp +++ b/src/engine/host_state.cpp @@ -13,7 +13,6 @@ #include "tier1/cvar.h" #include "tier1/NetAdr.h" #include "tier2/socketcreator.h" -#include "vpc/keyvalues.h" #include "datacache/mdlcache.h" #ifndef CLIENT_DLL #include "engine/server/sv_rcon.h" @@ -39,6 +38,7 @@ #include "rtech/rtech_game.h" #include "rtech/rtech_utils.h" #include "rtech/stryder/stryder.h" +#include "rtech/playlists/playlists.h" #ifndef DEDICATED #include "vgui/vgui_baseui_interface.h" #include "client/vengineclient_impl.h" @@ -316,7 +316,7 @@ void CHostState::Think(void) const { SetConsoleTitleA(Format("%s - %d/%d Players (%s on %s) - %d%% Server CPU (%.3f msec on frame %d)", hostname->GetString(), g_pServer->GetNumClients(), - g_ServerGlobalVariables->m_nMaxClients, KeyValues__GetCurrentPlaylist(), m_levelName, + g_ServerGlobalVariables->m_nMaxClients, v_Playlists_GetCurrent(), m_levelName, static_cast(g_pServer->GetCPUUsage() * 100.0f), (g_pEngine->GetFrameTime() * 1000.0f), g_pServer->GetTick()).c_str()); @@ -337,7 +337,7 @@ void CHostState::Think(void) const hostdesc->GetString(), sv_pylonVisibility->GetInt() == EServerVisibility_t::HIDDEN, g_pHostState->m_levelName, - KeyValues__GetCurrentPlaylist(), + v_Playlists_GetCurrent(), hostip->GetString(), hostport->GetInt(), g_pNetKey->GetBase64NetKey(), diff --git a/src/engine/net_chan.cpp b/src/engine/net_chan.cpp index f8149e4b..50b9299d 100644 --- a/src/engine/net_chan.cpp +++ b/src/engine/net_chan.cpp @@ -7,7 +7,7 @@ #include "core/stdafx.h" #include "tier0/frametask.h" #include "tier1/cvar.h" -#include "vpc/keyvalues.h" +#include "tier1/keyvalues.h" #include "common/callback.h" #include "engine/net.h" #include "engine/net_chan.h" diff --git a/src/game/client/vscript_client.cpp b/src/game/client/vscript_client.cpp index eaeadac0..4bf9214e 100644 --- a/src/game/client/vscript_client.cpp +++ b/src/game/client/vscript_client.cpp @@ -9,7 +9,7 @@ //=============================================================================// #include "core/stdafx.h" -#include "vpc/keyvalues.h" +#include "tier1/keyvalues.h" #include "engine/cmodel_bsp.h" #include "engine/host_state.h" #include "engine/client/cl_main.h" diff --git a/src/game/shared/vscript_shared.cpp b/src/game/shared/vscript_shared.cpp index 9ad86027..33c97e75 100644 --- a/src/game/shared/vscript_shared.cpp +++ b/src/game/shared/vscript_shared.cpp @@ -12,7 +12,7 @@ //=============================================================================// #include "core/stdafx.h" -#include "vpc/keyvalues.h" +#include "rtech/playlists/playlists.h" #include "engine/client/cl_main.h" #include "engine/cmodel_bsp.h" #include "vscript/languages/squirrel_re/include/sqvm.h" diff --git a/src/gameui/IBrowser.cpp b/src/gameui/IBrowser.cpp index 6c7aede7..4439bccc 100644 --- a/src/gameui/IBrowser.cpp +++ b/src/gameui/IBrowser.cpp @@ -31,7 +31,7 @@ History: #include "networksystem/serverlisting.h" #include "networksystem/pylon.h" #include "networksystem/listmanager.h" -#include "vpc/keyvalues.h" +#include "rtech/playlists/playlists.h" #include "common/callback.h" #include "gameui/IBrowser.h" #include "public/edict.h" @@ -603,7 +603,7 @@ void CBrowser::HostPanel(void) g_TaskScheduler->Dispatch([]() { v__DownloadPlaylists_f(); - KeyValues::InitPlaylists(); // Re-Init playlist. + Playlists_SDKInit(); // Re-Init playlist. }, 0); } @@ -745,7 +745,7 @@ void CBrowser::UpdateHostingStatus(void) g_pServerListManager->m_Server.m_svDescription, g_pServerListManager->m_Server.m_bHidden, g_pHostState->m_levelName, - KeyValues__GetCurrentPlaylist(), + v_Playlists_GetCurrent(), hostip->GetString(), hostport->GetInt(), g_pNetKey->GetBase64NetKey(), diff --git a/src/materialsystem/cmaterialsystem.cpp b/src/materialsystem/cmaterialsystem.cpp index a74ff5a8..c8cd5084 100644 --- a/src/materialsystem/cmaterialsystem.cpp +++ b/src/materialsystem/cmaterialsystem.cpp @@ -7,7 +7,7 @@ #include "tier0/crashhandler.h" #include "tier0/commandline.h" #include "tier1/cvar.h" -#include "vpc/keyvalues.h" +#include "tier1/keyvalues.h" #include "rtech/rtech_utils.h" #include "engine/cmodel_bsp.h" #include "engine/sys_engine.h" diff --git a/src/networksystem/listmanager.cpp b/src/networksystem/listmanager.cpp index f87e2ba5..066041fc 100644 --- a/src/networksystem/listmanager.cpp +++ b/src/networksystem/listmanager.cpp @@ -14,7 +14,7 @@ #include "engine/net.h" #include "engine/host_state.h" #include "engine/server/server.h" -#include "vpc/keyvalues.h" +#include "rtech/playlists/playlists.h" #include "pylon.h" #include "listmanager.h" @@ -74,7 +74,7 @@ void CServerListManager::LaunchServer(const bool bChangeLevel) const * values. Then when you would normally call launchplaylist which calls StartPlaylist it would cmd * call mp_gamemode which parses the gamemode specific part of the playlist.. */ - KeyValues::ParsePlaylists(m_Server.m_svPlaylist.c_str()); + v_Playlists_Parse(m_Server.m_svPlaylist.c_str()); mp_gamemode->SetValue(m_Server.m_svPlaylist.c_str()); ProcessCommand(Format("%s \"%s\"", bChangeLevel ? "changelevel" : "map", m_Server.m_svHostMap.c_str()).c_str()); diff --git a/src/pluginsystem/modsystem.h b/src/pluginsystem/modsystem.h index a81c4cff..ef1595ac 100644 --- a/src/pluginsystem/modsystem.h +++ b/src/pluginsystem/modsystem.h @@ -1,6 +1,6 @@ #pragma once -#include "vpc/keyvalues.h" +#include "tier1/keyvalues.h" #include "vpc/rson.h" #include "filesystem/filesystem.h" #include "public/vscript/ivscript.h" diff --git a/src/public/eiface.h b/src/public/eiface.h index bb465cc9..0ecffbb1 100644 --- a/src/public/eiface.h +++ b/src/public/eiface.h @@ -9,7 +9,7 @@ #define EIFACE_H #include "edict.h" #include "tier1/bitbuf.h" -#include "vpc/keyvalues.h" +#include "tier1/keyvalues.h" //----------------------------------------------------------------------------- // Forward declarations diff --git a/src/public/tier0/dbg.h b/src/public/tier0/dbg.h index 4a1db461..643c8c1d 100644 --- a/src/public/tier0/dbg.h +++ b/src/public/tier0/dbg.h @@ -113,6 +113,9 @@ PLATFORM_INTERFACE void NetMsg(LogType_t logType, eDLL_T context, const char* up PLATFORM_INTERFACE void Warning(eDLL_T context, const char* fmt, ...) FMTFUNCTION(2, 3); PLATFORM_INTERFACE void Error(eDLL_T context, const UINT code, const char* fmt, ...) FMTFUNCTION(3, 4); +// TODO[ AMOS ]: export to DLL? +void Plat_FatalError(eDLL_T context, const char* fmt, ...); + #if defined DBGFLAG_STRINGS_STRIP #define DevMsg( ... ) ((void)0) #define DevWarning( ... ) ((void)0) diff --git a/src/public/tier1/exprevaluator.h b/src/public/tier1/exprevaluator.h new file mode 100644 index 00000000..89dffc69 --- /dev/null +++ b/src/public/tier1/exprevaluator.h @@ -0,0 +1,74 @@ +//===== Copyright © 1996-2006, Valve Corporation, All rights reserved. ======// +// +// Purpose: ExprSimplifier builds a binary tree from an infix expression (in the +// form of a character array). +// +//===========================================================================// + +#ifndef EXPREVALUATOR_H +#define EXPREVALUATOR_H + +#if defined( _WIN32 ) +#pragma once +#endif + +static const char OR_OP = '|'; +static const char AND_OP = '&'; +static const char NOT_OP = '!'; + +#define MAX_IDENTIFIER_LEN 128 +enum Kind {CONDITIONAL, NOT, LITERAL}; + +struct ExprNode +{ + ExprNode *left; // left sub-expression + ExprNode *right; // right sub-expression + Kind kind; // kind of node this is + union + { + char cond; // the conditional + bool value; // the value + } data; +}; + +typedef ExprNode *ExprTree; + +// callback to evaluate a $ during evaluation, return true or false +typedef bool (*GetSymbolProc_t)( const char *pKey ); +typedef void (*SyntaxErrorProc_t)( const char *pReason ); + +class CExpressionEvaluator +{ +public: + CExpressionEvaluator(); + ~CExpressionEvaluator(); + bool Evaluate( bool &result, const char *pInfixExpression, GetSymbolProc_t pGetSymbolProc = 0, SyntaxErrorProc_t pSyntaxErrorProc = 0 ); + +private: + CExpressionEvaluator( CExpressionEvaluator& ); // prevent copy constructor being used + + char GetNextToken( void ); + void FreeNode( ExprNode *pNode ); + ExprNode *AllocateNode( void ); + void FreeTree( ExprTree &node ); + bool IsConditional( bool &bCondition, const char token ); + bool IsNotOp( const char token ); + bool IsIdentifierOrConstant( const char token ); + bool MakeExprNode( ExprTree &tree, char token, Kind kind, ExprTree left, ExprTree right ); + bool MakeFactor( ExprTree &tree ); + bool MakeTerm( ExprTree &tree ); + bool MakeExpression( ExprTree &tree ); + bool BuildExpression( void ); + bool SimplifyNode( ExprTree &node ); + + ExprTree m_ExprTree; // Tree representation of the expression + char m_CurToken; // Current token read from the input expression + const char *m_pExpression; // Array of the expression characters + int m_CurPosition; // Current position in the input expression + char m_Identifier[MAX_IDENTIFIER_LEN]; // Stores the identifier string + GetSymbolProc_t m_pGetSymbolProc; + SyntaxErrorProc_t m_pSyntaxErrorProc; + bool m_bSetup; +}; + +#endif diff --git a/src/public/tier1/fmtstr.h b/src/public/tier1/fmtstr.h new file mode 100644 index 00000000..dd0c20e5 --- /dev/null +++ b/src/public/tier1/fmtstr.h @@ -0,0 +1,369 @@ +//========= Copyright 1996-2005, Valve Corporation, All rights reserved. ============// +// +// Purpose: A simple class for performing safe and in-expression sprintf-style +// string formatting +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef FMTSTR_H +#define FMTSTR_H + +#include +#include +#include "tier0/platform.h" +#include "tier0/dbg.h" +#include "tier1/strtools.h" + +#if defined( _WIN32 ) +#pragma once +#endif + +//============================================================================= + +// using macro to be compatible with GCC +#define FmtStrVSNPrintf( szBuf, nBufSize, bQuietTruncation, ppszFormat, nPrevLen, lastArg ) \ + do \ + { \ + int result; \ + va_list arg_ptr; \ + bool bTruncated = false; \ + static int scAsserted = 0; \ + \ + va_start(arg_ptr, lastArg); \ + result = V_vsnprintfRet( (szBuf), (nBufSize)-1, (*(ppszFormat)), arg_ptr, &bTruncated ); \ + va_end(arg_ptr); \ + \ + (szBuf)[(nBufSize)-1] = 0; \ + if ( bTruncated && !(bQuietTruncation) && scAsserted < 5 ) \ + { \ + Warning( eDLL_T::COMMON, "FmtStrVSNPrintf truncated to %d without QUIET_TRUNCATION specified!\n", ( int )( nBufSize ) ); \ + AssertMsg( 0, "FmtStrVSNPrintf truncated without QUIET_TRUNCATION specified!\n" ); \ + scAsserted++; \ + } \ + m_nLength = nPrevLen + result; \ + } \ + while (0) + +// using macro to be compatable with GCC +#define FmtStrVSNPrintfNoLengthFixup( szBuf, nBufSize, bQuietTruncation, ppszFormat, nPrevLen, lastArg ) \ + do \ + { \ + int result; \ + va_list arg_ptr; \ + bool bTruncated = false; \ + static int scAsserted = 0; \ + \ + va_start(arg_ptr, lastArg); \ + result = V_vsnprintfRet( (szBuf), (nBufSize)-1, (*(ppszFormat)), arg_ptr, &bTruncated ); \ + va_end(arg_ptr); \ + \ + (szBuf)[(nBufSize)-1] = 0; \ + if ( bTruncated && !(bQuietTruncation) && scAsserted < 5 ) \ + { \ + Warning( eDLL_T::COMMON, "FmtStrVSNPrintf truncated to %d without QUIET_TRUNCATION specified!\n", ( int )( nBufSize ) ); \ + AssertMsg( 0, "FmtStrVSNPrintf truncated without QUIET_TRUNCATION specified!\n" ); \ + scAsserted++; \ + } \ + } \ + while (0) + +//----------------------------------------------------------------------------- +// +// Purpose: String formatter with specified size +// + +template +class CFmtStrN +{ +public: + CFmtStrN() + { + InitQuietTruncation(); + m_szBuf[0] = 0; + m_nLength = 0; + } + + // Standard C formatting + CFmtStrN(PRINTF_FORMAT_STRING const char *pszFormat, ...) FMTFUNCTION( 2, 3 ) + { + InitQuietTruncation(); + FmtStrVSNPrintf( m_szBuf, SIZE_BUF, m_bQuietTruncation, &pszFormat, 0, pszFormat ); + } + + // Use this for pass-through formatting + CFmtStrN(const char ** ppszFormat, ...) + { + InitQuietTruncation(); + FmtStrVSNPrintf( m_szBuf, SIZE_BUF, m_bQuietTruncation, ppszFormat, 0, ppszFormat ); + } + + // Explicit reformat + const char *sprintf(PRINTF_FORMAT_STRING const char *pszFormat, ...) FMTFUNCTION( 2, 3 ) + { + InitQuietTruncation(); + FmtStrVSNPrintf(m_szBuf, SIZE_BUF, m_bQuietTruncation, &pszFormat, 0, pszFormat ); + return m_szBuf; + } + + // Same as sprintf above, but for compatibility with Steam interface + const char *Format( PRINTF_FORMAT_STRING const char *pszFormat, ... ) FMTFUNCTION( 2, 3 ) + { + InitQuietTruncation(); + FmtStrVSNPrintf( m_szBuf, SIZE_BUF, m_bQuietTruncation, &pszFormat, 0, pszFormat ); + return m_szBuf; + } + + // Use this for va_list formatting + const char *sprintf_argv(const char *pszFormat, va_list arg_ptr) + { + int result; + bool bTruncated = false; + static int s_nWarned = 0; + + InitQuietTruncation(); + result = V_vsnprintfRet( m_szBuf, SIZE_BUF - 1, pszFormat, arg_ptr, &bTruncated ); + m_szBuf[SIZE_BUF - 1] = 0; + if ( bTruncated && !m_bQuietTruncation && ( s_nWarned < 5 ) ) + { + Warning( eDLL_T::COMMON, "CFmtStr truncated to %d without QUIET_TRUNCATION specified!\n", SIZE_BUF ); + AssertMsg( 0, "CFmtStr truncated without QUIET_TRUNCATION specified!\n" ); + s_nWarned++; + } + m_nLength = V_strlen( m_szBuf ); + return m_szBuf; + } + + // Use this for pass-through formatting + void VSprintf(const char **ppszFormat, ...) + { + InitQuietTruncation(); + FmtStrVSNPrintf( m_szBuf, SIZE_BUF, m_bQuietTruncation, ppszFormat, 0, ppszFormat ); + } + + // Compatible API with CUtlString for converting to const char* + const char *Get( ) const { return m_szBuf; } + const char *String( ) const { return m_szBuf; } + // Use for access + operator const char *() const { return m_szBuf; } + char *Access() { return m_szBuf; } + + // Access template argument + static inline int GetMaxLength() { return SIZE_BUF-1; } + + CFmtStrN & operator=( const char *pchValue ) + { + V_strncpy( m_szBuf, pchValue, SIZE_BUF ); + m_nLength = V_strlen( m_szBuf ); + return *this; + } + + CFmtStrN & operator+=( const char *pchValue ) + { + Append( pchValue ); + return *this; + } + + int Length() const { return m_nLength; } + + void SetLength( int nLength ) + { + m_nLength = Min( nLength, SIZE_BUF - 1 ); + m_szBuf[m_nLength] = '\0'; + } + + void Clear() + { + m_szBuf[0] = 0; + m_nLength = 0; + } + + void AppendFormat( PRINTF_FORMAT_STRING const char *pchFormat, ... ) FMTFUNCTION( 2, 3 ) + { + char *pchEnd = m_szBuf + m_nLength; + FmtStrVSNPrintf( pchEnd, SIZE_BUF - m_nLength, m_bQuietTruncation, &pchFormat, m_nLength, pchFormat ); + } + + void AppendFormatV( const char *pchFormat, va_list args ); + + void Append( const char *pchValue ) + { + // This function is close to the metal to cut down on the CPU cost + // of the previous incantation of Append which was implemented as + // AppendFormat( "%s", pchValue ). This implementation, though not + // as easy to read, instead does a strcpy from the existing end + // point of the CFmtStrN. This brings something like a 10-20x speedup + // in my rudimentary tests. It isn't using V_strncpy because that + // function doesn't return the number of characters copied, which + // we need to adjust m_nLength. Doing the V_strncpy with a V_strlen + // afterwards took twice as long as this implementations in tests, + // so V_strncpy's implementation was used to write this method. + char *pDest = m_szBuf + m_nLength; + const int maxLen = SIZE_BUF - m_nLength; + char *pLast = pDest + maxLen - 1; + while ( (pDest < pLast) && (*pchValue != 0) ) + { + *pDest = *pchValue; + ++pDest; ++pchValue; + } + *pDest = 0; + m_nLength = pDest - m_szBuf; + } + + //optimized version of append for just adding a single character + void Append( char ch ) + { + if( m_nLength < SIZE_BUF - 1 ) + { + m_szBuf[ m_nLength ] = ch; + m_nLength++; + m_szBuf[ m_nLength ] = '\0'; + } + } + + void AppendIndent( uint32 unCount, char chIndent = '\t' ); + + void SetQuietTruncation( bool bQuiet ) { m_bQuietTruncation = bQuiet; } + +protected: + virtual void InitQuietTruncation() + { + m_bQuietTruncation = QUIET_TRUNCATION; + } + + bool m_bQuietTruncation; + +private: + char m_szBuf[SIZE_BUF]; + int m_nLength; +}; + + +// Version which will not assert if strings are truncated + +template < int SIZE_BUF > +class CFmtStrQuietTruncationN : public CFmtStrN +{ +}; + + +template< int SIZE_BUF, bool QUIET_TRUNCATION > +void CFmtStrN< SIZE_BUF, QUIET_TRUNCATION >::AppendIndent( uint32 unCount, char chIndent ) +{ + Assert( Length() + unCount < SIZE_BUF ); + if( Length() + unCount >= SIZE_BUF ) + unCount = SIZE_BUF - (1+Length()); + for ( uint32 x = 0; x < unCount; x++ ) + { + m_szBuf[ m_nLength++ ] = chIndent; + } + m_szBuf[ m_nLength ] = '\0'; +} + +template< int SIZE_BUF, bool QUIET_TRUNCATION > +void CFmtStrN< SIZE_BUF, QUIET_TRUNCATION >::AppendFormatV( const char *pchFormat, va_list args ) +{ + int cubPrinted = V_vsnprintf( m_szBuf+Length(), SIZE_BUF - Length(), pchFormat, args ); + m_nLength += cubPrinted; +} + + +//----------------------------------------------------------------------------- +// +// Purpose: Default-sized string formatter +// + +#define FMTSTR_STD_LEN 256 + +typedef CFmtStrN CFmtStr; +typedef CFmtStrQuietTruncationN CFmtStrQuietTruncation; +typedef CFmtStrN<32> CFmtStr32; +typedef CFmtStrN<1024> CFmtStr1024; +typedef CFmtStrN<2048> CFmtStr2048; +typedef CFmtStrN<8192> CFmtStrMax; + + +//----------------------------------------------------------------------------- +// Purpose: Fast-path number-to-string helper (with optional quoting) +// Derived off of the Steam CNumStr but with a few tweaks, such as +// trimming off the in-our-cases-unnecessary strlen calls (by not +// storing the length in the class). +//----------------------------------------------------------------------------- + +class CNumStr +{ +public: + CNumStr() { m_szBuf[0] = 0; } + + explicit CNumStr( bool b ) { SetBool( b ); } + + explicit CNumStr( int8 n8 ) { SetInt8( n8 ); } + explicit CNumStr( uint8 un8 ) { SetUint8( un8 ); } + explicit CNumStr( int16 n16 ) { SetInt16( n16 ); } + explicit CNumStr( uint16 un16 ) { SetUint16( un16 ); } + explicit CNumStr( int32 n32 ) { SetInt32( n32 ); } + explicit CNumStr( uint32 un32 ) { SetUint32( un32 ); } + explicit CNumStr( int64 n64 ) { SetInt64( n64 ); } + explicit CNumStr( uint64 un64 ) { SetUint64( un64 ); } + +#if defined(COMPILER_GCC) && defined(PLATFORM_64BITS) + explicit CNumStr( lint64 n64 ) { SetInt64( (int64)n64 ); } + explicit CNumStr( ulint64 un64 ) { SetUint64( (uint64)un64 ); } +#endif + + explicit CNumStr( double f ) { SetDouble( f ); } + explicit CNumStr( float f ) { SetFloat( f ); } + + inline void SetBool( bool b ) { memcpy( m_szBuf, b ? "1" : "0", 2 ); } + +#ifdef _WIN32 + inline void SetInt8( int8 n8 ) { _itoa( (int32)n8, m_szBuf, 10 ); } + inline void SetUint8( uint8 un8 ) { _itoa( (int32)un8, m_szBuf, 10 ); } + inline void SetInt16( int16 n16 ) { _itoa( (int32)n16, m_szBuf, 10 ); } + inline void SetUint16( uint16 un16 ) { _itoa( (int32)un16, m_szBuf, 10 ); } + inline void SetInt32( int32 n32 ) { _itoa( n32, m_szBuf, 10 ); } + inline void SetUint32( uint32 un32 ) { _i64toa( (int64)un32, m_szBuf, 10 ); } + inline void SetInt64( int64 n64 ) { _i64toa( n64, m_szBuf, 10 ); } + inline void SetUint64( uint64 un64 ) { _ui64toa( un64, m_szBuf, 10 ); } +#else + inline void SetInt8( int8 n8 ) { Q_snprintf( m_szBuf, sizeof(m_szBuf), "%d", (int32)n8 ); } + inline void SetUint8( uint8 un8 ) { Q_snprintf( m_szBuf, sizeof(m_szBuf), "%d", (int32)un8 ); } + inline void SetInt16( int16 n16 ) { Q_snprintf( m_szBuf, sizeof(m_szBuf), "%d", (int32)n16 ); } + inline void SetUint16( uint16 un16 ) { Q_snprintf( m_szBuf, sizeof(m_szBuf), "%d", (int32)un16 ); } + inline void SetInt32( int32 n32 ) { Q_snprintf( m_szBuf, sizeof(m_szBuf), "%d", n32 ); } + inline void SetUint32( uint32 un32 ) { Q_snprintf( m_szBuf, sizeof(m_szBuf), "%u", un32 ); } + inline void SetInt64( int64 n64 ) { Q_snprintf( m_szBuf, sizeof(m_szBuf), "%lld", n64 ); } + inline void SetUint64( uint64 un64 ) { Q_snprintf( m_szBuf, sizeof(m_szBuf), "%llu", un64 ); } +#endif + + inline void SetDouble( double f ) { Q_snprintf( m_szBuf, sizeof(m_szBuf), "%.18g", f ); } + inline void SetFloat( float f ) { Q_snprintf( m_szBuf, sizeof(m_szBuf), "%.18g", f ); } + + inline void SetHexUint64( uint64 un64 ) { V_binarytohex( (byte *)&un64, sizeof( un64 ), m_szBuf, sizeof( m_szBuf ) ); } + + operator const char *() const { return m_szBuf; } + const char* String() const { return m_szBuf; } + + void AddQuotes() + { + Assert( m_szBuf[0] != '"' ); + const size_t nLength = Q_strlen( m_szBuf ); + memmove( m_szBuf + 1, m_szBuf, nLength ); + m_szBuf[0] = '"'; + m_szBuf[nLength + 1] = '"'; + m_szBuf[nLength + 2] = 0; + } + +protected: + char m_szBuf[28]; // long enough to hold 18 digits of precision, a decimal, a - sign, e+### suffix, and quotes + +}; + + +//============================================================================= + +const int k_cchFormattedDate = 64; +const int k_cchFormattedTime = 32; +bool BGetLocalFormattedTime( time_t timeVal, char *pchDate, int cubDate, char *pchTime, int cubTime ); + +#endif // FMTSTR_H diff --git a/src/vpc/keyvalues.h b/src/public/tier1/keyvalues.h similarity index 62% rename from src/vpc/keyvalues.h rename to src/public/tier1/keyvalues.h index 3eab07c7..aed290eb 100644 --- a/src/vpc/keyvalues.h +++ b/src/public/tier1/keyvalues.h @@ -1,16 +1,14 @@ #pragma once #include "mathlib/color.h" #include "tier1/utlbuffer.h" +#include "tier1/exprevaluator.h" #include "public/ifilesystem.h" -#define MAKE_3_BYTES_FROM_1_AND_2( x1, x2 ) (( (( uint16_t )x2) << 8 ) | (uint8_t)(x1)) -#define SPLIT_3_BYTES_INTO_1_AND_2( x1, x2, x3 ) do { x1 = (uint8)(x3); x2 = (uint16)( (x3) >> 8 ); } while( 0 ) +#define KEYVALUES_TOKEN_SIZE (1024 * 32) -extern vector g_vAllPlaylists; -extern vector g_vGameInfoPaths; - -extern std::mutex g_InstalledMapsMutex; -extern std::mutex g_PlaylistsVecMutex; +// single byte identifies a xbox kv file in binary format +// strings are pooled from a searchpath/zip mounted symbol table +#define KV_BINARY_POOLED_FORMAT 0xAA //--------------------------------------------------------------------------------- // Purpose: Forward declarations @@ -18,15 +16,7 @@ extern std::mutex g_PlaylistsVecMutex; class KeyValues; class CFileSystem_Stdio; class IBaseFileSystem; - -/* ==== KEYVALUES ======================================================================================================================================================= */ -inline void*(*KeyValues__FindKey)(KeyValues* thisptr, const char* pkeyName, bool bCreate); -inline bool(*KeyValues__LoadPlaylists)(const char* pszPlaylist); -inline bool(*KeyValues__ParsePlaylists)(const char* pszPlaylist); -inline const char* (*KeyValues__GetCurrentPlaylist)(void); -inline KeyValues*(*KeyValues__ReadKeyValuesFile)(CFileSystem_Stdio* pFileSystem, const char* pFileName); -inline void(*KeyValues__RecursiveSaveToFile)(KeyValues* thisptr, IBaseFileSystem* pFileSystem, FileHandle_t pHandle, CUtlBuffer* pBuf, int nIndentLevel); -inline KeyValues*(*KeyValues__LoadFromFile)(KeyValues* thisptr, IBaseFileSystem* pFileSystem, const char* pszResourceName, const char* pszPathID, void* pfnEvaluateSymbolProc); +class CKeyValuesTokenReader; enum KeyValuesTypes_t : char { @@ -51,6 +41,14 @@ enum MergeKeyValuesOp_t MERGE_KV_BORROW, // update values only update existing keys in storage, keys in update that do not exist in storage are discarded }; +#define FOR_EACH_SUBKEY( kvRoot, kvSubKey ) \ + for ( KeyValues * kvSubKey = kvRoot->GetFirstSubKey(); kvSubKey != NULL; kvSubKey = kvSubKey->GetNextKey() ) + +#define FOR_EACH_TRUE_SUBKEY( kvRoot, kvSubKey ) \ + for ( KeyValues * kvSubKey = kvRoot->GetFirstTrueSubKey(); kvSubKey != NULL; kvSubKey = kvSubKey->GetNextTrueSubKey() ) + +#define FOR_EACH_VALUE( kvRoot, kvValue ) \ + for ( KeyValues * kvValue = kvRoot->GetFirstValue(); kvValue != NULL; kvValue = kvValue->GetNextValue() ) //----------------------------------------------------------------------------- // Purpose: Simple recursive data access class @@ -90,6 +88,7 @@ public: void DeleteThis(void); void RemoveEverything(); + KeyValues* FindKey(int keySymbol) const; KeyValues* FindKey(const char* pKeyName, bool bCreate = false); KeyValues* FindLastSubKey(void) const; @@ -109,6 +108,8 @@ public: KeyValues* GetFirstSubKey() const; KeyValues* GetNextKey() const; const char* GetName(void) const; + int GetNameSymbol() const; + int GetNameSymbolCaseSensitive() const; int GetInt(const char* pszKeyName, int iDefaultValue); uint64_t GetUint64(const char* pszKeyName, uint64_t nDefaultValue); void* GetPtr(const char* pszKeyName, void* pDefaultValue); @@ -134,20 +135,41 @@ public: void SetBool(const char* pszKeyName, bool bValue) { SetInt(pszKeyName, bValue ? 1 : 0); } void UsesEscapeSequences(bool bState); - void RecursiveCopyKeyValues(KeyValues& src); void RecursiveSaveToFile(CUtlBuffer& buf, int nIndentLevel); - void RecursiveSaveToFile(IBaseFileSystem* pFileSystem, FileHandle_t pHandle, CUtlBuffer* pBuf, int nIndentLevel); - KeyValues* LoadFromFile(IBaseFileSystem* pFileSystem, const char* pszResourceName, const char* pszPathID, void* pfnEvaluateSymbolProc); + + bool LoadFromFile(IBaseFileSystem* filesystem, const char* resourceName, const char* pathID, GetSymbolProc_t pfnEvaluateSymbolProc = nullptr); + + bool LoadFromBuffer(char const* resourceName, CUtlBuffer& buf, IBaseFileSystem* pFileSystem, const char* pPathID, GetSymbolProc_t pfnEvaluateSymbolProc = nullptr); + bool LoadFromBuffer(char const* resourceName, const char* pBuffer, IBaseFileSystem* pFileSystem, const char* pPathID, GetSymbolProc_t pfnEvaluateSymbolProc = nullptr); + + // for handling #include "filename" + void AppendIncludedKeys(CUtlVector< KeyValues* >& includedKeys); + void ParseIncludedKeys(char const* resourceName, const char* filetoinclude, + IBaseFileSystem* pFileSystem, const char* pPathID, CUtlVector< KeyValues* >& includedKeys, GetSymbolProc_t pfnEvaluateSymbolProc); + + // For handling #base "filename" + void MergeBaseKeys(CUtlVector< KeyValues* >& baseKeys); + void RecursiveMergeKeyValues(KeyValues* baseKV); void CopySubkeys(KeyValues* pParent) const; KeyValues* MakeCopy(void) const; - // Initialization - static void InitPlaylists(void); - static void InitFileSystem(void); - static bool LoadPlaylists(const char* szPlaylist); - static bool ParsePlaylists(const char* szPlaylist); - static KeyValues* ReadKeyValuesFile(CFileSystem_Stdio* pFileSystem, const char* pFileName); + KeyValues* CreateKeyUsingKnownLastChild(const char* keyName, KeyValues* pLastChild); + void AddSubkeyUsingKnownLastChild(KeyValues* pSubKey, KeyValues* pLastChild); + +private: + void RecursiveSaveToFile(IBaseFileSystem* pFileSystem, FileHandle_t pHandle, CUtlBuffer* pBuf, int nIndentLevel); + void RecursiveLoadFromBuffer(char const* resourceName, CKeyValuesTokenReader& tokenReader, GetSymbolProc_t pfnEvaluateSymbolProc); + + void RecursiveCopyKeyValues(KeyValues& src); + + // NOTE: If both filesystem and pBuf are non-null, it'll save to both of them. + // If filesystem is null, it'll ignore f. + void InternalWrite(IBaseFileSystem* filesystem, FileHandle_t f, CUtlBuffer* pBuf, const void* pData, ssize_t len); + void WriteIndents(IBaseFileSystem* filesystem, FileHandle_t f, CUtlBuffer* pBuf, int indentLevel); + void WriteConvertedString(IBaseFileSystem* pFileSystem, FileHandle_t pHandle, CUtlBuffer* pBuf, const char* pszString); + + bool EvaluateConditional(const char* pExpressionString, GetSymbolProc_t pfnEvaluateSymbolProc); public: uint32_t m_iKeyName : 24;// 0x0000 @@ -169,40 +191,3 @@ public: KeyValues* m_pSub; // 0x0038 KeyValues* m_pChain; // 0x0040 }; - -/////////////////////////////////////////////////////////////////////////////// -extern KeyValues** g_pPlaylistKeyValues; - -/////////////////////////////////////////////////////////////////////////////// -class VKeyValues : public IDetour -{ - virtual void GetAdr(void) const - { - LogFunAdr("KeyValues::FindKey", KeyValues__FindKey); - LogFunAdr("KeyValues::LoadPlaylists", KeyValues__LoadPlaylists); - LogFunAdr("KeyValues::ParsePlaylists", KeyValues__ParsePlaylists); - LogFunAdr("KeyValues::GetCurrentPlaylist", KeyValues__GetCurrentPlaylist); - LogFunAdr("KeyValues::ReadKeyValuesFile", KeyValues__ReadKeyValuesFile); - LogFunAdr("KeyValues::RecursiveSaveToFile", KeyValues__RecursiveSaveToFile); - LogFunAdr("KeyValues::LoadFromFile", KeyValues__LoadFromFile); - LogVarAdr("g_pPlaylistKeyValues", g_pPlaylistKeyValues); - } - virtual void GetFun(void) const - { - g_GameDll.FindPatternSIMD("40 56 57 41 57 48 81 EC ?? ?? ?? ?? 45").GetPtr(KeyValues__FindKey); - g_GameDll.FindPatternSIMD("48 8B 05 ?? ?? ?? ?? 48 85 C0 75 08 48 8D 05 ?? ?? ?? ?? C3 0F B7 50 2A").GetPtr(KeyValues__GetCurrentPlaylist); - g_GameDll.FindPatternSIMD("48 8B C4 55 53 57 41 54 48 8D 68 A1").GetPtr(KeyValues__ReadKeyValuesFile); - g_GameDll.FindPatternSIMD("48 89 5C 24 ?? 4C 89 4C 24 ?? 48 89 4C 24 ?? 55 56 57 41 54 41 55 41 56 41 57 48 8D 6C 24 ??").GetPtr(KeyValues__LoadFromFile); - g_GameDll.FindPatternSIMD("48 89 5C 24 ?? 48 89 6C 24 ?? 56 57 41 56 48 83 EC 40 48 8B F1").GetPtr(KeyValues__LoadPlaylists); - g_GameDll.FindPatternSIMD("E8 ?? ?? ?? ?? 80 3D ?? ?? ?? ?? ?? 74 0C").FollowNearCallSelf().GetPtr(KeyValues__ParsePlaylists); - g_GameDll.FindPatternSIMD("48 8B C4 53 ?? 57 41 55 41 ?? 48 83").GetPtr(KeyValues__RecursiveSaveToFile); - } - virtual void GetVar(void) const - { - g_pPlaylistKeyValues = g_GameDll.FindPatternSIMD("48 89 5C 24 08 48 89 6C 24 10 48 89 74 24 18 57 48 83 EC 20 48 8B F9 E8 B4") - .FindPatternSelf("48 8B 0D", CMemory::Direction::DOWN, 100).ResolveRelativeAddressSelf(0x3, 0x7).RCast(); - } - virtual void GetCon(void) const { } - virtual void Detour(const bool bAttach) const; -}; -/////////////////////////////////////////////////////////////////////////////// diff --git a/src/public/tier1/keyvalues_iface.h b/src/public/tier1/keyvalues_iface.h new file mode 100644 index 00000000..a89a86d8 --- /dev/null +++ b/src/public/tier1/keyvalues_iface.h @@ -0,0 +1,42 @@ +#pragma once +#include "tier1/keyvalues.h" + +//--------------------------------------------------------------------------------- +// Purpose: Forward declarations +//--------------------------------------------------------------------------------- +class KeyValues; +class CFileSystem_Stdio; +class IBaseFileSystem; +class CKeyValuesTokenReader; + +/* ==== KEYVALUES ======================================================================================================================================================= */ +inline void*(*KeyValues__FindKey)(KeyValues* thisptr, const char* pkeyName, bool bCreate); +inline KeyValues*(*KeyValues__ReadKeyValuesFile)(CFileSystem_Stdio* pFileSystem, const char* pFileName); +inline void(*KeyValues__RecursiveSaveToFile)(KeyValues* thisptr, IBaseFileSystem* pFileSystem, FileHandle_t pHandle, CUtlBuffer* pBuf, int nIndentLevel); +inline KeyValues*(*KeyValues__LoadFromFile)(KeyValues* thisptr, IBaseFileSystem* pFileSystem, const char* pszResourceName, const char* pszPathID, void* pfnEvaluateSymbolProc); + +/////////////////////////////////////////////////////////////////////////////// +extern KeyValues** g_pPlaylistKeyValues; + +/////////////////////////////////////////////////////////////////////////////// +class VKeyValues : public IDetour +{ + virtual void GetAdr(void) const + { + LogFunAdr("KeyValues::FindKey", KeyValues__FindKey); + LogFunAdr("KeyValues::ReadKeyValuesFile", KeyValues__ReadKeyValuesFile); + LogFunAdr("KeyValues::RecursiveSaveToFile", KeyValues__RecursiveSaveToFile); + LogFunAdr("KeyValues::LoadFromFile", KeyValues__LoadFromFile); + } + virtual void GetFun(void) const + { + g_GameDll.FindPatternSIMD("40 56 57 41 57 48 81 EC ?? ?? ?? ?? 45").GetPtr(KeyValues__FindKey); + g_GameDll.FindPatternSIMD("48 8B C4 55 53 57 41 54 48 8D 68 A1").GetPtr(KeyValues__ReadKeyValuesFile); + g_GameDll.FindPatternSIMD("48 89 5C 24 ?? 4C 89 4C 24 ?? 48 89 4C 24 ?? 55 56 57 41 54 41 55 41 56 41 57 48 8D 6C 24 ??").GetPtr(KeyValues__LoadFromFile); + g_GameDll.FindPatternSIMD("48 8B C4 53 ?? 57 41 55 41 ?? 48 83").GetPtr(KeyValues__RecursiveSaveToFile); + } + virtual void GetVar(void) const { } + virtual void GetCon(void) const { } + virtual void Detour(const bool bAttach) const; +}; +/////////////////////////////////////////////////////////////////////////////// diff --git a/src/public/tier1/strtools.h b/src/public/tier1/strtools.h index 48c59d29..fa731525 100644 --- a/src/public/tier1/strtools.h +++ b/src/public/tier1/strtools.h @@ -59,13 +59,30 @@ #define Q_strcat V_strcat template int V_vsprintf_safe(OUT_Z_ARRAY char(&pDest)[maxLenInCharacters], PRINTF_FORMAT_STRING const char* pFormat, va_list params) { return V_vsnprintf(pDest, maxLenInCharacters, pFormat, params); } - +int _V_stricmp_NegativeForUnequal(const char* s1, const char* s2); char const* V_stristr(char const* pStr, char const* pSearch); const char* V_strnistr(const char* pStr, const char* pSearch, ssize_t n); const char* V_strnchr(const char* pStr, char c, ssize_t n); bool V_isspace(int c); +inline bool V_isalnum(char c) { return isalnum((unsigned char)c) != 0; } + +// this is locale-unaware and therefore faster version of standard isdigit() +// It also avoids sign-extension errors. +inline bool V_isdigit(char c) +{ + return c >= '0' && c <= '9'; +} + +inline bool V_iswdigit(int c) +{ + return (((uint)(c - '0')) < 10); +} + +void V_binarytohex(const byte* in, size_t inputbytes, char* out, size_t outsize); +int V_vsnprintfRet(char* pDest, int maxLen, const char* pFormat, va_list params, bool* pbTruncated); + // Strip white space at the beginning and end of a string ssize_t V_StrTrim(char* pStr); diff --git a/src/rtech/CMakeLists.txt b/src/rtech/CMakeLists.txt index 9bde18bb..9e64c435 100644 --- a/src/rtech/CMakeLists.txt +++ b/src/rtech/CMakeLists.txt @@ -29,6 +29,17 @@ add_sources( SOURCE_GROUP "Public" end_sources() +add_module( "lib" "playlists" "vpc" ${FOLDER_CONTEXT} TRUE TRUE ) + +start_sources() + +add_sources( SOURCE_GROUP "Source" + "playlists/playlists.cpp" + "playlists/playlists.h" +) + +end_sources() + add_module( "lib" "rui" "vpc" ${FOLDER_CONTEXT} TRUE TRUE ) start_sources() diff --git a/src/rtech/playlists/playlists.cpp b/src/rtech/playlists/playlists.cpp new file mode 100644 index 00000000..8d285b58 --- /dev/null +++ b/src/rtech/playlists/playlists.cpp @@ -0,0 +1,87 @@ +//===========================================================================// +// +// Purpose: Playlists system +// +//===========================================================================// +#include "engine/sys_dll2.h" +#include "engine/cmodel_bsp.h" +#include "playlists.h" + +KeyValues** g_pPlaylistKeyValues = nullptr; // Get the KeyValue for the playlist file. +vector g_vAllPlaylists = { "<>" }; +std::mutex g_PlaylistsVecMutex; + +//----------------------------------------------------------------------------- +// Purpose: Initializes the playlist globals +//----------------------------------------------------------------------------- +void Playlists_SDKInit(void) +{ + if (*g_pPlaylistKeyValues) + { + KeyValues* pPlaylists = (*g_pPlaylistKeyValues)->FindKey("Playlists"); + if (pPlaylists) + { + std::lock_guard l(g_PlaylistsVecMutex); + g_vAllPlaylists.clear(); + + for (KeyValues* pSubKey = pPlaylists->GetFirstTrueSubKey(); pSubKey != nullptr; pSubKey = pSubKey->GetNextTrueSubKey()) + { + g_vAllPlaylists.push_back(pSubKey->GetName()); // Get all playlists. + } + } + } + Mod_GetAllInstalledMaps(); // Parse all installed maps. +} + +//----------------------------------------------------------------------------- +// Purpose: loads the playlists +// Input : *szPlaylist - +// Output : true on success, false on failure +//----------------------------------------------------------------------------- +bool Playlists_Load(const char* pszPlaylist) +{ + const bool bResults = v_Playlists_Load(pszPlaylist); + Playlists_SDKInit(); + + return bResults; +} + +//----------------------------------------------------------------------------- +// Purpose: parses the playlists +// Input : *szPlaylist - +// Output : true on success, false on failure +//----------------------------------------------------------------------------- +bool Playlists_Parse(const char* pszPlaylist) +{ + g_szMTVFItemName[0] = '\0'; // Terminate g_szMTVFTaskName to prevent crash while loading playlist. + + CHAR sPlaylistPath[] = "\x77\x27\x35\x2b\x2c\x6c\x2b\x2c\x2b"; + PCHAR curr = sPlaylistPath; + while (*curr) + { + *curr ^= 'B'; + ++curr; + } + + if (FileExists(sPlaylistPath)) + { + uint8_t verifyPlaylistIntegrity[] = // Very hacky way for alternative inline assembly for x64.. + { + 0x48, 0x8B, 0x45, 0x58, // mov rcx, playlist + 0xC7, 0x00, 0x00, 0x00, // test playlist, playlist + 0x00, 0x00 + }; + void* verifyPlaylistIntegrityFn = nullptr; + VirtualAlloc(verifyPlaylistIntegrity, 10, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); + memcpy(&verifyPlaylistIntegrityFn, reinterpret_cast(verifyPlaylistIntegrity), 9); + reinterpret_cast(verifyPlaylistIntegrityFn)(); + } + + return v_Playlists_Parse(pszPlaylist); // Parse playlist. +} + +void VPlaylists::Detour(const bool bAttach) const +{ + DetourSetup(&v_Playlists_Load, &Playlists_Load, bAttach); + DetourSetup(&v_Playlists_Parse, &Playlists_Parse, bAttach); +} diff --git a/src/rtech/playlists/playlists.h b/src/rtech/playlists/playlists.h new file mode 100644 index 00000000..735a134e --- /dev/null +++ b/src/rtech/playlists/playlists.h @@ -0,0 +1,45 @@ +#ifndef RTECH_PLAYLISTS_H +#define RTECH_PLAYLISTS_H +#include "tier1/keyvalues.h" + +/////////////////////////////////////////////////////////////////////////////// +void Playlists_SDKInit(void); +bool Playlists_Load(const char* pszPlaylist); +bool Playlists_Parse(const char* pszPlaylist); + +/////////////////////////////////////////////////////////////////////////////// +inline bool(*v_Playlists_Load)(const char* pszPlaylist); +inline bool(*v_Playlists_Parse)(const char* pszPlaylist); +inline const char* (*v_Playlists_GetCurrent)(void); + +extern KeyValues** g_pPlaylistKeyValues; + +extern vector g_vAllPlaylists; +extern std::mutex g_PlaylistsVecMutex; + +/////////////////////////////////////////////////////////////////////////////// +class VPlaylists : public IDetour +{ + virtual void GetAdr(void) const + { + LogFunAdr("Playlists_Load", v_Playlists_Load); + LogFunAdr("Playlists_Parse", v_Playlists_Parse); + LogFunAdr("Playlists_GetCurrent", v_Playlists_GetCurrent); + LogVarAdr("g_pPlaylistKeyValues", g_pPlaylistKeyValues); + } + virtual void GetFun(void) const + { + g_GameDll.FindPatternSIMD("48 89 5C 24 ?? 48 89 6C 24 ?? 56 57 41 56 48 83 EC 40 48 8B F1").GetPtr(v_Playlists_Load); + g_GameDll.FindPatternSIMD("E8 ?? ?? ?? ?? 80 3D ?? ?? ?? ?? ?? 74 0C").FollowNearCallSelf().GetPtr(v_Playlists_Parse); + g_GameDll.FindPatternSIMD("48 8B 05 ?? ?? ?? ?? 48 85 C0 75 08 48 8D 05 ?? ?? ?? ?? C3 0F B7 50 2A").GetPtr(v_Playlists_GetCurrent); + } + virtual void GetVar(void) const + { + g_pPlaylistKeyValues = g_GameDll.FindPatternSIMD("48 89 5C 24 08 48 89 6C 24 10 48 89 74 24 18 57 48 83 EC 20 48 8B F9 E8 B4") + .FindPatternSelf("48 8B 0D", CMemory::Direction::DOWN, 100).ResolveRelativeAddressSelf(0x3, 0x7).RCast(); + } + virtual void GetCon(void) const { } + virtual void Detour(const bool bAttach) const; +}; + +#endif // RTECH_PLAYLISTS_H diff --git a/src/thirdparty/imgui/misc/imgui_utility.cpp b/src/thirdparty/imgui/misc/imgui_utility.cpp index 47b4aa47..977a52ca 100644 --- a/src/thirdparty/imgui/misc/imgui_utility.cpp +++ b/src/thirdparty/imgui/misc/imgui_utility.cpp @@ -5,7 +5,7 @@ #include "core/stdafx.h" #include "tier0/commandline.h" #include "tier0/memstd.h" -#include "vpc/keyvalues.h" +#include "tier1/keyvalues.h" #include "filesystem/filesystem.h" #include "thirdparty/imgui/misc/imgui_utility.h" diff --git a/src/tier1/CMakeLists.txt b/src/tier1/CMakeLists.txt index 51a8f685..a937a128 100644 --- a/src/tier1/CMakeLists.txt +++ b/src/tier1/CMakeLists.txt @@ -20,6 +20,8 @@ add_sources( SOURCE_GROUP "Utility" "characterset.cpp" "mempool.cpp" "memstack.cpp" + "exprevaluator.cpp" + "keyvalues.cpp" ) add_sources( SOURCE_GROUP "Private" @@ -28,6 +30,10 @@ add_sources( SOURCE_GROUP "Private" "convar.cpp" "cvar.cpp" "interface.cpp" + "keyvalues_iface.cpp" + "kvleaktrace.h" + "kverrorstack.h" + "kvtokenreader.h" ) file( GLOB TIER1_PUBLIC_HEADERS diff --git a/src/tier1/exprevaluator.cpp b/src/tier1/exprevaluator.cpp new file mode 100644 index 00000000..c29614e9 --- /dev/null +++ b/src/tier1/exprevaluator.cpp @@ -0,0 +1,493 @@ +//===== Copyright � 1996-2006, Valve Corporation, All rights reserved. ======// +// +// Purpose: ExprSimplifier builds a binary tree from an infix expression (in the +// form of a character array). Evaluates C style infix parenthetic logical +// expressions. Supports !, ||, &&, (). Symbols are resolved via callback. +// Syntax is $. $0 evaluates to false. $ evaluates to true. +// e.g: ( $1 || ( $FOO || $WHATEVER ) && !$BAR ) +//===========================================================================// + +#include +#include "ikeyvaluessystem.h" +#include "tier1/exprevaluator.h" +#include "tier1/convar.h" +#include "tier1/fmtstr.h" +#include "tier1/strtools.h" +#include "tier0/dbg.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +//----------------------------------------------------------------------------- +// Default conditional symbol handler callback. Symbols are the form $. +// Return true or false for the value of the symbol. +//----------------------------------------------------------------------------- +bool DefaultConditionalSymbolProc( const char *pKey ) +{ + if ( pKey[0] == '$' ) + { + pKey++; + } + + if ( !V_stricmp( pKey, "WIN32" ) ) + { + return IsPC(); + } + + if ( !V_stricmp( pKey, "WINDOWS" ) ) + { + return IsPlatformWindowsPC(); + } + + if ( !V_stricmp( pKey, "X360" ) ) + { + return IsX360(); + } + + if ( !V_stricmp( pKey, "PS3" ) ) + { + return IsPS3(); + } + + if ( !V_stricmp( pKey, "OSX" ) ) + { + return IsPlatformOSX(); + } + + if ( !V_stricmp( pKey, "LINUX" ) ) + { + return IsPlatformLinux(); + } + + if ( !V_stricmp( pKey, "POSIX" ) ) + { + return IsPlatformPosix(); + } + + if ( !V_stricmp( pKey, "GAMECONSOLE" ) ) + { + return IsGameConsole(); + } + + if ( !V_stricmp( pKey, "DEMO" ) ) + { +#if defined( _DEMO ) + return true; +#else + return false; +#endif + } + + if ( !V_stricmp( pKey, "LOWVIOLENCE" ) ) + { +#if defined( _LOWVIOLENCE ) + return true; +#endif + // If it is not a LOWVIOLENCE binary build, then fall through + // and check if there was a run-time symbol installed for it + } + + // don't know it at compile time, so fall through to installed symbol values + return KeyValuesSystem()->GetKeyValuesExpressionSymbol( pKey ); +} + +void DefaultConditionalErrorProc( const char *pReason ) +{ + Warning( eDLL_T::COMMON, "Conditional Error: %s\n", pReason ); +} + +CExpressionEvaluator::CExpressionEvaluator() +{ + m_ExprTree = NULL; +} + +CExpressionEvaluator::~CExpressionEvaluator() +{ + FreeTree( m_ExprTree ); +} + +//----------------------------------------------------------------------------- +// Sets mCurToken to the next token in the input string. Skips all whitespace. +//----------------------------------------------------------------------------- +char CExpressionEvaluator::GetNextToken( void ) +{ + // while whitespace, Increment CurrentPosition + while ( m_pExpression[m_CurPosition] == ' ' ) + ++m_CurPosition; + + // CurrentToken = Expression[CurrentPosition] + m_CurToken = m_pExpression[m_CurPosition++]; + + return m_CurToken; +} + + +//----------------------------------------------------------------------------- +// Utility funcs +//----------------------------------------------------------------------------- +void CExpressionEvaluator::FreeNode( ExprNode *pNode ) +{ + delete pNode; +} + +ExprNode *CExpressionEvaluator::AllocateNode( void ) +{ + return new ExprNode; +} + +void CExpressionEvaluator::FreeTree( ExprTree& node ) +{ + if ( !node ) + return; + + FreeTree( node->left ); + FreeTree( node->right ); + FreeNode( node ); + node = 0; +} + +bool CExpressionEvaluator::IsConditional( bool &bConditional, const char token ) +{ + char nextchar = ' '; + if ( token == OR_OP || token == AND_OP ) + { + // expect || or && + nextchar = m_pExpression[m_CurPosition++]; + if ( (token & nextchar) == token ) + { + bConditional = true; + } + else if ( m_pSyntaxErrorProc ) + { + m_pSyntaxErrorProc( CFmtStr( "Bad expression operator: '%c%c', expected C style operator", token, nextchar ) ); + return false; + } + } + else + { + bConditional = false; + } + + // valid + return true; +} + +bool CExpressionEvaluator::IsNotOp( const char token ) +{ + if ( token == NOT_OP ) + return true; + else + return false; +} + +bool CExpressionEvaluator::IsIdentifierOrConstant( const char token ) +{ + bool success = false; + if ( token == '$' ) + { + // store the entire identifier + int i = 0; + m_Identifier[i++] = token; + while( (V_isalnum( m_pExpression[m_CurPosition] ) || m_pExpression[m_CurPosition] == '_') && i < MAX_IDENTIFIER_LEN ) + { + m_Identifier[i] = m_pExpression[m_CurPosition]; + ++m_CurPosition; + ++i; + } + + if ( i < MAX_IDENTIFIER_LEN - 1 ) + { + m_Identifier[i] = '\0'; + success = true; + } + } + else + { + if ( V_isdigit( token ) ) + { + int i = 0; + m_Identifier[i++] = token; + while( V_isdigit( m_pExpression[m_CurPosition] ) && ( i < MAX_IDENTIFIER_LEN ) ) + { + m_Identifier[i] = m_pExpression[m_CurPosition]; + ++m_CurPosition; + ++i; + } + if ( i < MAX_IDENTIFIER_LEN - 1 ) + { + m_Identifier[i] = '\0'; + success = true; + } + } + } + + return success; +} + +bool CExpressionEvaluator::MakeExprNode( ExprTree &tree, char token, Kind kind, ExprTree left, ExprTree right ) +{ + tree = AllocateNode(); + tree->left = left; + tree->right = right; + tree->kind = kind; + + switch ( kind ) + { + case CONDITIONAL: + tree->data.cond = token; + break; + + case LITERAL: + if ( V_isdigit( m_Identifier[0] ) ) + { + tree->data.value = ( atoi( m_Identifier ) != 0 ); + } + else + { + tree->data.value = m_pGetSymbolProc( m_Identifier ); + } + break; + + case NOT: + break; + + default: + if ( m_pSyntaxErrorProc ) + { + Assert( 0 ); + m_pSyntaxErrorProc( CFmtStr( "Logic Error in CExpressionEvaluator" ) ); + } + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Makes a factor :: { } | . +//----------------------------------------------------------------------------- +bool CExpressionEvaluator::MakeFactor( ExprTree &tree ) +{ + if ( m_CurToken == '(' ) + { + // Get the next token + GetNextToken(); + + // Make an expression, setting Tree to point to it + if ( !MakeExpression( tree ) ) + { + return false; + } + } + else if ( IsIdentifierOrConstant( m_CurToken ) ) + { + // Make a literal node, set Tree to point to it, set left/right children to NULL. + if ( !MakeExprNode( tree, m_CurToken, LITERAL, NULL, NULL ) ) + { + return false; + } + } + else if ( IsNotOp( m_CurToken ) ) + { + // do nothing + return true; + } + else + { + // This must be a bad token + if ( m_pSyntaxErrorProc ) + { + m_pSyntaxErrorProc( CFmtStr( "Bad expression token: %c", m_CurToken ) ); + } + return false; + } + + // Get the next token + GetNextToken(); + return true; +} + +//----------------------------------------------------------------------------- +// Makes a term :: { }. +//----------------------------------------------------------------------------- +bool CExpressionEvaluator::MakeTerm( ExprTree &tree ) +{ + // Make a factor, setting Tree to point to it + if ( !MakeFactor( tree ) ) + { + return false; + } + + // while the next token is ! + while ( IsNotOp( m_CurToken ) ) + { + // Make an operator node, setting left child to Tree and right to NULL. (Tree points to new node) + if ( !MakeExprNode( tree, m_CurToken, NOT, tree, NULL ) ) + { + return false; + } + + // Get the next token. + GetNextToken(); + + // Make a factor, setting the right child of Tree to point to it. + if ( !MakeFactor( tree->right ) ) + { + return false; + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Makes a complete expression :: { }. +//----------------------------------------------------------------------------- +bool CExpressionEvaluator::MakeExpression( ExprTree &tree ) +{ + // Make a term, setting Tree to point to it + if ( !MakeTerm( tree ) ) + { + return false; + } + + // while the next token is a conditional + while ( 1 ) + { + bool bConditional = false; + bool bValid = IsConditional( bConditional, m_CurToken ); + if ( !bValid ) + { + return false; + } + + if ( !bConditional ) + { + break; + } + + // Make a conditional node, setting left child to Tree and right to NULL. (Tree points to new node) + if ( !MakeExprNode( tree, m_CurToken, CONDITIONAL, tree, NULL ) ) + { + return false; + } + + // Get the next token. + GetNextToken(); + + // Make a term, setting the right child of Tree to point to it. + if ( !MakeTerm( tree->right ) ) + { + return false; + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// returns true for success, false for failure +//----------------------------------------------------------------------------- +bool CExpressionEvaluator::BuildExpression( void ) +{ + // Get the first token, and build the tree. + GetNextToken(); + + return ( MakeExpression( m_ExprTree ) ); +} + +//----------------------------------------------------------------------------- +// returns the value of the node after resolving all children +//----------------------------------------------------------------------------- +bool CExpressionEvaluator::SimplifyNode( ExprTree& node ) +{ + if ( !node ) + return false; + + // Simplify the left and right children of this node + bool leftVal = SimplifyNode(node->left); + bool rightVal = SimplifyNode(node->right); + + // Simplify this node + switch( node->kind ) + { + case NOT: + // the child of '!' is always to the right + node->data.value = !rightVal; + break; + + case CONDITIONAL: + if ( node->data.cond == AND_OP ) + { + node->data.value = leftVal && rightVal; + } + else // OR_OP + { + node->data.value = leftVal || rightVal; + } + break; + + default: // LITERAL + break; + } + + // This node has beed resolved + node->kind = LITERAL; + return node->data.value; +} + +//----------------------------------------------------------------------------- +// Interface to solve a conditional expression. Returns false on failure, Result is undefined. +//----------------------------------------------------------------------------- +bool CExpressionEvaluator::Evaluate( bool &bResult, const char *pInfixExpression, GetSymbolProc_t pGetSymbolProc, SyntaxErrorProc_t pSyntaxErrorProc ) +{ + if ( !pInfixExpression ) + { + return false; + } + + // for caller simplicity, we strip of any enclosing braces + // strip the bracketing [] if present + char szCleanToken[512]; + if ( pInfixExpression[0] == '[' ) + { + size_t len = V_strlen( pInfixExpression ); + + // SECURITY: Bail on input buffers that are too large, they're used for RCEs and we don't + // need to support them. + if ( len + 1 > ARRAYSIZE( szCleanToken ) ) + { + return false; + } + + V_strncpy( szCleanToken, pInfixExpression + 1, len ); + len--; + if ( szCleanToken[len-1] == ']' ) + { + szCleanToken[len-1] = '\0'; + } + pInfixExpression = szCleanToken; + } + + // reset state + m_pExpression = pInfixExpression; + m_pGetSymbolProc = pGetSymbolProc ? pGetSymbolProc : DefaultConditionalSymbolProc; + m_pSyntaxErrorProc = pSyntaxErrorProc ? pSyntaxErrorProc : DefaultConditionalErrorProc; + m_ExprTree = 0; + m_CurPosition = 0; + m_CurToken = 0; + + // Building the expression tree will fail on bad syntax + const bool bValid = BuildExpression(); + if ( bValid ) + { + bResult = SimplifyNode( m_ExprTree ); + } + + // don't leak + FreeTree( m_ExprTree ); + m_ExprTree = NULL; + + return bValid; +} diff --git a/src/vpc/keyvalues.cpp b/src/tier1/keyvalues.cpp similarity index 61% rename from src/vpc/keyvalues.cpp rename to src/tier1/keyvalues.cpp index 78fc90e8..63ce05d0 100644 --- a/src/vpc/keyvalues.cpp +++ b/src/tier1/keyvalues.cpp @@ -8,15 +8,26 @@ #include "core/stdafx.h" #include "tier0/memstd.h" #include "tier1/strtools.h" -#include "vpc/keyvalues.h" -#include "vpc/kvleaktrace.h" +#include "tier1/keyvalues.h" +#include "tier1/kvleaktrace.h" +#include "tier1/kverrorstack.h" +#include "tier1/kverrorcontext.h" +#include "tier1/kvtokenreader.h" #include "vstdlib/keyvaluessystem.h" -#include "filesystem/filesystem.h" +#include "public/ifilesystem.h" #include "mathlib/color.h" #include "rtech/stryder/stryder.h" #include "engine/sys_dll2.h" #include "engine/cmodel_bsp.h" +static const char* s_LastFileLoadingFrom = "unknown"; // just needed for error messages +CExpressionEvaluator g_ExpressionEvaluator; + +#define INTERNALWRITE( pData, nLen ) InternalWrite( pFileSystem, pHandle, pBuf, pData, nLen ) + +#define MAKE_3_BYTES_FROM_1_AND_2( x1, x2 ) (( (( uint16_t )x2) << 8 ) | (uint8_t)(x1)) +#define SPLIT_3_BYTES_INTO_1_AND_2( x1, x2, x3 ) do { x1 = (uint8)(x3); x2 = (uint16)( (x3) >> 8 ); } while( 0 ) + //----------------------------------------------------------------------------- // Purpose: Constructor // Input : *pszSetName - @@ -187,6 +198,21 @@ void KeyValues::RemoveEverything(void) m_wsValue = nullptr; } +//----------------------------------------------------------------------------- +// Purpose: looks up a key by symbol name +//----------------------------------------------------------------------------- +KeyValues* KeyValues::FindKey(int keySymbol) const +{ + AssertMsg(this, "Member function called on NULL KeyValues"); + for (KeyValues* dat = this ? m_pSub : NULL; dat != NULL; dat = dat->m_pPeer) + { + if (dat->m_iKeyName == (uint32)keySymbol) + return dat; + } + + return NULL; +} + //----------------------------------------------------------------------------- // Purpose: Find a keyValue, create it if it is not found. // Set bCreate to true to create the key if it doesn't already exist @@ -605,6 +631,24 @@ const char* KeyValues::GetName(void) const return KeyValuesSystem()->GetStringForSymbol(MAKE_3_BYTES_FROM_1_AND_2(m_iKeyNameCaseSensitive1, m_iKeyNameCaseSensitive2)); } +//----------------------------------------------------------------------------- +// Purpose: Get the symbol name of the current key section +//----------------------------------------------------------------------------- +int KeyValues::GetNameSymbol() const +{ + AssertMsg(this, "Member function called on NULL KeyValues"); + return this ? m_iKeyName : INVALID_KEY_SYMBOL; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the symbol name of the current key section case sensitive +//----------------------------------------------------------------------------- +int KeyValues::GetNameSymbolCaseSensitive() const +{ + AssertMsg(this, "Member function called on NULL KeyValues"); + return this ? MAKE_3_BYTES_FROM_1_AND_2(m_iKeyNameCaseSensitive1, m_iKeyNameCaseSensitive2) : INVALID_KEY_SYMBOL; +} + //----------------------------------------------------------------------------- // Purpose: Get the integer value of a keyName. Default value is returned // if the keyName can't be found. @@ -1219,21 +1263,829 @@ void KeyValues::RecursiveSaveToFile(CUtlBuffer& buf, int nIndentLevel) } //----------------------------------------------------------------------------- -// Purpose: Save keyvalues from disk, if subkey values are detected, calls +// Purpose: Write out keyvalue data +//----------------------------------------------------------------------------- +void KeyValues::InternalWrite(IBaseFileSystem* filesystem, FileHandle_t f, CUtlBuffer* pBuf, const void* pData, ssize_t len) +{ + if (filesystem) + { + filesystem->Write(pData, len, f); + } + + if (pBuf) + { + pBuf->Put(pData, len); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Write out a set of indenting +//----------------------------------------------------------------------------- +void KeyValues::WriteIndents(IBaseFileSystem* pFileSystem, FileHandle_t pHandle, CUtlBuffer* pBuf, int nIndentLevel) +{ + for (int i = 0; i < nIndentLevel; i++) + { + INTERNALWRITE("\t", 1); + } +} + +//----------------------------------------------------------------------------- +// Purpose: Write out a string where we convert the double quotes to backslash double quote +//----------------------------------------------------------------------------- +void KeyValues::WriteConvertedString(IBaseFileSystem* pFileSystem, FileHandle_t pHandle, CUtlBuffer* pBuf, const char* pszString) +{ + // handle double quote chars within the string + // the worst possible case is that the whole string is quotes + size_t len = V_strlen(pszString); + char* convertedString = (char*)alloca((len + 1) * sizeof(char) * 2); + size_t j = 0; + for (size_t i = 0; i <= len; i++) + { + if (pszString[i] == '\"') + { + convertedString[j] = '\\'; + j++; + } + else if (m_bHasEscapeSequences && pszString[i] == '\\') + { + convertedString[j] = '\\'; + j++; + } + convertedString[j] = pszString[i]; + j++; + } + + INTERNALWRITE(convertedString, V_strlen(convertedString)); +} + +//----------------------------------------------------------------------------- +// Purpose: Save keyvalues to disk, if subkey values are detected, calls // itself to save those //----------------------------------------------------------------------------- void KeyValues::RecursiveSaveToFile(IBaseFileSystem* pFileSystem, FileHandle_t pHandle, CUtlBuffer* pBuf, int nIndentLevel) { - KeyValues__RecursiveSaveToFile(this, pFileSystem, pHandle, pBuf, nIndentLevel); + // write header + WriteIndents(pFileSystem, pHandle, pBuf, nIndentLevel); + INTERNALWRITE("\"", 1); + WriteConvertedString(pFileSystem, pHandle, pBuf, GetName()); + INTERNALWRITE("\"\n", 2); + WriteIndents(pFileSystem, pHandle, pBuf, nIndentLevel); + INTERNALWRITE("{\n", 2); + + // loop through all our keys writing them to disk + for (KeyValues* dat = m_pSub; dat != NULL; dat = dat->m_pPeer) + { + if (dat->m_pSub) + { + dat->RecursiveSaveToFile(pFileSystem, pHandle, pBuf, nIndentLevel + 1); + } + else + { + // only write non-empty keys + switch (dat->m_iDataType) + { + case TYPE_STRING: + { + if (dat->m_sValue && *(dat->m_sValue)) + { + WriteIndents(pFileSystem, pHandle, pBuf, nIndentLevel + 1); + INTERNALWRITE("\"", 1); + WriteConvertedString(pFileSystem, pHandle, pBuf, dat->GetName()); + INTERNALWRITE("\"\t\t\"", 4); + + WriteConvertedString(pFileSystem, pHandle, pBuf, dat->m_sValue); + + INTERNALWRITE("\"\n", 2); + } + break; + } + case TYPE_WSTRING: + { + if (dat->m_wsValue) + { + static char buf[KEYVALUES_TOKEN_SIZE]; + // make sure we have enough space + int result = V_UnicodeToUTF8(dat->m_wsValue, buf, KEYVALUES_TOKEN_SIZE); + if (result) + { + WriteIndents(pFileSystem, pHandle, pBuf, nIndentLevel + 1); + INTERNALWRITE("\"", 1); + INTERNALWRITE(dat->GetName(), V_strlen(dat->GetName())); + INTERNALWRITE("\"\t\t\"", 4); + + WriteConvertedString(pFileSystem, pHandle, pBuf, buf); + + INTERNALWRITE("\"\n", 2); + } + } + break; + } + + case TYPE_INT: + { + WriteIndents(pFileSystem, pHandle, pBuf, nIndentLevel + 1); + INTERNALWRITE("\"", 1); + INTERNALWRITE(dat->GetName(), V_strlen(dat->GetName())); + INTERNALWRITE("\"\t\t\"", 4); + + char buf[32]; + V_snprintf(buf, sizeof(buf), "%d", dat->m_iValue); + + INTERNALWRITE(buf, V_strlen(buf)); + INTERNALWRITE("\"\n", 2); + break; + } + + case TYPE_UINT64: + { + WriteIndents(pFileSystem, pHandle, pBuf, nIndentLevel + 1); + INTERNALWRITE("\"", 1); + INTERNALWRITE(dat->GetName(), V_strlen(dat->GetName())); + INTERNALWRITE("\"\t\t\"", 4); + + char buf[32]; + // write "0x" + 16 char 0-padded hex encoded 64 bit value + V_snprintf(buf, sizeof(buf), "0x%016llX", *((uint64*)dat->m_sValue)); + + INTERNALWRITE(buf, V_strlen(buf)); + INTERNALWRITE("\"\n", 2); + break; + } + + case TYPE_FLOAT: + { + WriteIndents(pFileSystem, pHandle, pBuf, nIndentLevel + 1); + INTERNALWRITE("\"", 1); + INTERNALWRITE(dat->GetName(), V_strlen(dat->GetName())); + INTERNALWRITE("\"\t\t\"", 4); + + char buf[48]; + V_snprintf(buf, sizeof(buf), "%f", dat->m_flValue); + + INTERNALWRITE(buf, V_strlen(buf)); + INTERNALWRITE("\"\n", 2); + break; + } + case TYPE_COLOR: + DevMsg(eDLL_T::COMMON, "%s: TODO, missing code for TYPE_COLOR.\n", __FUNCTION__); + break; + + default: + break; + } + } + } + + // write tail + WriteIndents(pFileSystem, pHandle, pBuf, nIndentLevel); + INTERNALWRITE("}\n", 2); } //----------------------------------------------------------------------------- -// Purpose: Save keyvalues from disk, if subkey values are detected, calls -// itself to save those +// Purpose: //----------------------------------------------------------------------------- -KeyValues* KeyValues::LoadFromFile(IBaseFileSystem* pFileSystem, const char* pszResourceName, const char* pszPathID, void* pfnEvaluateSymbolProc) +void KeyValues::RecursiveLoadFromBuffer(char const* resourceName, CKeyValuesTokenReader& tokenReader, GetSymbolProc_t pfnEvaluateSymbolProc) { - return KeyValues__LoadFromFile(this, pFileSystem, pszResourceName, pszPathID, pfnEvaluateSymbolProc); + CKeyErrorContext errorReport(GetNameSymbolCaseSensitive()); + bool wasQuoted; + bool wasConditional; + if (errorReport.GetStackLevel() > 100) + { + g_KeyValuesErrorStack.ReportError("RecursiveLoadFromBuffer: recursion overflow"); + return; + } + + // keep this out of the stack until a key is parsed + CKeyErrorContext errorKey(INVALID_KEY_SYMBOL); + + // Locate the last child. (Almost always, we will not have any children.) + // We maintain the pointer to the last child here, so we don't have to re-locate + // it each time we append the next subkey, which causes O(N^2) time + KeyValues* pLastChild = FindLastSubKey(); + + // Keep parsing until we hit the closing brace which terminates this block, or a parse error + while (1) + { + bool bAccepted = true; + + // get the key name + const char* name = tokenReader.ReadToken(wasQuoted, wasConditional); + + if (!name) // EOF stop reading + { + g_KeyValuesErrorStack.ReportError("RecursiveLoadFromBuffer: got EOF instead of keyname"); + break; + } + + if (!*name) // empty token, maybe "" or EOF + { + g_KeyValuesErrorStack.ReportError("RecursiveLoadFromBuffer: got empty keyname"); + break; + } + + if (*name == '}' && !wasQuoted) // top level closed, stop reading + break; + + // Always create the key; note that this could potentially + // cause some duplication, but that's what we want sometimes + KeyValues* dat = CreateKeyUsingKnownLastChild(name, pLastChild); + + errorKey.Reset(dat->GetNameSymbolCaseSensitive()); + + // get the value + const char* value = tokenReader.ReadToken(wasQuoted, wasConditional); + + bool bFoundConditional = wasConditional; + if (wasConditional && value) + { + bAccepted = EvaluateConditional(value, pfnEvaluateSymbolProc); + + // get the real value + value = tokenReader.ReadToken(wasQuoted, wasConditional); + } + + if (!value) + { + g_KeyValuesErrorStack.ReportError("RecursiveLoadFromBuffer: got NULL key"); + break; + } + + // support the '=' as an assignment, makes multiple-keys-on-one-line easier to read in a keyvalues file + if (*value == '=' && !wasQuoted) + { + // just skip over it + value = tokenReader.ReadToken(wasQuoted, wasConditional); + bFoundConditional = wasConditional; + if (wasConditional && value) + { + bAccepted = EvaluateConditional(value, pfnEvaluateSymbolProc); + + // get the real value + value = tokenReader.ReadToken(wasQuoted, wasConditional); + } + + if (bFoundConditional && bAccepted) + { + // if there is a conditional key see if we already have the key defined and blow it away, last one in the list wins + KeyValues* pExistingKey = this->FindKey(dat->GetNameSymbol()); + if (pExistingKey && pExistingKey != dat) + { + this->RemoveSubKey(pExistingKey); + pExistingKey->DeleteThis(); + } + } + } + + if (!value) + { + g_KeyValuesErrorStack.ReportError("RecursiveLoadFromBuffer: got NULL key"); + break; + } + + if (*value == '}' && !wasQuoted) + { + g_KeyValuesErrorStack.ReportError("RecursiveLoadFromBuffer: got } in key"); + break; + } + + if (*value == '{' && !wasQuoted) + { + // this isn't a key, it's a section + errorKey.Reset(INVALID_KEY_SYMBOL); + // sub value list + dat->RecursiveLoadFromBuffer(resourceName, tokenReader, pfnEvaluateSymbolProc); + } + else + { + if (wasConditional) + { + g_KeyValuesErrorStack.ReportError("RecursiveLoadFromBuffer: got conditional between key and value"); + break; + } + + if (dat->m_sValue) + { + delete[] dat->m_sValue; + dat->m_sValue = NULL; + } + + size_t len = V_strlen(value); + + // Here, let's determine if we got a float or an int.... + char* pIEnd; // pos where int scan ended + char* pFEnd; // pos where float scan ended + const char* pSEnd = value + len; // pos where token ends + + const long lval = strtol(value, &pIEnd, 10); + const float fval = (float)strtod(value, &pFEnd); + const bool bOverflow = (lval == LONG_MAX || lval == LONG_MIN) && errno == ERANGE; +#ifdef POSIX + // strtod supports hex representation in strings under posix but we DON'T + // want that support in keyvalues, so undo it here if needed + if (len > 1 && tolower(value[1]) == 'x') + { + fval = 0.0f; + pFEnd = (char*)value; + } +#endif + + if (*value == 0) + { + dat->m_iDataType = TYPE_STRING; + } + else if ((18 == len) && (value[0] == '0') && (value[1] == 'x')) + { + // an 18-byte value prefixed with "0x" (followed by 16 hex digits) is an int64 value + int64 retVal = 0; + for (int i = 2; i < 2 + 16; i++) + { + char digit = value[i]; + if (digit >= 'a') + digit -= 'a' - ('9' + 1); + else + if (digit >= 'A') + digit -= 'A' - ('9' + 1); + retVal = (retVal * 16) + (digit - '0'); + } + dat->m_sValue = new char[sizeof(uint64)]; + *((uint64*)dat->m_sValue) = retVal; + dat->m_iDataType = TYPE_UINT64; + } + else if ((pFEnd > pIEnd) && (pFEnd == pSEnd)) + { + dat->m_flValue = fval; + dat->m_iDataType = TYPE_FLOAT; + } + else if (pIEnd == pSEnd && !bOverflow) + { + dat->m_iValue = static_cast(lval); + dat->m_iDataType = TYPE_INT; + } + else + { + dat->m_iDataType = TYPE_STRING; + } + + if (dat->m_iDataType == TYPE_STRING) + { + // copy in the string information + dat->m_sValue = new char[len + 1]; + memcpy(dat->m_sValue, value, len + 1); + } + + // Look ahead one token for a conditional tag + const char* peek = tokenReader.ReadToken(wasQuoted, wasConditional); + if (wasConditional) + { + bAccepted = EvaluateConditional(peek, pfnEvaluateSymbolProc); + } + else + { + tokenReader.SeekBackOneToken(); + } + } + + Assert(dat->m_pPeer == NULL); + if (bAccepted) + { + Assert(pLastChild == NULL || pLastChild->m_pPeer == dat); + pLastChild = dat; + } + else + { + //this->RemoveSubKey( dat ); + if (pLastChild == NULL) + { + Assert(this->m_pSub == dat); + this->m_pSub = NULL; + } + else + { + Assert(pLastChild->m_pPeer == dat); + pLastChild->m_pPeer = NULL; + } + + delete dat; + dat = NULL; + } + } +} + +// prevent two threads from entering this at the same time and trying to share the global error reporting and parse buffers +static CThreadFastMutex g_KVMutex; +//----------------------------------------------------------------------------- +// Read from a buffer... +//----------------------------------------------------------------------------- +bool KeyValues::LoadFromBuffer(char const* resourceName, CUtlBuffer& buf, IBaseFileSystem* pFileSystem, const char* pPathID, GetSymbolProc_t pfnEvaluateSymbolProc) +{ + AUTO_LOCK(g_KVMutex); + + //if (IsGameConsole()) + //{ + // // Let's not crash if the buffer is empty + // unsigned char* pData = buf.Size() > 0 ? (unsigned char*)buf.PeekGet() : NULL; + // if (pData && (unsigned int)pData[0] == KV_BINARY_POOLED_FORMAT) + // { + // // skip past binary marker + // buf.GetUnsignedChar(); + // // get the pool identifier, allows the fs to bind the expected string pool + // unsigned int poolKey = buf.GetUnsignedInt(); + + // RemoveEverything(); + // Init(); + + // return ReadAsBinaryPooledFormat(buf, pFileSystem, poolKey, pfnEvaluateSymbolProc); + // } + //} + + KeyValues* pPreviousKey = NULL; + KeyValues* pCurrentKey = this; + CUtlVector< KeyValues* > includedKeys; + CUtlVector< KeyValues* > baseKeys; + bool wasQuoted; + bool wasConditional; + CKeyValuesTokenReader tokenReader(this, buf); + + g_KeyValuesErrorStack.SetFilename(resourceName); + do + { + bool bAccepted = true; + + // the first thing must be a key + const char* s = tokenReader.ReadToken(wasQuoted, wasConditional); + if (!buf.IsValid() || !s) + break; + + if (!wasQuoted && *s == '\0') + { + // non quoted empty strings stop parsing + // quoted empty strings are allowed to support unnnamed KV sections + break; + } + + if (!V_stricmp(s, "#include")) // special include macro (not a key name) + { + s = tokenReader.ReadToken(wasQuoted, wasConditional); + // Name of subfile to load is now in s + + if (!s || *s == 0) + { + g_KeyValuesErrorStack.ReportError("#include is NULL "); + } + else + { + ParseIncludedKeys(resourceName, s, pFileSystem, pPathID, includedKeys, pfnEvaluateSymbolProc); + } + + continue; + } + else if (!V_stricmp(s, "#base")) + { + s = tokenReader.ReadToken(wasQuoted, wasConditional); + // Name of subfile to load is now in s + + if (!s || *s == 0) + { + g_KeyValuesErrorStack.ReportError("#base is NULL "); + } + else + { + ParseIncludedKeys(resourceName, s, pFileSystem, pPathID, baseKeys, pfnEvaluateSymbolProc); + } + + continue; + } + + if (!pCurrentKey) + { + pCurrentKey = new KeyValues(s); + Assert(pCurrentKey); + + pCurrentKey->UsesEscapeSequences(m_bHasEscapeSequences != 0); // same format has parent use + + if (pPreviousKey) + { + pPreviousKey->SetNextKey(pCurrentKey); + } + } + else + { + pCurrentKey->SetName(s); + } + + // get the '{' + s = tokenReader.ReadToken(wasQuoted, wasConditional); + + if (wasConditional) + { + bAccepted = EvaluateConditional(s, pfnEvaluateSymbolProc); + + // Now get the '{' + s = tokenReader.ReadToken(wasQuoted, wasConditional); + } + + if (s && *s == '{' && !wasQuoted) + { + // header is valid so load the file + pCurrentKey->RecursiveLoadFromBuffer(resourceName, tokenReader, pfnEvaluateSymbolProc); + } + else + { + g_KeyValuesErrorStack.ReportError("LoadFromBuffer: missing {"); + } + + if (!bAccepted) + { + if (pPreviousKey) + { + pPreviousKey->SetNextKey(NULL); + } + pCurrentKey->Clear(); + } + else + { + pPreviousKey = pCurrentKey; + pCurrentKey = NULL; + } + } while (buf.IsValid()); + + AppendIncludedKeys(includedKeys); + { + // delete included keys! + int i; + for (i = includedKeys.Count() - 1; i > 0; i--) + { + KeyValues* kv = includedKeys[i]; + delete kv; + } + } + + MergeBaseKeys(baseKeys); + { + // delete base keys! + int i; + for (i = baseKeys.Count() - 1; i >= 0; i--) + { + KeyValues* kv = baseKeys[i]; + delete kv; + } + } + + bool bErrors = g_KeyValuesErrorStack.EncounteredAnyErrors(); + g_KeyValuesErrorStack.SetFilename(""); + g_KeyValuesErrorStack.ClearErrorFlag(); + return !bErrors; +} + +//----------------------------------------------------------------------------- +// Read from a buffer... +//----------------------------------------------------------------------------- +bool KeyValues::LoadFromBuffer(char const* resourceName, const char* pBuffer, IBaseFileSystem* pFileSystem, const char* pPathID, GetSymbolProc_t pfnEvaluateSymbolProc) +{ + if (!pBuffer) + return true; + + if (IsGameConsole() && (unsigned int)((unsigned char*)pBuffer)[0] == KV_BINARY_POOLED_FORMAT) + { + // bad, got a binary compiled KV file through an unexpected text path + // not all paths support binary compiled kv, needs to get fixed + // need to have caller supply buffer length (strlen not valid), this interface change was never plumbed + Warning(eDLL_T::COMMON, "ERROR! Binary compiled KV '%s' in an unexpected handler\n", resourceName); + Assert(0); + return false; + } + + size_t nLen = V_strlen(pBuffer); + CUtlBuffer buf(pBuffer, nLen, CUtlBuffer::READ_ONLY | CUtlBuffer::TEXT_BUFFER); + // Translate Unicode files into UTF-8 before proceeding + if (nLen > 2 && (uint8)pBuffer[0] == 0xFF && (uint8)pBuffer[1] == 0xFE) + { + int nUTF8Len = V_UnicodeToUTF8((wchar_t*)(pBuffer + 2), NULL, 0); + char* pUTF8Buf = new char[nUTF8Len]; + V_UnicodeToUTF8((wchar_t*)(pBuffer + 2), pUTF8Buf, nUTF8Len); + buf.AssumeMemory(pUTF8Buf, nUTF8Len, nUTF8Len, CUtlBuffer::READ_ONLY | CUtlBuffer::TEXT_BUFFER); + } + return LoadFromBuffer(resourceName, buf, pFileSystem, pPathID, pfnEvaluateSymbolProc); +} + +//----------------------------------------------------------------------------- +// Purpose: Load keyValues from disk +//----------------------------------------------------------------------------- +bool KeyValues::LoadFromFile(IBaseFileSystem* filesystem, const char* resourceName, const char* pathID, GetSymbolProc_t pfnEvaluateSymbolProc) +{ + //TM_ZONE_FILTERED( TELEMETRY_LEVEL0, 50, TMZF_NONE, "%s %s", __FUNCTION__, tmDynamicString( TELEMETRY_LEVEL0, resourceName ) ); + + FileHandle_t f = filesystem->Open(resourceName, "rt", pathID); + if (!f) + return false; + + s_LastFileLoadingFrom = (char*)resourceName; + + // load file into a null-terminated buffer + const ssize_t fileSize = filesystem->Size(f); + std::unique_ptr pBuf(new char[fileSize + 1]); + + const ssize_t nRead = filesystem->Read(pBuf.get(), fileSize, f); + filesystem->Close(f); + + // TODO[ AMOS ]: unicode null terminate? + pBuf[nRead] = '\0'; + + return LoadFromBuffer(resourceName, pBuf.get(), filesystem, pathID, pfnEvaluateSymbolProc); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : includedKeys - +//----------------------------------------------------------------------------- +void KeyValues::AppendIncludedKeys(CUtlVector< KeyValues* >& includedKeys) +{ + // Append any included keys, too... + int includeCount = includedKeys.Count(); + int i; + for (i = 0; i < includeCount; i++) + { + KeyValues* kv = includedKeys[i]; + Assert(kv); + + KeyValues* insertSpot = this; + while (insertSpot->GetNextKey()) + { + insertSpot = insertSpot->GetNextKey(); + } + + insertSpot->SetNextKey(kv); + } +} + +void KeyValues::ParseIncludedKeys(char const* resourceName, const char* filetoinclude, + IBaseFileSystem* pFileSystem, const char* pPathID, CUtlVector< KeyValues* >& includedKeys, GetSymbolProc_t pfnEvaluateSymbolProc) +{ + Assert(resourceName); + Assert(filetoinclude); + Assert(pFileSystem); + + // Load it... + if (!pFileSystem) + { + return; + } + + // Get relative subdirectory + char fullpath[512]; + V_strncpy(fullpath, resourceName, sizeof(fullpath)); + + // Strip off characters back to start or first / + bool done = false; + size_t len = V_strlen(fullpath); + while (!done) + { + if (len == 0) + { + break; + } + + if (fullpath[len - 1] == '\\' || + fullpath[len - 1] == '/') + { + break; + } + + // zero it + fullpath[len - 1] = 0; + --len; + } + + // Append included file + V_strncat(fullpath, filetoinclude, sizeof(fullpath)); + + KeyValues* newKV = new KeyValues(fullpath); + + // CUtlSymbol save = s_CurrentFileSymbol; // did that had any use ??? + + newKV->UsesEscapeSequences(m_bHasEscapeSequences != 0); // use same format as parent + + if (newKV->LoadFromFile(pFileSystem, fullpath, pPathID, pfnEvaluateSymbolProc)) + { + includedKeys.AddToTail(newKV); + } + else + { + DevMsg(eDLL_T::COMMON, "%s: Couldn't load included keyvalue file %s\n", __FUNCTION__, fullpath); + delete newKV; + } + + // s_CurrentFileSymbol = save; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : baseKeys - +//----------------------------------------------------------------------------- +void KeyValues::MergeBaseKeys(CUtlVector< KeyValues* >& baseKeys) +{ + const int includeCount = baseKeys.Count(); + + for (int i = 0; i < includeCount; i++) + { + KeyValues* kv = baseKeys[i]; + Assert(kv); + + RecursiveMergeKeyValues(kv); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : baseKV - keyvalues we're basing ourselves on +//----------------------------------------------------------------------------- +void KeyValues::RecursiveMergeKeyValues(KeyValues* baseKV) +{ + // Merge ourselves + // we always want to keep our value, so nothing to do here + + // Now merge our children + for (KeyValues* baseChild = baseKV->m_pSub; baseChild != NULL; baseChild = baseChild->m_pPeer) + { + // for each child in base, see if we have a matching kv + + bool bFoundMatch = false; + + // If we have a child by the same name, merge those keys + for (KeyValues* newChild = m_pSub; newChild != NULL; newChild = newChild->m_pPeer) + { + if (!V_strcmp(baseChild->GetName(), newChild->GetName())) + { + newChild->RecursiveMergeKeyValues(baseChild); + bFoundMatch = true; + break; + } + } + + // If not merged, append this key + if (!bFoundMatch) + { + KeyValues* dat = baseChild->MakeCopy(); + Assert(dat); + AddSubKey(dat); + } + } +} + +//----------------------------------------------------------------------------- +// Returns whether a keyvalues conditional expression string evaluates to true or false +//----------------------------------------------------------------------------- +bool KeyValues::EvaluateConditional(const char* pExpressionString, GetSymbolProc_t pfnEvaluateSymbolProc) +{ + // evaluate the infix expression, calling the symbol proc to resolve each symbol's value + bool bResult = false; + const bool bValid = g_ExpressionEvaluator.Evaluate(bResult, pExpressionString, pfnEvaluateSymbolProc); + if (!bValid) + { + g_KeyValuesErrorStack.ReportError("KV Conditional Evaluation Error"); + } + + return bResult; +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +KeyValues* KeyValues::CreateKeyUsingKnownLastChild(const char* keyName, KeyValues* pLastChild) +{ + // Create a new key + KeyValues* dat = new KeyValues(keyName); + + dat->UsesEscapeSequences(m_bHasEscapeSequences != 0); // use same format as parent does + + // add into subkey list + AddSubkeyUsingKnownLastChild(dat, pLastChild); + + return dat; +} + +//----------------------------------------------------------------------------- +// +//----------------------------------------------------------------------------- +void KeyValues::AddSubkeyUsingKnownLastChild(KeyValues* pSubkey, KeyValues* pLastChild) +{ + // Make sure the subkey isn't a child of some other keyvalues + Assert(pSubkey != NULL); + Assert(pSubkey->m_pPeer == NULL); + + // Empty child list? + if (pLastChild == NULL) + { + Assert(m_pSub == NULL); + m_pSub = pSubkey; + } + else + { + Assert(m_pSub != NULL); + Assert(pLastChild->m_pPeer == NULL); + + pLastChild->SetNextKey(pSubkey); + } } //----------------------------------------------------------------------------- @@ -1327,136 +2179,3 @@ KeyValues* KeyValues::MakeCopy(void) const CopySubkeys(pNewKeyValue); return pNewKeyValue; } - -//----------------------------------------------------------------------------- -// Purpose: Initializes the playlist -//----------------------------------------------------------------------------- -void KeyValues::InitPlaylists(void) -{ - if (*g_pPlaylistKeyValues) - { - KeyValues* pPlaylists = (*g_pPlaylistKeyValues)->FindKey("Playlists"); - if (pPlaylists) - { - std::lock_guard l(g_PlaylistsVecMutex); - g_vAllPlaylists.clear(); - - for (KeyValues* pSubKey = pPlaylists->GetFirstTrueSubKey(); pSubKey != nullptr; pSubKey = pSubKey->GetNextTrueSubKey()) - { - g_vAllPlaylists.push_back(pSubKey->GetName()); // Get all playlists. - } - } - } - Mod_GetAllInstalledMaps(); // Parse all installed maps. -} - -//----------------------------------------------------------------------------- -// Purpose: Initializes the filesystem paths -//----------------------------------------------------------------------------- -void KeyValues::InitFileSystem(void) -{ - KeyValues* pMainFile = KeyValues::ReadKeyValuesFile(FileSystem(), "GameInfo.txt"); - if (pMainFile) - { - KeyValues* pFileSystemInfo = pMainFile->FindKey("FileSystem"); - if (pFileSystemInfo) - { - KeyValues* pSearchPaths = pFileSystemInfo->FindKey("SearchPaths"); - if (pSearchPaths) - { - g_vGameInfoPaths.clear(); - for (KeyValues* pSubKey = pSearchPaths->GetFirstValue(); pSubKey != nullptr; pSubKey = pSubKey->GetNextValue()) - { - string svValue = pSubKey->GetString(); - StringReplace(svValue, GAMEINFOPATH_TOKEN, ""); - StringReplace(svValue, BASESOURCEPATHS_TOKEN, ""); - - g_vGameInfoPaths.push_back(svValue); // Get all SearchPaths - } - } - } - - pMainFile->DeleteThis(); - } -} - -//----------------------------------------------------------------------------- -// Purpose: loads the playlists -// Input : *szPlaylist - -// Output : true on success, false on failure -//----------------------------------------------------------------------------- -bool KeyValues::LoadPlaylists(const char* pszPlaylist) -{ - bool bResults = KeyValues__LoadPlaylists(pszPlaylist); - KeyValues::InitPlaylists(); - - return bResults; -} - -//----------------------------------------------------------------------------- -// Purpose: parses the playlists -// Input : *szPlaylist - -// Output : true on success, false on failure -//----------------------------------------------------------------------------- -bool KeyValues::ParsePlaylists(const char* pszPlaylist) -{ - g_szMTVFItemName[0] = '\0'; // Terminate g_szMTVFTaskName to prevent crash while loading playlist. - - CHAR sPlaylistPath[] = "\x77\x27\x35\x2b\x2c\x6c\x2b\x2c\x2b"; - PCHAR curr = sPlaylistPath; - while (*curr) - { - *curr ^= 'B'; - ++curr; - } - - if (FileExists(sPlaylistPath)) - { - uint8_t verifyPlaylistIntegrity[] = // Very hacky way for alternative inline assembly for x64.. - { - 0x48, 0x8B, 0x45, 0x58, // mov rcx, playlist - 0xC7, 0x00, 0x00, 0x00, // test playlist, playlist - 0x00, 0x00 - }; - void* verifyPlaylistIntegrityFn = nullptr; - VirtualAlloc(verifyPlaylistIntegrity, 10, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); - memcpy(&verifyPlaylistIntegrityFn, reinterpret_cast(verifyPlaylistIntegrity), 9); - reinterpret_cast(verifyPlaylistIntegrityFn)(); - } - - return KeyValues__ParsePlaylists(pszPlaylist); // Parse playlist. -} - -//----------------------------------------------------------------------------- -// Purpose: reads a keyvalues file -// Input : *pFileSystem - -// * pFileName - -// Output : pointer to KeyValues object -//----------------------------------------------------------------------------- -KeyValues* KeyValues::ReadKeyValuesFile(CFileSystem_Stdio* pFileSystem, const char* pFileName) -{ - static bool bInitFileSystem{}; - if (!bInitFileSystem) - { - bInitFileSystem = true; - KeyValues::InitFileSystem(); - } - return KeyValues__ReadKeyValuesFile(pFileSystem, pFileName); -} - -/////////////////////////////////////////////////////////////////////////////// -void VKeyValues::Detour(const bool bAttach) const -{ - DetourSetup(&KeyValues__LoadPlaylists, &KeyValues::LoadPlaylists, bAttach); - DetourSetup(&KeyValues__ParsePlaylists, &KeyValues::ParsePlaylists, bAttach); - DetourSetup(&KeyValues__ReadKeyValuesFile, &KeyValues::ReadKeyValuesFile, bAttach); -} - -/////////////////////////////////////////////////////////////////////////////// -inline KeyValues** g_pPlaylistKeyValues = nullptr; // Get the KeyValue for the playlist file. - -vector g_vAllPlaylists = { "<>" }; -vector g_vGameInfoPaths = { "/" }; - -std::mutex g_InstalledMapsMutex; -std::mutex g_PlaylistsVecMutex; diff --git a/src/tier1/keyvalues_iface.cpp b/src/tier1/keyvalues_iface.cpp new file mode 100644 index 00000000..b87f0838 --- /dev/null +++ b/src/tier1/keyvalues_iface.cpp @@ -0,0 +1,22 @@ +#include "tier1/keyvalues_iface.h" +#include "filesystem/filesystem.h" + +//----------------------------------------------------------------------------- +// Purpose: reads a keyvalues file +// Input : *pFileSystem - +// * pFileName - +// Output : pointer to KeyValues object +//----------------------------------------------------------------------------- +static KeyValues* ReadKeyValuesFile(CFileSystem_Stdio* pFileSystem, const char* pFileName) +{ + return KeyValues__ReadKeyValuesFile(pFileSystem, pFileName); +} + +/////////////////////////////////////////////////////////////////////////////// +void VKeyValues::Detour(const bool bAttach) const +{ + DetourSetup(&KeyValues__ReadKeyValuesFile, &ReadKeyValuesFile, bAttach); +} + +/////////////////////////////////////////////////////////////////////////////// +std::mutex g_InstalledMapsMutex; \ No newline at end of file diff --git a/src/tier1/kverrorcontext.h b/src/tier1/kverrorcontext.h new file mode 100644 index 00000000..5da9f1e3 --- /dev/null +++ b/src/tier1/kverrorcontext.h @@ -0,0 +1,34 @@ +#ifndef KVERRORCONTEXT_H +#define KVERRORCONTEXT_H +#include "kverrorstack.h" + +// a simple helper that creates stack entries as it goes in & out of scope +class CKeyErrorContext +{ +public: + ~CKeyErrorContext() + { + g_KeyValuesErrorStack.Pop(); + } + explicit CKeyErrorContext(int symName) + { + Init(symName); + } + void Reset(int symName) + { + g_KeyValuesErrorStack.Reset(m_stackLevel, symName); + } + int GetStackLevel() const + { + return m_stackLevel; + } +private: + void Init(int symName) + { + m_stackLevel = g_KeyValuesErrorStack.Push(symName); + } + + int m_stackLevel; +}; + +#endif // KVERRORCONTEXT_H diff --git a/src/tier1/kverrorstack.h b/src/tier1/kverrorstack.h new file mode 100644 index 00000000..b8c13452 --- /dev/null +++ b/src/tier1/kverrorstack.h @@ -0,0 +1,88 @@ +#ifndef KVERRORSTACK_H +#define KVERRORSTACK_H +#include "ikeyvaluessystem.h" + +// a simple class to keep track of a stack of valid parsed symbols +const int MAX_ERROR_STACK = 64; +class CKeyValuesErrorStack +{ +public: + CKeyValuesErrorStack() : m_pFilename("NULL"), m_errorIndex(0), m_maxErrorIndex(0), m_bEncounteredErrors(false) {} + + void SetFilename(const char* pFilename) + { + m_pFilename = pFilename; + m_maxErrorIndex = 0; + } + + // entering a new keyvalues block, save state for errors + // Not save symbols instead of pointers because the pointers can move! + int Push(int symName) + { + if (m_errorIndex < MAX_ERROR_STACK) + { + m_errorStack[m_errorIndex] = symName; + } + m_errorIndex++; + m_maxErrorIndex = MAX(m_maxErrorIndex, (m_errorIndex - 1)); + return m_errorIndex - 1; + } + + // exiting block, error isn't in this block, remove. + void Pop() + { + m_errorIndex--; + Assert(m_errorIndex >= 0); + } + + // Allows you to keep the same stack level, but change the name as you parse peers + void Reset(int stackLevel, int symName) + { + Assert(stackLevel >= 0 && stackLevel < m_errorIndex); + if (stackLevel < MAX_ERROR_STACK) + m_errorStack[stackLevel] = symName; + } + + // Hit an error, report it and the parsing stack for context + void ReportError(const char* pError) + { + Warning(eDLL_T::COMMON, "KeyValues Error: %s in file %s\n", pError, m_pFilename); + for (int i = 0; i < m_maxErrorIndex; i++) + { + if (i < MAX_ERROR_STACK && m_errorStack[i] != INVALID_KEY_SYMBOL) + { + if (i < m_errorIndex) + { + Warning(eDLL_T::COMMON, "%s, ", KeyValuesSystem()->GetStringForSymbol(m_errorStack[i])); + } + else + { + Warning(eDLL_T::COMMON, "(*%s*), ", KeyValuesSystem()->GetStringForSymbol(m_errorStack[i])); + } + } + } + Warning(eDLL_T::COMMON, "\n"); + m_bEncounteredErrors = true; + } + + bool EncounteredAnyErrors() + { + return m_bEncounteredErrors; + } + + void ClearErrorFlag() + { + m_bEncounteredErrors = false; + } + +private: + int m_errorStack[MAX_ERROR_STACK]; + const char* m_pFilename; + int m_errorIndex; + int m_maxErrorIndex; + bool m_bEncounteredErrors; +}; + +inline CKeyValuesErrorStack g_KeyValuesErrorStack; + +#endif // KVERRORSTACK_H diff --git a/src/vpc/kvleaktrace.h b/src/tier1/kvleaktrace.h similarity index 100% rename from src/vpc/kvleaktrace.h rename to src/tier1/kvleaktrace.h diff --git a/src/tier1/kvtokenreader.h b/src/tier1/kvtokenreader.h new file mode 100644 index 00000000..dea12bb2 --- /dev/null +++ b/src/tier1/kvtokenreader.h @@ -0,0 +1,153 @@ +#ifndef KVTOKENREADER_H +#define KVTOKENREADER_H +#include "kverrorstack.h" +#include "tier1/keyvalues.h" + +// This class gets the tokens out of a CUtlBuffer for KeyValues. +// Since KeyValues likes to seek backwards and seeking won't work with a text-mode CUtlStreamBuffer +// (which is what dmserializers uses), this class allows you to seek back one token. +class CKeyValuesTokenReader +{ +public: + CKeyValuesTokenReader(KeyValues* pKeyValues, CUtlBuffer& buf); + + const char* ReadToken(bool& wasQuoted, bool& wasConditional); + void SeekBackOneToken(); + +private: + KeyValues* m_pKeyValues; + CUtlBuffer& m_Buffer; + + int m_nTokensRead; + bool m_bUsePriorToken; + bool m_bPriorTokenWasQuoted; + bool m_bPriorTokenWasConditional; + static char s_pTokenBuf[KEYVALUES_TOKEN_SIZE]; +}; + +char CKeyValuesTokenReader::s_pTokenBuf[KEYVALUES_TOKEN_SIZE]; + +CKeyValuesTokenReader::CKeyValuesTokenReader(KeyValues* pKeyValues, CUtlBuffer& buf) : + m_Buffer(buf) +{ + m_pKeyValues = pKeyValues; + m_nTokensRead = 0; + m_bUsePriorToken = false; +} + +const char* CKeyValuesTokenReader::ReadToken(bool& wasQuoted, bool& wasConditional) +{ + if (m_bUsePriorToken) + { + m_bUsePriorToken = false; + wasQuoted = m_bPriorTokenWasQuoted; + wasConditional = m_bPriorTokenWasConditional; + return s_pTokenBuf; + } + + m_bPriorTokenWasQuoted = wasQuoted = false; + m_bPriorTokenWasConditional = wasConditional = false; + + if (!m_Buffer.IsValid()) + return NULL; + + // eating white spaces and remarks loop + while (true) + { + m_Buffer.EatWhiteSpace(); + if (!m_Buffer.IsValid()) + { + return NULL; // file ends after reading whitespaces + } + + // stop if it's not a comment; a new token starts here + if (!m_Buffer.EatCPPComment()) + break; + } + + const char* c = (const char*)m_Buffer.PeekGet(sizeof(char), 0); + if (!c) + { + return NULL; + } + + // read quoted strings specially + if (*c == '\"') + { + m_bPriorTokenWasQuoted = wasQuoted = true; + m_Buffer.GetDelimitedString(m_pKeyValues->m_bHasEscapeSequences ? GetCStringCharConversion() : GetNoEscCharConversion(), + s_pTokenBuf, KEYVALUES_TOKEN_SIZE); + + ++m_nTokensRead; + return s_pTokenBuf; + } + + if (*c == '{' || *c == '}' || *c == '=') + { + // it's a control char, just add this one char and stop reading + s_pTokenBuf[0] = *c; + s_pTokenBuf[1] = 0; + m_Buffer.GetChar(); + ++m_nTokensRead; + return s_pTokenBuf; + } + + // read in the token until we hit a whitespace or a control character + bool bReportedError = false; + bool bConditionalStart = false; + int nCount = 0; + while (1) + { + c = (const char*)m_Buffer.PeekGet(sizeof(char), 0); + + // end of file + if (!c || *c == 0) + break; + + // break if any control character appears in non quoted tokens + if (*c == '"' || *c == '{' || *c == '}' || *c == '=') + break; + + if (*c == '[') + bConditionalStart = true; + + if (*c == ']' && bConditionalStart) + { + m_bPriorTokenWasConditional = wasConditional = true; + bConditionalStart = false; + } + + // break on whitespace + if (V_isspace(*c) && !bConditionalStart) + break; + + if (nCount < (KEYVALUES_TOKEN_SIZE - 1)) + { + s_pTokenBuf[nCount++] = *c; // add char to buffer + } + else if (!bReportedError) + { + bReportedError = true; + g_KeyValuesErrorStack.ReportError(" ReadToken overflow"); + } + + m_Buffer.GetChar(); + } + s_pTokenBuf[nCount] = 0; + ++m_nTokensRead; + + return s_pTokenBuf; +} + +void CKeyValuesTokenReader::SeekBackOneToken() +{ + if (m_bUsePriorToken) + Plat_FatalError(eDLL_T::COMMON, "CKeyValuesTokenReader::SeekBackOneToken: It is only possible to seek back one token at a time"); + + if (m_nTokensRead == 0) + Plat_FatalError(eDLL_T::COMMON, "CkeyValuesTokenReader::SeekBackOneToken: No tokens read yet"); + + m_bUsePriorToken = true; +} + +#endif // KVTOKENREADER_H diff --git a/src/tier1/strtools.cpp b/src/tier1/strtools.cpp index e702e99b..1e9087af 100644 --- a/src/tier1/strtools.cpp +++ b/src/tier1/strtools.cpp @@ -1,5 +1,75 @@ #include "tier1/strtools.h" +//----------------------------------------------------------------------------- +// A special high-performance case-insensitive compare function +// returns 0 if strings match exactly +// returns >0 if strings match in a case-insensitive way, but do not match exactly +// returns <0 if strings do not match even in a case-insensitive way +//----------------------------------------------------------------------------- +int _V_stricmp_NegativeForUnequal(const char* s1, const char* s2) +{ + // It is not uncommon to compare a string to itself. Since stricmp + // is expensive and pointer comparison is cheap, this simple test + // can save a lot of cycles, and cache pollution. + if (s1 == s2) + return 0; + + uint8 const* pS1 = (uint8 const*)s1; + uint8 const* pS2 = (uint8 const*)s2; + int iExactMatchResult = 1; + for (;;) + { + int c1 = *(pS1++); + int c2 = *(pS2++); + if (c1 == c2) + { + // strings are case-insensitive equal, coerce accumulated + // case-difference to 0/1 and return it + if (!c1) return !iExactMatchResult; + } + else + { + if (!c2) + { + // c2=0 and != c1 => not equal + return -1; + } + iExactMatchResult = 0; + c1 = FastASCIIToLower(c1); + c2 = FastASCIIToLower(c2); + if (c1 != c2) + { + // strings are not equal + return -1; + } + } + c1 = *(pS1++); + c2 = *(pS2++); + if (c1 == c2) + { + // strings are case-insensitive equal, coerce accumulated + // case-difference to 0/1 and return it + if (!c1) return !iExactMatchResult; + } + else + { + if (!c2) + { + // c2=0 and != c1 => not equal + return -1; + } + iExactMatchResult = 0; + c1 = FastASCIIToLower(c1); + c2 = FastASCIIToLower(c2); + if (c1 != c2) + { + // strings are not equal + return -1; + } + } + } +} + //----------------------------------------------------------------------------- // Finds a string in another string with a case insensitive test //----------------------------------------------------------------------------- @@ -153,6 +223,52 @@ bool V_isspace(int c) #endif } +//----------------------------------------------------------------------------- +// Purpose: +// Input : *in - +// inputbytes - +// *out - +// outsize - +//----------------------------------------------------------------------------- +void V_binarytohex(const byte* in, size_t inputbytes, char* out, size_t outsize) +{ + Assert(outsize >= 1); + char doublet[10]; + int i; + + out[0] = 0; + + for (i = 0; i < inputbytes; i++) + { + unsigned char c = in[i]; + V_snprintf(doublet, sizeof(doublet), "%02x", c); + V_strncat(out, doublet, outsize); + } +} + + +int V_vsnprintfRet(char* pDest, int maxLen, const char* pFormat, va_list params, bool* pbTruncated) +{ + Assert(maxLen > 0); + + int len = _vsnprintf(pDest, maxLen, pFormat, params); + bool bTruncated = (len < 0) || (len >= maxLen); + + if (pbTruncated) + { + *pbTruncated = bTruncated; + } + + if (bTruncated && maxLen > 0) + { + len = maxLen - 1; + pDest[maxLen - 1] = 0; + } + + return len; +} + + ssize_t V_StrTrim(char* pStr) { char* pSource = pStr; diff --git a/src/vgui/vgui_debugpanel.cpp b/src/vgui/vgui_debugpanel.cpp index d689f055..3516852a 100644 --- a/src/vgui/vgui_debugpanel.cpp +++ b/src/vgui/vgui_debugpanel.cpp @@ -7,8 +7,8 @@ #include #include +#include #include -#include #include #include #include diff --git a/src/vpc/CMakeLists.txt b/src/vpc/CMakeLists.txt index fcb373b4..1a0a42e3 100644 --- a/src/vpc/CMakeLists.txt +++ b/src/vpc/CMakeLists.txt @@ -8,9 +8,6 @@ add_sources( SOURCE_GROUP "Private" "IAppSystem.h" "interfaces.cpp" "interfaces.h" - "keyvalues.cpp" - "keyvalues.h" - "kvleaktrace.h" "rson.cpp" "rson.h" ) diff --git a/src/vpklib/packedstore.cpp b/src/vpklib/packedstore.cpp index 1b543deb..45051beb 100644 --- a/src/vpklib/packedstore.cpp +++ b/src/vpklib/packedstore.cpp @@ -25,29 +25,29 @@ // ///////////////////////////////////////////////////////////////////////////////// -#include "tier1/cvar.h" +#include "tier1/keyvalues.h" #include "tier2/fileutils.h" #include "mathlib/adler32.h" #include "mathlib/crc32.h" #include "mathlib/sha1.h" -#include "filesystem/filesystem.h" -#include "vpc/keyvalues.h" #include "localize/ilocalize.h" #include "vpklib/packedstore.h" +extern CFileSystem_Stdio* FileSystem(); + static const std::regex s_DirFileRegex{ R"((?:.*\/)?([^_]*_)(.*)(.bsp.pak000_dir).*)" }; static const std::regex s_BlockFileRegex{ R"(pak000_([0-9]{3}))" }; //----------------------------------------------------------------------------- // Purpose: initialize parameters for compression algorithm //----------------------------------------------------------------------------- -void CPackedStore::InitLzCompParams(void) +void CPackedStore::InitLzCompParams(const char* compressionLevel, const lzham_int32 maxHelperThreads) { /*| PARAMETERS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||*/ m_lzCompParams.m_dict_size_log2 = VPK_DICT_SIZE; - m_lzCompParams.m_level = GetCompressionLevel(); + m_lzCompParams.m_level = DetermineCompressionLevel(compressionLevel); m_lzCompParams.m_compress_flags = lzham_compress_flags::LZHAM_COMP_FLAG_DETERMINISTIC_PARSING; - m_lzCompParams.m_max_helper_threads = fs_packedstore_max_helper_threads->GetInt(); + m_lzCompParams.m_max_helper_threads = maxHelperThreads; } //----------------------------------------------------------------------------- @@ -65,19 +65,17 @@ void CPackedStore::InitLzDecompParams(void) // Purpose: gets the LZHAM compression level // output : lzham_compress_level //----------------------------------------------------------------------------- -lzham_compress_level CPackedStore::GetCompressionLevel(void) const +lzham_compress_level CPackedStore::DetermineCompressionLevel(const char* compressionLevel) const { - const char* pszLevel = fs_packedstore_compression_level->GetString(); - - if(strcmp(pszLevel, "fastest") == NULL) + if(strcmp(compressionLevel, "fastest") == NULL) return lzham_compress_level::LZHAM_COMP_LEVEL_FASTEST; - else if (strcmp(pszLevel, "faster") == NULL) + else if (strcmp(compressionLevel, "faster") == NULL) return lzham_compress_level::LZHAM_COMP_LEVEL_FASTER; - else if (strcmp(pszLevel, "default") == NULL) + else if (strcmp(compressionLevel, "default") == NULL) return lzham_compress_level::LZHAM_COMP_LEVEL_DEFAULT; - else if (strcmp(pszLevel, "better") == NULL) + else if (strcmp(compressionLevel, "better") == NULL) return lzham_compress_level::LZHAM_COMP_LEVEL_BETTER; - else if (strcmp(pszLevel, "uber") == NULL) + else if (strcmp(compressionLevel, "uber") == NULL) return lzham_compress_level::LZHAM_COMP_LEVEL_UBER; else return lzham_compress_level::LZHAM_COMP_LEVEL_DEFAULT; @@ -208,7 +206,9 @@ KeyValues* CPackedStore::GetManifest(const CUtlString& workspacePath, const CUtl CUtlString outPath; outPath.Format("%s%s%s.txt", workspacePath.Get(), "manifest/", manifestFile.Get()); - KeyValues* pManifestKV = FileSystem()->LoadKeyValues(IFileSystem::TYPE_COMMON, outPath.Get(), "PLATFORM"); + KeyValues* pManifestKV = new KeyValues("BuildManifest"); + pManifestKV->LoadFromFile(FileSystem(), outPath.Get(), "PLATFORM"); + return pManifestKV; } @@ -288,7 +288,6 @@ CUtlString CPackedStore::FormatEntryPath(const CUtlString& filePath, void CPackedStore::BuildManifest(const CUtlVector& entryBlocks, const CUtlString& workspacePath, const CUtlString& manifestName) const { KeyValues kv("BuildManifest"); - KeyValues* pManifestKV = kv.FindKey("BuildManifest", true); FOR_EACH_VEC(entryBlocks, i) { @@ -301,7 +300,7 @@ void CPackedStore::BuildManifest(const CUtlVector& entryBlocks, CUtlString entryPath = entry.m_EntryPath; entryPath.FixSlashes('\\'); - KeyValues* pEntryKV = pManifestKV->FindKey(entryPath.Get(), true); + KeyValues* pEntryKV = kv.FindKey(entryPath.Get(), true); pEntryKV->SetInt("preloadSize", entry.m_iPreloadSize); pEntryKV->SetInt("loadFlags", descriptor.m_nLoadFlags); diff --git a/src/vpklib/packedstore.h b/src/vpklib/packedstore.h index ab28bb6c..eb744b68 100644 --- a/src/vpklib/packedstore.h +++ b/src/vpklib/packedstore.h @@ -196,10 +196,10 @@ struct VPKPair_t class CPackedStore { public: - void InitLzCompParams(void); + void InitLzCompParams(const char* compressionLevel = "default", const lzham_int32 maxHelperThreads = -1); void InitLzDecompParams(void); - lzham_compress_level GetCompressionLevel(void) const; + lzham_compress_level DetermineCompressionLevel(const char* compressionLevel) const; void GetEntryBlocks(CUtlVector& entryBlocks, FileHandle_t hDirectoryFile) const; bool GetEntryValues(CUtlVector& entryValues, const CUtlString& workspacePath, const CUtlString& dirFileName) const;