533 lines
15 KiB
C++
533 lines
15 KiB
C++
/**
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
* 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 <http://www.sourcemod.net/license.php>.
|
|
*
|
|
* Version: $Id$
|
|
*/
|
|
|
|
#ifndef _INCLUDE_SOURCEMOD_PLUGINSYSTEM_H_
|
|
#define _INCLUDE_SOURCEMOD_PLUGINSYSTEM_H_
|
|
|
|
#include <errno.h>
|
|
#include <sys/stat.h>
|
|
#include <time.h>
|
|
|
|
#include <memory>
|
|
|
|
#include <IPluginSys.h>
|
|
#include <IHandleSys.h>
|
|
#include <IForwardSys.h>
|
|
#include <sh_list.h>
|
|
#include <sh_stack.h>
|
|
#include <sh_vector.h>
|
|
#include <sh_string.h>
|
|
#include "common_logic.h"
|
|
#include <IRootConsoleMenu.h>
|
|
#include <sm_stringhashmap.h>
|
|
#include <sm_namehashset.h>
|
|
#include "ITranslator.h"
|
|
#include "IGameConfigs.h"
|
|
#include "NativeOwner.h"
|
|
#include "ShareSys.h"
|
|
#include "PhraseCollection.h"
|
|
#include <am-string.h>
|
|
#include <bridge/include/IScriptManager.h>
|
|
#include <am-function.h>
|
|
#include <ReentrantList.h>
|
|
|
|
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::Function<bool(const sp_pubvar_t *, const ExtVar& ext)> ExtVarCallback;
|
|
bool ForEachExtVar(const ExtVarCallback& callback);
|
|
|
|
void ForEachLibrary(ke::Function<void(const char *)> 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::Function<bool(const char *)> callback);
|
|
|
|
bool HasMissingFakeNatives() const {
|
|
return m_FakeNativesMissing;
|
|
}
|
|
bool HasMissingLibrary() const {
|
|
return m_LibraryMissing;
|
|
}
|
|
bool HasFakeNatives() const {
|
|
return m_fakes.size() > 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.
|
|
std::unique_ptr<IPluginRuntime> m_pRuntime;
|
|
std::unique_ptr<CPhraseCollection> m_pPhrases;
|
|
IPluginContext *m_pContext;
|
|
sp_pubvar_t *m_MaxClientsVar;
|
|
StringHashMap<void *> m_Props;
|
|
CVector<AutoConfig *> m_configs;
|
|
List<String> m_Libraries;
|
|
bool m_bGotAllLoaded;
|
|
int m_FileVersion;
|
|
|
|
// Information that survives past eviction.
|
|
List<String> m_RequiredLibs;
|
|
IdentityToken_t *m_ident;
|
|
time_t m_LastFileModTime;
|
|
Handle_t m_handle;
|
|
char m_DateTime[256];
|
|
|
|
// Cached.
|
|
sm_plugininfo_t m_info;
|
|
std::string info_name_;
|
|
std::string info_author_;
|
|
std::string info_description_;
|
|
std::string info_version_;
|
|
std::string 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<CPlugin *>& in);
|
|
virtual ~CPluginIterator();
|
|
virtual bool MorePlugins();
|
|
virtual IPlugin *GetPlugin();
|
|
virtual void NextPlugin();
|
|
void Release();
|
|
void OnPluginDestroyed(IPlugin *plugin) override;
|
|
private:
|
|
std::list<CPlugin *> mylist;
|
|
std::list<CPlugin *>::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<SMPlugin *>(PluginFromHandle(hndl, errp));
|
|
}
|
|
const CVector<SMPlugin *> *ListPlugins();
|
|
void FreePluginList(const CVector<SMPlugin *> *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::Function<void(CPlugin *)> 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<IPluginsListener *> m_listeners;
|
|
ReentrantList<CPlugin *> m_plugins;
|
|
std::list<CPluginIterator *> 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
|
|
std::string lower = ke::Lowercase(key.c_str());
|
|
return detail::CharsAndLength(lower.c_str()).hash();
|
|
#else
|
|
return key.hash();
|
|
#endif
|
|
}
|
|
|
|
static inline bool matches(const char *file, const CPlugin *plugin)
|
|
{
|
|
const char *pluginFileChars = const_cast<CPlugin*>(plugin)->GetFilename();
|
|
#if defined PLATFORM_WINDOWS || defined PLATFORM_APPLE
|
|
std::string pluginFile = ke::Lowercase(pluginFileChars);
|
|
std::string input = ke::Lowercase(file);
|
|
|
|
return pluginFile == input;
|
|
#else
|
|
return strcmp(pluginFileChars, file) == 0;
|
|
#endif
|
|
}
|
|
};
|
|
NameHashSet<CPlugin *, CPluginPolicy> m_LoadLookup;
|
|
|
|
bool m_AllPluginsLoaded;
|
|
IdentityToken_t *m_MyIdent;
|
|
|
|
/* Dynamic native stuff */
|
|
List<FakeNative *> m_Natives;
|
|
|
|
bool m_LoadingLocked;
|
|
|
|
// Config
|
|
bool m_bBlockBadPlugins;
|
|
|
|
// Forwards
|
|
IForward *m_pOnLibraryAdded;
|
|
IForward *m_pOnLibraryRemoved;
|
|
IForward *m_pOnNotifyPluginUnloaded;
|
|
};
|
|
|
|
extern CPluginManager g_PluginSys;
|
|
|
|
#endif //_INCLUDE_SOURCEMOD_PLUGINSYSTEM_H_
|
|
|