diff options
Diffstat (limited to 'engine/sv_plugin.cpp')
| -rw-r--r-- | engine/sv_plugin.cpp | 757 |
1 files changed, 757 insertions, 0 deletions
diff --git a/engine/sv_plugin.cpp b/engine/sv_plugin.cpp new file mode 100644 index 0000000..b64882e --- /dev/null +++ b/engine/sv_plugin.cpp @@ -0,0 +1,757 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// + +#include "server_pch.h" +#include "sv_plugin.h" +#include "filesystem.h" +#include "filesystem_engine.h" +#include "eiface.h" +#include "sys.h" +#include "client.h" +#include "sys_dll.h" +#include "pr_edict.h" +#include "networkstringtable.h" +#include "networkstringtableserver.h" +#include "tier0/etwprof.h" + +extern CreateInterfaceFn g_AppSystemFactory; +extern CSysModule *g_GameDLL; + +CServerPlugin s_ServerPlugin; +CServerPlugin *g_pServerPluginHandler = &s_ServerPlugin; +EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CServerPlugin, IServerPluginHelpers, INTERFACEVERSION_ISERVERPLUGINHELPERS, s_ServerPlugin ); + +// Ascending value so they have a unique cookie to relate queries to the responses. +static int g_iQueryCvarCookie = 1; + + +QueryCvarCookie_t SendCvarValueQueryToClient( IClient *client, const char *pCvarName, bool bPluginQuery ) +{ + // Send a message to the client asking for the value. + SVC_GetCvarValue msg; + msg.m_iCookie = g_iQueryCvarCookie++; + msg.m_szCvarName = pCvarName; + + // If the query came from the game DLL instead of from a plugin, then we negate the cookie + // so it knows who to callback on when the value arrives back from the client. + if ( !bPluginQuery ) + msg.m_iCookie = -msg.m_iCookie; + + client->SendNetMsg( msg ); + return msg.m_iCookie; +} + + +//--------------------------------------------------------------------------------- +// Purpose: constructor/destructor +//--------------------------------------------------------------------------------- +CPlugin::CPlugin() +{ + m_pPlugin = NULL; + m_pPluginModule = NULL; + m_bDisable = false; + m_szName[0] = 0; +} + +CPlugin::~CPlugin() +{ + if ( m_pPlugin ) + { + Unload(); + } + m_pPlugin = NULL; + + if ( m_pPluginModule ) + { + g_pFileSystem->UnloadModule( m_pPluginModule ); + } + m_pPluginModule = NULL; +} + +//--------------------------------------------------------------------------------- +// Purpose: loads and initializes a plugin +//--------------------------------------------------------------------------------- +bool CPlugin::Load( const char *fileName ) +{ + if ( IsX360() ) + { + return false; + } + + char fixedFileName[ MAX_PATH ]; + Q_strncpy( fixedFileName, fileName, sizeof(fixedFileName) ); + Q_FixSlashes( fixedFileName ); + +#if defined ( OSX ) || defined( LINUX ) + // Linux doesn't check signatures, so in that case disable plugins on the client completely unless -insecure is specified + if ( !sv.IsDedicated() && Host_IsSecureServerAllowed() ) + return false; +#endif + // Only allow unsigned plugins in -insecure mode + if ( !Host_AllowLoadModule( fixedFileName, "GAME", false ) ) + return false; + + m_pPluginModule = g_pFileSystem->LoadModule( fixedFileName, "GAME", false ); + if ( m_pPluginModule ) + { + CreateInterfaceFn pluginFactory = Sys_GetFactory( m_pPluginModule ); + if ( pluginFactory ) + { + m_iPluginInterfaceVersion = 3; + m_bDisable = true; + m_pPlugin = (IServerPluginCallbacks *)pluginFactory( INTERFACEVERSION_ISERVERPLUGINCALLBACKS, NULL ); + if ( !m_pPlugin ) + { + m_iPluginInterfaceVersion = 2; + m_pPlugin = (IServerPluginCallbacks *)pluginFactory( INTERFACEVERSION_ISERVERPLUGINCALLBACKS_VERSION_2, NULL ); + if ( !m_pPlugin ) + { + m_iPluginInterfaceVersion = 1; + m_pPlugin = (IServerPluginCallbacks *)pluginFactory( INTERFACEVERSION_ISERVERPLUGINCALLBACKS_VERSION_1, NULL ); + if ( !m_pPlugin ) + { + Warning( "Could not get IServerPluginCallbacks interface from plugin \"%s\"", fileName ); + return false; + } + } + } + + CreateInterfaceFn gameServerFactory = Sys_GetFactory( g_GameDLL ); + + if ( !m_pPlugin->Load( g_AppSystemFactory, gameServerFactory ) ) + { + Warning( "Failed to load plugin \"%s\"\n", fileName ); + return false; + } + SetName( m_pPlugin->GetPluginDescription() ); + m_bDisable = false; + } + } + else + { + Warning( "Unable to load plugin \"%s\"\n", fileName ); + return false; + } + + return true; +} + +//--------------------------------------------------------------------------------- +// Purpose: unloads and cleans up a module +//--------------------------------------------------------------------------------- +void CPlugin::Unload() +{ + if ( m_pPlugin ) + { + m_pPlugin->Unload(); + } + m_pPlugin = NULL; + m_bDisable = true; + + g_pFileSystem->UnloadModule( m_pPluginModule ); + m_pPluginModule = NULL; +} + +//--------------------------------------------------------------------------------- +// Purpose: sets the name of the plugin +//--------------------------------------------------------------------------------- +void CPlugin::SetName( const char *name ) +{ + Q_strncpy( m_szName, name, sizeof(m_szName) ); +} + +//--------------------------------------------------------------------------------- +// Purpose: returns the name of the plugin +//--------------------------------------------------------------------------------- +const char *CPlugin::GetName() +{ + return m_szName; +} + +//--------------------------------------------------------------------------------- +// Purpose: returns the callback interface of a module +//--------------------------------------------------------------------------------- +IServerPluginCallbacks *CPlugin::GetCallback() +{ + Assert( m_pPlugin ); + if ( m_pPlugin ) + { + return m_pPlugin; + } + else + { + Assert( !"Unable to get plugin callback interface" ); + Warning( "Unable to get callback interface for \"%s\"\n", GetName() ); + return NULL; + } +} + +//--------------------------------------------------------------------------------- +// Purpose: enables or disabled a plugin (i.e stops it running) +//--------------------------------------------------------------------------------- +void CPlugin::Disable( bool state ) +{ + Assert( m_pPlugin ); + if ( state ) + { + m_pPlugin->Pause(); + } + else + { + m_pPlugin->UnPause(); + } + m_bDisable = state; +} + + +//--------------------------------------------------------------------------------- +// Purpose: constructor/destructor +//--------------------------------------------------------------------------------- +CServerPlugin::CServerPlugin() +{ + m_PluginHelperCheck = NULL; +} + +CServerPlugin::~CServerPlugin() +{ +} + +//--------------------------------------------------------------------------------- +// Purpose: loads all plugins +//--------------------------------------------------------------------------------- +void CServerPlugin::LoadPlugins() +{ + if ( IsX360() ) + { + return; + } + + m_Plugins.PurgeAndDeleteElements(); + + char const *findfn = Sys_FindFirst( "addons/*.vdf", NULL, 0 ); + while ( findfn ) + { + DevMsg( "Plugins: found file \"%s\"\n", findfn ); + if ( !g_pFileSystem->FileExists( va("addons/%s", findfn), "MOD" ) ) // verify its in the mods directory + { + findfn = Sys_FindNext( NULL, 0 ); + continue; + } + + KeyValues *pluginsFile = new KeyValues("Plugins"); + pluginsFile->LoadFromFile( g_pFileSystem, va("addons/%s", findfn), "MOD" ); + + if ( pluginsFile->GetString("file", NULL) ) + { + LoadPlugin(pluginsFile->GetString("file")); + } + + pluginsFile->deleteThis(); + + // move to next item + findfn = Sys_FindNext( NULL, 0 ); + } + + Sys_FindClose(); + + CreateInterfaceFn gameServerFactory = Sys_GetFactory( g_GameDLL ); + m_PluginHelperCheck = (IPluginHelpersCheck *)gameServerFactory( INTERFACEVERSION_PLUGINHELPERSCHECK, NULL ); +} + +//--------------------------------------------------------------------------------- +// Purpose: unloads all plugins +//--------------------------------------------------------------------------------- +void CServerPlugin::UnloadPlugins() +{ + for ( int i = m_Plugins.Count() - 1; i >= 0; --i ) + { + m_Plugins[i]->Unload(); + m_Plugins.Remove(i); + } +} + +//--------------------------------------------------------------------------------- +// Purpose: unload a single plugin +//--------------------------------------------------------------------------------- +bool CServerPlugin::UnloadPlugin( int index ) +{ + if ( m_Plugins.IsValidIndex( index ) ) + { + m_Plugins[index]->Unload(); + m_Plugins.Remove(index); + return true; + } + return false; +} + +//--------------------------------------------------------------------------------- +// Purpose: loads a particular dll +//--------------------------------------------------------------------------------- +bool CServerPlugin::LoadPlugin( const char *fileName ) +{ + CPlugin *plugin = new CPlugin(); + if ( plugin->Load( fileName ) ) + { + m_Plugins.AddToTail( plugin ); + return true; + } + else + { + delete plugin; + return false; + } +} + +//--------------------------------------------------------------------------------- +// Purpose: stop all plugins from running +//--------------------------------------------------------------------------------- +void CServerPlugin::DisablePlugins() +{ + for ( int i = 0; i < m_Plugins.Count(); i++ ) + { + m_Plugins[i]->Disable(true); + } +} + +//--------------------------------------------------------------------------------- +// Purpose: turns all plugins back on +//--------------------------------------------------------------------------------- +void CServerPlugin::EnablePlugins() +{ + for ( int i = 0; i < m_Plugins.Count(); i++ ) + { + m_Plugins[i]->Disable(false); + } +} + +//--------------------------------------------------------------------------------- +// Purpose: stops a single plugin +//--------------------------------------------------------------------------------- +void CServerPlugin::DisablePlugin( int index ) +{ + if ( m_Plugins.IsValidIndex( index ) ) + { + m_Plugins[index]->Disable(true); + } +} + +//--------------------------------------------------------------------------------- +// Purpose: stops a single plugin +//--------------------------------------------------------------------------------- +void CServerPlugin::EnablePlugin( int index ) +{ + if ( m_Plugins.IsValidIndex( index ) ) + { + m_Plugins[index]->Disable(false); + } +} + +//--------------------------------------------------------------------------------- +// Purpose: prints info about loaded plugins and their state +//--------------------------------------------------------------------------------- +void CServerPlugin::PrintDetails() +{ + ConMsg( "Loaded plugins:\n"); + ConMsg( "---------------------\n" ); + for ( int i = 0; i < m_Plugins.Count(); i++ ) + { + ConMsg( "%i:\t\"%s\"%s\n", i, m_Plugins[i]->GetName(), m_Plugins[i]->IsDisabled() ? " (disabled)": "" ); + } + ConMsg( "---------------------\n" ); +} + +// helper macro to stop this being typed for every passthrough +#define FORALL_PLUGINS for( int i = 0; i < m_Plugins.Count(); i++ ) +#define CALL_PLUGIN_IF_ENABLED(call) \ + do { \ + CPlugin *plugin = m_Plugins[i]; \ + if ( ! plugin->IsDisabled() ) \ + { \ + IServerPluginCallbacks *callbacks = plugin->GetCallback(); \ + if ( callbacks ) \ + { \ + callbacks-> call ; \ + } \ + } \ + } while (0) + + +extern CNetworkStringTableContainer *networkStringTableContainerServer; +//--------------------------------------------------------------------------------- +// Purpose: pass through functions for the 3rd party API +//--------------------------------------------------------------------------------- +void CServerPlugin::LevelInit( char const *pMapName, + char const *pMapEntities, char const *pOldLevel, + char const *pLandmarkName, bool loadGame, bool background ) +{ + CETWScope timer( "CServerPlugin::LevelInit" ); + + MDLCACHE_COARSE_LOCK_(g_pMDLCache); + FORALL_PLUGINS + { + CALL_PLUGIN_IF_ENABLED( LevelInit( pMapName ) ); + } + + bool bPrevState = networkStringTableContainerServer->Lock( false ); + serverGameDLL->LevelInit( pMapName, pMapEntities, pOldLevel, pLandmarkName, loadGame, background ); + networkStringTableContainerServer->Lock( bPrevState ); + +} + +void CServerPlugin::ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) +{ + MDLCACHE_COARSE_LOCK_(g_pMDLCache); + FORALL_PLUGINS + { + CALL_PLUGIN_IF_ENABLED( ServerActivate( pEdictList, edictCount, clientMax ) ); + } + + serverGameDLL->ServerActivate( pEdictList, edictCount, clientMax ); +} + +void CServerPlugin::GameFrame( bool simulating ) +{ + FORALL_PLUGINS + { + CALL_PLUGIN_IF_ENABLED( GameFrame( simulating ) ); + } + + serverGameDLL->GameFrame( simulating ); +} + +void CServerPlugin::LevelShutdown( void ) +{ + MDLCACHE_COARSE_LOCK_(g_pMDLCache); + FORALL_PLUGINS + { + CALL_PLUGIN_IF_ENABLED( LevelShutdown() ); + } + + serverGameDLL->LevelShutdown(); +} + + +void CServerPlugin::ClientActive( edict_t *pEntity, bool bLoadGame ) +{ + FORALL_PLUGINS + { + CALL_PLUGIN_IF_ENABLED( ClientActive( pEntity ) ); + } + + serverGameClients->ClientActive( pEntity, bLoadGame ); +} + +void CServerPlugin::ClientDisconnect( edict_t *pEntity ) +{ + FORALL_PLUGINS + { + CALL_PLUGIN_IF_ENABLED( ClientDisconnect( pEntity ) ); + } + + serverGameClients->ClientDisconnect( pEntity ); +} + +void CServerPlugin::ClientPutInServer( edict_t *pEntity, char const *playername ) +{ + FORALL_PLUGINS + { + CALL_PLUGIN_IF_ENABLED( ClientPutInServer( pEntity, playername ) ); + } + + serverGameClients->ClientPutInServer( pEntity, playername ); +} + +void CServerPlugin::SetCommandClient( int index ) +{ + FORALL_PLUGINS + { + CALL_PLUGIN_IF_ENABLED( SetCommandClient( index ) ); + } + + serverGameClients->SetCommandClient( index ); +} + +void CServerPlugin::ClientSettingsChanged( edict_t *pEdict ) +{ + FORALL_PLUGINS + { + CALL_PLUGIN_IF_ENABLED( ClientSettingsChanged( pEdict ) ); + } + + serverGameClients->ClientSettingsChanged( pEdict ); +} + +bool CServerPlugin::ClientConnect( edict_t *pEntity, const char *pszName, const char *pszAddress, char *reject, int maxrejectlen ) +{ + PLUGIN_RESULT result = PLUGIN_CONTINUE; + bool bAllowConnect = true, bSavedRetVal = true, bRetValOverridden = false; + FORALL_PLUGINS + { + if ( ! m_Plugins[i]->IsDisabled() ) + { + result = m_Plugins[i]->GetCallback()->ClientConnect( &bAllowConnect, pEntity, pszName, pszAddress, reject, maxrejectlen ); + if ( result == PLUGIN_STOP ) // stop executing right away + { + Assert( bAllowConnect == false ); + return bAllowConnect; + } + else if ( result == PLUGIN_OVERRIDE && bRetValOverridden == false ) // only the first PLUGIN_OVERRIDE return set the retval + { + bSavedRetVal = bAllowConnect; + bRetValOverridden = true; + } + } + } + + bAllowConnect = serverGameClients->ClientConnect( pEntity, pszName, pszAddress, reject, maxrejectlen ); + return bRetValOverridden ? bSavedRetVal : bAllowConnect; +} + +void CServerPlugin::ClientCommand( edict_t *pEntity, const CCommand &args ) +{ + PLUGIN_RESULT result = PLUGIN_CONTINUE; + FORALL_PLUGINS + { + if ( !m_Plugins[i]->IsDisabled() ) + { + result = m_Plugins[i]->GetCallback()->ClientCommand( pEntity, args ); + if ( result == PLUGIN_STOP ) // stop executing right away + return; + } + } + + serverGameClients->ClientCommand( pEntity, args ); +} + +QueryCvarCookie_t CServerPlugin::StartQueryCvarValue( edict_t *pEntity, const char *pCvarName ) +{ + // Figure out which client they're talking about. + int clientnum = NUM_FOR_EDICT( pEntity ); + if (clientnum < 1 || clientnum > sv.GetClientCount() ) + { + Warning( "StartQueryCvarValue: Invalid entity\n" ); + return InvalidQueryCvarCookie; + } + + IClient *client = sv.Client( clientnum-1 ); + return SendCvarValueQueryToClient( client, pCvarName, true ); +} + +void CServerPlugin::NetworkIDValidated( const char *pszUserName, const char *pszNetworkID ) +{ + PLUGIN_RESULT result = PLUGIN_CONTINUE; + FORALL_PLUGINS + { + if ( ! m_Plugins[i]->IsDisabled() ) + { + result = m_Plugins[i]->GetCallback()->NetworkIDValidated( pszUserName, pszNetworkID ); + if ( result == PLUGIN_STOP ) // stop executing right away + { + return; + } + } + } +} + +void CServerPlugin::OnQueryCvarValueFinished( QueryCvarCookie_t iCookie, edict_t *pPlayerEntity, EQueryCvarValueStatus eStatus, const char *pCvarName, const char *pCvarValue ) +{ + FORALL_PLUGINS + { + CPlugin *p = m_Plugins[i]; + if ( !p->IsDisabled() ) + { + // OnQueryCvarValueFinished was added in version 2 of this interface. + if ( p->GetPluginInterfaceVersion() >= 2 ) + { + p->GetCallback()->OnQueryCvarValueFinished( iCookie, pPlayerEntity, eStatus, pCvarName, pCvarValue ); + } + } + } +} +void CServerPlugin::OnEdictAllocated( edict_t *edict ) +{ + FORALL_PLUGINS + { + CPlugin *p = m_Plugins[i]; + if ( !p->IsDisabled() ) + { + // OnEdictAllocated was added in version 3 of this interface. + if ( p->GetPluginInterfaceVersion() >= 3 ) + { + p->GetCallback()->OnEdictAllocated( edict ); + } + } + } +} +void CServerPlugin::OnEdictFreed( const edict_t *edict ) +{ + FORALL_PLUGINS + { + CPlugin *p = m_Plugins[i]; + if ( !p->IsDisabled() ) + { + // OnEdictFreed was added in version 3 of this interface. + if ( p->GetPluginInterfaceVersion() >= 3 ) + { + p->GetCallback()->OnEdictFreed( edict ); + } + } + } +} +//--------------------------------------------------------------------------------- +// Purpose: creates a VGUI menu on a clients screen +//--------------------------------------------------------------------------------- +void CServerPlugin::CreateMessage( edict_t *pEntity, DIALOG_TYPE type, KeyValues *data, IServerPluginCallbacks *plugin ) +{ + if ( !pEntity ) + { + ConMsg( "Invaid pEntity\n" ); + return; + } + + if ( !data ) + { + ConMsg( "No data keyvalues provided\n" ); + return; + } + + if ( !plugin ) + { + ConMsg( "No plugin provided\n" ); + return; + } + + if ( m_PluginHelperCheck && !m_PluginHelperCheck->CreateMessage( plugin->GetPluginDescription(), pEntity, type, data ) ) + { + ConMsg( "Disallowed by game dll\n" ); + return; + } + + int clientnum = NUM_FOR_EDICT( pEntity ); + if (clientnum < 1 || clientnum > sv.GetClientCount() ) + { + ConMsg( "Invalid entity\n" ); + return; + } + + IClient *client = sv.Client(clientnum-1); + + SVC_Menu menu( type, data ); + client->SendNetMsg( menu ); +} + +void CServerPlugin::ClientCommand( edict_t *pEntity, const char *cmd ) +{ + int entnum = NUM_FOR_EDICT( pEntity ); + + if ( ( entnum < 1 ) || ( entnum > sv.GetClientCount() ) ) + { + Msg("\n!!!\nCServerPlugin::ClientCommand: Some entity tried to stuff '%s' to console buffer of entity %i when maxclients was set to %i, ignoring\n\n", + cmd, entnum, sv.GetMaxClients() ); + return; + } + + sv.GetClient(entnum-1)->ExecuteStringCommand( cmd ); + +} + +//--------------------------------------------------------------------------------- +// +// +// Purpose: client commands for plugin functions +// +// +//--------------------------------------------------------------------------------- +CON_COMMAND( plugin_print, "Prints details about loaded plugins" ) +{ + g_pServerPluginHandler->PrintDetails(); +} + +CON_COMMAND( plugin_pause, "plugin_pause <index> : pauses a loaded plugin" ) +{ + if ( args.ArgC() < 2 ) + { + Warning( "Syntax: plugin_pause <index>\n" ); + } + else + { + g_pServerPluginHandler->DisablePlugin( atoi(args[2]) ); + ConMsg( "Plugin disabled\n" ); + } +} + +CON_COMMAND( plugin_unpause, "plugin_unpause <index> : unpauses a disabled plugin" ) +{ + if ( args.ArgC() < 2 ) + { + Warning( "Syntax: plugin_unpause <index>\n" ); + } + else + { + g_pServerPluginHandler->EnablePlugin( atoi(args[2]) ); + ConMsg( "Plugin enabled\n" ); + } +} + +CON_COMMAND( plugin_pause_all, "pauses all loaded plugins" ) +{ + g_pServerPluginHandler->DisablePlugins(); + ConMsg( "Plugins disabled\n" ); +} + +CON_COMMAND( plugin_unpause_all, "unpauses all disabled plugins" ) +{ + g_pServerPluginHandler->EnablePlugins(); + ConMsg( "Plugins enabled\n" ); +} + +CON_COMMAND( plugin_load, "plugin_load <filename> : loads a plugin" ) +{ + if ( sv.IsDedicated() && sv.IsActive() ) + { + ConMsg( "plugin_load : cannot load a plugin while running a map\n" ); + } + else if ( !sv.IsDedicated() && cl.IsConnected() ) + { + ConMsg( "plugin_load : cannot load a plugin while connected to a server\n" ); + } + else + { + if ( args.ArgC() < 2 ) + { + Warning( "plugin_load <filename>\n" ); + } + else + { + if ( !g_pServerPluginHandler->LoadPlugin( args[1] ) ) + { + Warning( "Unable to load plugin \"%s\"\n", args[1] ); + return; + } + ConMsg( "Loaded plugin \"%s\"\n", args[1] ); + } + } +} + +CON_COMMAND( plugin_unload, "plugin_unload <index> : unloads a plugin" ) +{ + if ( args.ArgC() < 2 ) + { + Warning( "plugin_unload <index>\n" ); + } + else + { + if ( !g_pServerPluginHandler->UnloadPlugin( atoi(args[1]) ) ) + { + Warning( "Unable to unload plugin \"%s\", not found\n", args[1] ); + return; + } + ConMsg( "Unloaded plugin \"%s\"\n", args[1] ); + } +} + |