From cd82e22ea3d29ec1ef99d76e7293ec7f397d6594 Mon Sep 17 00:00:00 2001 From: Kawe Mazidjatari <48657826+Mauler125@users.noreply.github.com> Date: Fri, 12 Jan 2024 00:52:07 +0100 Subject: [PATCH] Tier1: move KeyValues class to Tier1 The KeyValues class belongs here. Also reimplemented most loading methods for KeyValues, and adjusted the VPK building code to account for it. Pointers to the engine's implementation of KeyValues have been moved to a separate header ('keyvalues_iface.h'), as this allows external tools code to utilize the standalone KeyValues class implementation. Playlist utilities are completely separated from the KeyValues header; these have nothing to do with KeyValues other than manipulating a global KeyValues object for the playlists, and thus have been named as such and moved to rtech/playlists. --- src/common/callback.cpp | 5 +- src/core/CMakeLists.txt | 1 + src/core/init.cpp | 4 +- src/engine/client/cdll_engine_int.cpp | 2 +- src/engine/client/client.h | 2 +- src/engine/client/clientstate.cpp | 4 +- src/engine/cmodel_bsp.cpp | 2 +- src/engine/cmodel_bsp.h | 1 + src/engine/gl_model_private.h | 2 +- src/engine/host_state.cpp | 6 +- src/engine/net_chan.cpp | 2 +- src/game/client/vscript_client.cpp | 2 +- src/game/shared/vscript_shared.cpp | 2 +- src/gameui/IBrowser.cpp | 6 +- src/materialsystem/cmaterialsystem.cpp | 2 +- src/networksystem/listmanager.cpp | 4 +- src/pluginsystem/modsystem.h | 2 +- src/public/eiface.h | 2 +- src/public/tier0/dbg.h | 3 + src/public/tier1/exprevaluator.h | 74 ++ src/public/tier1/fmtstr.h | 369 +++++++ src/{vpc => public/tier1}/keyvalues.h | 109 +- src/public/tier1/keyvalues_iface.h | 42 + src/public/tier1/strtools.h | 19 +- src/rtech/CMakeLists.txt | 11 + src/rtech/playlists/playlists.cpp | 87 ++ src/rtech/playlists/playlists.h | 45 + src/thirdparty/imgui/misc/imgui_utility.cpp | 2 +- src/tier1/CMakeLists.txt | 6 + src/tier1/exprevaluator.cpp | 493 +++++++++ src/{vpc => tier1}/keyvalues.cpp | 1003 ++++++++++++++++--- src/tier1/keyvalues_iface.cpp | 22 + src/tier1/kverrorcontext.h | 34 + src/tier1/kverrorstack.h | 88 ++ src/{vpc => tier1}/kvleaktrace.h | 0 src/tier1/kvtokenreader.h | 153 +++ src/tier1/strtools.cpp | 116 +++ src/vgui/vgui_debugpanel.cpp | 2 +- src/vpc/CMakeLists.txt | 3 - src/vpklib/packedstore.cpp | 33 +- src/vpklib/packedstore.h | 4 +- 41 files changed, 2517 insertions(+), 252 deletions(-) create mode 100644 src/public/tier1/exprevaluator.h create mode 100644 src/public/tier1/fmtstr.h rename src/{vpc => public/tier1}/keyvalues.h (62%) create mode 100644 src/public/tier1/keyvalues_iface.h create mode 100644 src/rtech/playlists/playlists.cpp create mode 100644 src/rtech/playlists/playlists.h create mode 100644 src/tier1/exprevaluator.cpp rename src/{vpc => tier1}/keyvalues.cpp (61%) create mode 100644 src/tier1/keyvalues_iface.cpp create mode 100644 src/tier1/kverrorcontext.h create mode 100644 src/tier1/kverrorstack.h rename src/{vpc => tier1}/kvleaktrace.h (100%) create mode 100644 src/tier1/kvtokenreader.h 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;