/**
* vim: set ts=4 sw=4 tw=99 noet :
* =============================================================================
* SourceMod
* Copyright (C) 2004-2010 AlliedModders LLC. All rights reserved.
* =============================================================================
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 3.0, as published by the
* Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see .
*
* As a special exception, AlliedModders LLC gives you permission to link the
* code of this program (as well as its derivative works) to "Half-Life 2," the
* "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software
* by the Valve Corporation. You must obey the GNU General Public License in
* all respects for all other code used. Additionally, AlliedModders LLC grants
* this exception to all derivative works. AlliedModders LLC defines further
* exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007),
* or .
*
* Version: $Id$
*/
#ifndef _INCLUDE_SOURCEMOD_PLUGINSYSTEM_H_
#define _INCLUDE_SOURCEMOD_PLUGINSYSTEM_H_
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "common_logic.h"
#include
#include
#include
#include "ITranslator.h"
#include "IGameConfigs.h"
#include "NativeOwner.h"
#include "ShareSys.h"
#include "PhraseCollection.h"
#include
#include
#include
#include
class CPlayer;
using namespace SourceHook;
enum LoadRes
{
LoadRes_Successful,
LoadRes_AlreadyLoaded,
LoadRes_Failure,
LoadRes_NeverLoad
};
enum APLRes
{
APLRes_Success,
APLRes_Failure,
APLRes_SilentFailure
};
// Plugin membership state.
enum class PluginState
{
// The plugin has not yet been added to the global plugin list.
Unregistered,
// The plugin is a member of the global plugin list.
Registered,
// The plugin has been evicted.
Evicted,
// The plugin is waiting to be unloaded.
WaitingToUnload,
WaitingToUnloadAndReload,
};
class CPlugin :
public SMPlugin,
public CNativeOwner
{
public:
CPlugin(const char *file);
~CPlugin();
public:
PluginType GetType();
SourcePawn::IPluginContext *GetBaseContext();
sp_context_t *GetContext();
void *GetPluginStructure();
const char *GetFilename();
bool IsDebugging();
PluginStatus GetStatus();
PluginStatus Status() const;
bool IsSilentlyFailed();
const sm_plugininfo_t *GetPublicInfo();
bool SetPauseState(bool paused);
unsigned int GetSerial();
IdentityToken_t *GetIdentity();
unsigned int CalcMemUsage();
bool SetProperty(const char *prop, void *ptr);
bool GetProperty(const char *prop, void **ptr, bool remove=false);
void DropEverything();
SourcePawn::IPluginRuntime *GetRuntime();
CNativeOwner *ToNativeOwner() {
return this;
}
struct ExtVar {
char *name;
char *file;
bool autoload;
bool required;
};
typedef ke::Lambda ExtVarCallback;
bool ForEachExtVar(const ExtVarCallback& callback);
void ForEachLibrary(ke::Lambda callback);
public:
/**
* Creates a plugin object with default values.
* If an error buffer is specified, and an error occurs, the error will be copied to the buffer
* and NULL will be returned.
* If an error buffer is not specified, the error will be copied to an internal buffer and
* a valid (but error-stated) CPlugin will be returned.
*/
static CPlugin *Create(const char *file);
public:
// Evicts the plugin from memory and sets an error state.
void EvictWithError(PluginStatus status, const char *error_fmt, ...);
void FinishEviction();
// Initializes the plugin's identity information
void InitIdentity();
// Calls the OnPluginLoad function, and sets any failed states if necessary.
// After invoking AskPluginLoad, its state is either Running or Failed.
APLRes AskPluginLoad();
// Transition to the fully running state, if possible.
bool OnPluginStart();
void Call_OnPluginEnd();
void Call_OnAllPluginsLoaded();
void Call_OnLibraryAdded(const char *lib);
// Returns true if a plugin is usable.
bool IsRunnable();
// Get languages info.
IPhraseCollection *GetPhrases();
PluginState State() const {
return m_state;
}
void SetRegistered();
void SetWaitingToUnload(bool andReload=false);
PluginStatus GetDisplayStatus() const {
return m_status;
}
bool IsEvictionCandidate() const;
public:
// Returns true if the plugin was running, but is now invalid.
bool WasRunning();
Handle_t GetMyHandle();
bool AddFakeNative(IPluginFunction *pFunc, const char *name, SPVM_FAKENATIVE_FUNC func);
void AddConfig(bool autoCreate, const char *cfg, const char *folder);
size_t GetConfigCount();
AutoConfig *GetConfig(size_t i);
inline void AddLibrary(const char *name) {
m_Libraries.push_back(name);
}
inline bool HasLibrary(const char *name) {
return m_Libraries.find(name) != m_Libraries.end();
}
void LibraryActions(LibraryAction action);
void SyncMaxClients(int max_clients);
// Returns true if the plugin's underlying file structure has a newer
// modification time than either when it was first instantiated or
// since the last call to HasUpdatedFile().
bool HasUpdatedFile();
const char *GetDateTime() const {
return m_DateTime;
}
int GetFileVersion() const {
return m_FileVersion;
}
const char *GetErrorMsg() const {
assert(m_status != Plugin_Running && m_status != Plugin_Loaded);
return m_errormsg;
}
void AddRequiredLib(const char *name);
bool ForEachRequiredLib(ke::Lambda callback);
bool HasMissingFakeNatives() const {
return m_FakeNativesMissing;
}
bool HasMissingLibrary() const {
return m_LibraryMissing;
}
bool HasFakeNatives() const {
return m_fakes.length() > 0;
}
// True if we got far enough into the second pass to call OnPluginLoaded
// in the listener list.
bool EnteredSecondPass() const {
return m_EnteredSecondPass;
}
bool IsInErrorState() const {
if (m_status == Plugin_Running || m_status == Plugin_Loaded)
return false;
return true;
}
bool TryCompile();
void BindFakeNativesTo(CPlugin *other);
protected:
void DependencyDropped(CPlugin *pOwner);
private:
time_t GetFileTimeStamp();
bool ReadInfo();
void DestroyIdentity();
private:
// This information is static for the lifetime of the plugin.
char m_filename[PLATFORM_MAX_PATH];
unsigned int m_serial;
PluginStatus m_status;
PluginState m_state;
bool m_AddedLibraries;
bool m_EnteredSecondPass;
// Statuses that are set during failure.
bool m_SilentFailure;
bool m_FakeNativesMissing;
bool m_LibraryMissing;
char m_errormsg[256];
// Internal properties that must by reset if the runtime is evicted.
ke::AutoPtr m_pRuntime;
ke::AutoPtr m_pPhrases;
IPluginContext *m_pContext;
sp_pubvar_t *m_MaxClientsVar;
StringHashMap m_Props;
CVector m_configs;
List m_Libraries;
bool m_bGotAllLoaded;
int m_FileVersion;
// Information that survives past eviction.
List m_RequiredLibs;
IdentityToken_t *m_ident;
time_t m_LastFileModTime;
Handle_t m_handle;
char m_DateTime[256];
// Cached.
sm_plugininfo_t m_info;
ke::AString info_name_;
ke::AString info_author_;
ke::AString info_description_;
ke::AString info_version_;
ke::AString info_url_;
};
class CPluginManager :
public IScriptManager,
public SMGlobalClass,
public IHandleTypeDispatch,
public IRootConsoleCommand
{
public:
CPluginManager();
~CPluginManager();
public:
/* Implements iterator class */
class CPluginIterator
: public IPluginIterator,
public IPluginsListener
{
public:
CPluginIterator(ReentrantList& in);
virtual ~CPluginIterator();
virtual bool MorePlugins();
virtual IPlugin *GetPlugin();
virtual void NextPlugin();
void Release();
void OnPluginDestroyed(IPlugin *plugin) override;
private:
ke::LinkedList mylist;
ke::LinkedList::iterator current;
};
friend class CPluginManager::CPluginIterator;
public: //IScriptManager
IPlugin *LoadPlugin(const char *path,
bool debug,
PluginType type,
char error[],
size_t maxlength,
bool *wasloaded);
bool UnloadPlugin(IPlugin *plugin);
IPlugin *FindPluginByContext(const sp_context_t *ctx);
unsigned int GetPluginCount();
IPluginIterator *GetPluginIterator();
void AddPluginsListener(IPluginsListener *listener);
void RemovePluginsListener(IPluginsListener *listener);
void LoadAll(const char *config_path, const char *plugins_path);
void RefreshAll();
SMPlugin *FindPluginByOrder(unsigned num) {
return GetPluginByOrder(num);
}
SMPlugin *FindPluginByIdentity(IdentityToken_t *ident) {
return GetPluginFromIdentity(ident);
}
SMPlugin *FindPluginByContext(IPluginContext *ctx) {
return GetPluginByCtx(ctx->GetContext());
}
SMPlugin *FindPluginByContext(sp_context_t *ctx) {
return GetPluginByCtx(ctx);
}
SMPlugin *FindPluginByConsoleArg(const char *text);
SMPlugin *FindPluginByHandle(Handle_t hndl, HandleError *errp) {
return static_cast(PluginFromHandle(hndl, errp));
}
const CVector *ListPlugins();
void FreePluginList(const CVector *plugins);
public: //SMGlobalClass
void OnSourceModAllInitialized();
void OnSourceModShutdown();
ConfigResult OnSourceModConfigChanged(const char *key, const char *value, ConfigSource source, char *error, size_t maxlength);
void OnSourceModMaxPlayersChanged(int newvalue);
public: //IHandleTypeDispatch
void OnHandleDestroy(HandleType_t type, void *object);
bool GetHandleApproxSize(HandleType_t type, void *object, unsigned int *pSize);
public: //IRootConsoleCommand
void OnRootConsoleCommand(const char *cmdname, const ICommandArgs *command) override;
public:
/**
* Loads all plugins not yet loaded
*/
void LoadAll_FirstPass(const char *config, const char *basedir);
/**
* Runs the second loading pass for all plugins
*/
void LoadAll_SecondPass();
/**
* Returns whether anything loaded will be a late load.
*/
bool IsLateLoadTime() const;
/**
* Converts a Handle to an IPlugin if possible.
*/
IPlugin *PluginFromHandle(Handle_t handle, HandleError *err);
/**
* Finds a plugin based on its index. (starts on index 1)
*/
CPlugin *GetPluginByOrder(int num);
int GetOrderOfPlugin(IPlugin *pl);
/**
* Internal version of FindPluginByContext()
*/
CPlugin *GetPluginByCtx(const sp_context_t *ctx);
/**
* Gets status text for a status code
*/
const char *GetStatusText(PluginStatus status);
/**
* Add public functions from all running or paused
* plugins to the specified forward if the names match.
*/
void AddFunctionsToForward(const char *name, IChangeableForward *pForward);
/**
* Iterates through plugins to call OnAllPluginsLoaded.
*/
void AllPluginsLoaded();
CPlugin *GetPluginFromIdentity(IdentityToken_t *pToken);
void Shutdown();
void OnLibraryAction(const char *lib, LibraryAction action);
bool LibraryExists(const char *lib);
bool ReloadPlugin(CPlugin *pl, bool print=false);
void UnloadAll();
void SyncMaxClients(int max_clients);
void ListPluginsToClient(CPlayer *player, const CCommand &args);
void _SetPauseState(CPlugin *pPlugin, bool pause);
void ForEachPlugin(ke::Lambda callback);
private:
LoadRes LoadPlugin(CPlugin **pPlugin, const char *path, bool debug, PluginType type);
void LoadAutoPlugin(const char *plugin);
/**
* Recursively loads all plugins in the given directory.
*/
void LoadPluginsFromDir(const char *basedir, const char *localdir);
/**
* Adds a plugin object. This is wrapped by LoadPlugin functions.
*/
void AddPlugin(CPlugin *pPlugin);
// First pass for loading a plugin, and its helpers.
CPlugin *CompileAndPrep(const char *path);
bool MalwareCheckPass(CPlugin *pPlugin);
// Runs the second loading pass on a plugin.
bool RunSecondPass(CPlugin *pPlugin);
void LoadExtensions(CPlugin *pPlugin);
bool RequireExtensions(CPlugin *pPlugin);
bool FindOrRequirePluginDeps(CPlugin *pPlugin);
void UnloadPluginImpl(CPlugin *plugin);
void ReloadPluginImpl(int id, const char filename[], PluginType ptype, bool print);
void Purge(CPlugin *plugin);
public:
inline IdentityToken_t *GetIdentity()
{
return m_MyIdent;
}
IPluginManager *GetOldAPI();
private:
void TryRefreshDependencies(CPlugin *pOther);
private:
ReentrantList m_listeners;
ReentrantList m_plugins;
ke::LinkedList m_iterators;
typedef decltype(m_listeners)::iterator ListenerIter;
typedef decltype(m_plugins)::iterator PluginIter;
struct CPluginPolicy
{
static inline uint32_t hash(const detail::CharsAndLength &key)
{
/* For windows & mac, we convert the path to lower-case in order to avoid duplicate plugin loading */
#if defined PLATFORM_WINDOWS || defined PLATFORM_APPLE
ke::AString original(key.chars());
ke::AString lower = original.lowercase();
return detail::CharsAndLength(lower.chars()).hash();
#else
return key.hash();
#endif
}
static inline bool matches(const char *file, const CPlugin *plugin)
{
const char *pluginFileChars = const_cast(plugin)->GetFilename();
#if defined PLATFORM_WINDOWS || defined PLATFORM_APPLE
ke::AString pluginFile = ke::AString(pluginFileChars).lowercase();
ke::AString input = ke::AString(file).lowercase();
return pluginFile == input;
#else
return strcmp(pluginFileChars, file) == 0;
#endif
}
};
NameHashSet m_LoadLookup;
bool m_AllPluginsLoaded;
IdentityToken_t *m_MyIdent;
/* Dynamic native stuff */
List m_Natives;
bool m_LoadingLocked;
// Config
bool m_bBlockBadPlugins;
// Forwards
IForward *m_pOnLibraryAdded;
IForward *m_pOnLibraryRemoved;
};
extern CPluginManager g_PluginSys;
#endif //_INCLUDE_SOURCEMOD_PLUGINSYSTEM_H_