diff --git a/core/smn_core.cpp b/core/smn_core.cpp index e8484243..cf4219a1 100644 --- a/core/smn_core.cpp +++ b/core/smn_core.cpp @@ -335,12 +335,6 @@ static cell_t MarkNativeAsOptional(IPluginContext *pContext, const cell_t *param char *name; uint32_t idx; sp_context_t *ctx = pContext->GetContext(); - CPlugin *pl = g_PluginSys.GetPluginByCtx(ctx); - - if (!pl->IsInAskPluginLoad()) - { - return pContext->ThrowNativeError("Calling MarkNativeAsOptional outside AskPluginLoad is not allowed"); - } pContext->LocalToString(params[1], &name); if (pContext->FindNativeByName(name, &idx) != SP_ERROR_NONE) @@ -353,6 +347,19 @@ static cell_t MarkNativeAsOptional(IPluginContext *pContext, const cell_t *param return 1; } +static cell_t RegPluginLibrary(IPluginContext *pContext, const cell_t *params) +{ + char *name; + uint32_t idx; + CPlugin *pl = g_PluginSys.GetPluginByCtx(pContext->GetContext()); + + pContext->LocalToString(params[1], &name); + + pl->AddLibrary(name); + + return 1; +} + REGISTER_NATIVES(coreNatives) { {"AutoExecConfig", AutoExecConfig}, @@ -369,5 +376,6 @@ REGISTER_NATIVES(coreNatives) {"SetFailState", SetFailState}, {"FormatTime", FormatTime}, {"MarkNativeAsOptional", MarkNativeAsOptional}, + {"RegPluginLibrary", RegPluginLibrary}, {NULL, NULL}, }; diff --git a/core/systems/ExtensionSys.cpp b/core/systems/ExtensionSys.cpp index 1dab719e..35a20379 100644 --- a/core/systems/ExtensionSys.cpp +++ b/core/systems/ExtensionSys.cpp @@ -18,7 +18,6 @@ #include "ShareSys.h" #include "Logger.h" #include "sourcemm_api.h" -#include "PluginSys.h" #include "sm_srvcmds.h" #include "sm_stringutil.h" @@ -126,6 +125,13 @@ CExtension::~CExtension() { m_pLib->CloseLibrary(); } + + List::iterator iter; + for (iter=m_WeakNatives.begin(); iter!=m_WeakNatives.end(); iter++) + { + delete (*iter); + } + m_WeakNatives.clear(); } void CExtension::MarkAllLoaded() @@ -539,6 +545,9 @@ void CExtensionManager::BindAllNativesToPlugin(IPlugin *pPlugin) if (!(pContext->GetContext()->natives[idx].flags & SP_NTVFLAG_OPTIONAL)) { set = true; + } else { + WeakNative *wkn = new WeakNative((CPlugin *)pPlugin, idx); + pExt->m_WeakNatives.push_back(wkn); } } i++; @@ -584,6 +593,16 @@ bool CExtensionManager::UnloadExtension(IExtension *_pExt) p_iter = pExt->m_Plugins.erase(p_iter); } + /* Unbound weak natives */ + WeakNative *wkn; + List::iterator wkn_iter; + for (wkn_iter=pExt->m_WeakNatives.begin(); wkn_iter!=pExt->m_WeakNatives.end(); wkn_iter++) + { + wkn = (*wkn_iter); + sp_context_t *ctx = wkn->pl->GetContext(); + ctx->natives[wkn->idx].status = SP_NATIVE_UNBOUND; + } + /* Notify and/or unload all dependencies */ List::iterator c_iter; CExtension *pDep; diff --git a/core/systems/ExtensionSys.h b/core/systems/ExtensionSys.h index 6c2da850..ed44dcb1 100644 --- a/core/systems/ExtensionSys.h +++ b/core/systems/ExtensionSys.h @@ -24,6 +24,7 @@ #include #include #include +#include "PluginSys.h" using namespace SourceMod; using namespace SourceHook; @@ -63,6 +64,7 @@ private: List m_Interfaces; List m_Plugins; List m_Natives; + List m_WeakNatives; PluginId m_PlId; unsigned int unload_code; bool m_FullyLoaded; diff --git a/core/systems/PluginSys.cpp b/core/systems/PluginSys.cpp index 7227a73d..827f64ac 100644 --- a/core/systems/PluginSys.cpp +++ b/core/systems/PluginSys.cpp @@ -47,6 +47,7 @@ CPlugin::CPlugin(const char *file) m_ident = NULL; m_pProps = sm_trie_create(); m_FakeNativesMissing = false; + m_LibraryMissing = false; } CPlugin::~CPlugin() @@ -91,6 +92,13 @@ CPlugin::~CPlugin() delete m_configs[i]; } m_configs.clear(); + + List::iterator iter; + for (iter=m_WeakNatives.begin(); iter!=m_WeakNatives.end(); iter++) + { + delete (*iter); + } + m_WeakNatives.clear(); } void CPlugin::InitIdentity() @@ -357,19 +365,15 @@ bool CPlugin::Call_AskPluginLoad(char *error, size_t maxlength) return true; } - m_IsInAskPluginLoad = true; pFunction->PushCell(m_handle); pFunction->PushCell(g_PluginSys.IsLateLoadTime() ? 1 : 0); pFunction->PushStringEx(error, maxlength, 0, SM_PARAM_COPYBACK); pFunction->PushCell(maxlength); if ((err=pFunction->Execute(&result)) != SP_ERROR_NONE) { - m_IsInAskPluginLoad = false; return false; } - m_IsInAskPluginLoad = false; - if (!result || m_status != Plugin_Loaded) { return false; @@ -592,6 +596,19 @@ void CPlugin::DependencyDropped(CPlugin *pOwner) return; } + List::iterator reqlib_iter; + List::iterator lib_iter; + for (lib_iter=pOwner->m_Libraries.begin(); lib_iter!=pOwner->m_Libraries.end(); lib_iter++) + { + for (reqlib_iter=m_RequiredLibs.begin(); reqlib_iter!=m_RequiredLibs.end(); reqlib_iter++) + { + if ((*reqlib_iter) == (*lib_iter)) + { + m_LibraryMissing = true; + } + } + } + List::iterator iter; FakeNative *pNative; sp_native_t *native; @@ -615,10 +632,14 @@ void CPlugin::DependencyDropped(CPlugin *pOwner) unbound++; } - /* :IDEA: in the future, add native trapping? */ if (unbound) { m_FakeNativesMissing = true; + } + + /* :IDEA: in the future, add native trapping? */ + if (m_FakeNativesMissing || m_LibraryMissing) + { SetErrorState(Plugin_Error, "Depends on plugin: %s", pOwner->GetFilename()); } @@ -1001,6 +1022,96 @@ void CPluginManager::LoadAll_SecondPass() m_AllPluginsLoaded = true; } +bool CPluginManager::FindOrRequirePluginDeps(CPlugin *pPlugin, char *error, size_t maxlength) +{ + struct _pl + { + cell_t name; + cell_t file; + cell_t required; + } *pl; + + IPluginContext *pBase = pPlugin->GetBaseContext(); + uint32_t num = pBase->GetPubVarsNum(); + sp_pubvar_t *pubvar; + char *name, *file; + char pathfile[PLATFORM_MAX_PATH]; + + for (uint32_t i=0; iGetPubvarByIndex(i, &pubvar) != SP_ERROR_NONE) + { + continue; + } + if (strncmp(pubvar->name, "__pl_", 5) == 0) + { + pl = (_pl *)pubvar->offs; + if (pBase->LocalToString(pl->file, &file) != SP_ERROR_NONE) + { + continue; + } + if (pBase->LocalToString(pl->name, &name) != SP_ERROR_NONE) + { + continue; + } + g_LibSys.GetFileFromPath(pathfile, sizeof(pathfile), pPlugin->GetFilename()); + if (strcmp(pathfile, file) == 0) + { + continue; + } + if (pl->required == false) + { + IPluginFunction *pFunc; + char buffer[64]; + UTIL_Format(buffer, sizeof(buffer), "__pl_%s_SetNTVOptional", &pubvar->name[6]); + if ((pFunc=pBase->GetFunctionByName(buffer))) + { + cell_t res; + pFunc->Execute(&res); + if (pPlugin->GetContext()->n_err != SP_ERROR_NONE) + { + if (error) + { + snprintf(error, maxlength, "Fatal error during initializing plugin load"); + } + return false; + } + } + } else { + /* Check that we aren't registering the same library twice */ + if (pPlugin->m_RequiredLibs.find(name) == pPlugin->m_RequiredLibs.end()) + { + pPlugin->m_RequiredLibs.push_back(name); + } else { + continue; + } + List::iterator iter; + CPlugin *pl; + bool found = false; + for (iter=m_plugins.begin(); iter!=m_plugins.end(); iter++) + { + pl = (*iter); + if (pl->m_Libraries.find(name) != pl->m_Libraries.end()) + { + found = true; + break; + } + } + if (!found) + { + if (error) + { + snprintf(error, maxlength, "Could not find required plugin \"%s\"", name); + } + return false; + } + } + } + } + + return true; +} + bool CPluginManager::LoadOrRequireExtensions(CPlugin *pPlugin, unsigned int pass, char *error, size_t maxlength) { /* Find any extensions this plugin needs */ @@ -1089,6 +1200,11 @@ bool CPluginManager::RunSecondPass(CPlugin *pPlugin, char *error, size_t maxleng g_Extensions.BindAllNativesToPlugin(pPlugin); AddFakeNativesToPlugin(pPlugin); + if (!FindOrRequirePluginDeps(pPlugin, error, maxlength)) + { + return false; + } + /* Find any unbound natives * Right now, these are not allowed */ @@ -1136,9 +1252,9 @@ bool CPluginManager::RunSecondPass(CPlugin *pPlugin, char *error, size_t maxleng { pOther = (*pl_iter); if (pOther->GetStatus() == Plugin_Error - && pOther->m_FakeNativesMissing) + && (pOther->m_FakeNativesMissing || pOther->m_LibraryMissing)) { - TryRefreshNatives(pOther); + TryRefreshDependencies(pOther); } } } @@ -1163,12 +1279,37 @@ void CPluginManager::AddCoreNativesToPlugin(CPlugin *pPlugin) } } -void CPluginManager::TryRefreshNatives(CPlugin *pPlugin) +void CPluginManager::TryRefreshDependencies(CPlugin *pPlugin) { assert(pPlugin->GetBaseContext() != NULL); AddFakeNativesToPlugin(pPlugin); + List::iterator lib_iter; + List::iterator req_iter; + List::iterator pl_iter; + CPlugin *pl; + for (req_iter=pPlugin->m_RequiredLibs.begin(); req_iter!=pPlugin->m_RequiredLibs.end(); req_iter++) + { + bool found = false; + for (pl_iter=m_plugins.begin(); pl_iter!=m_plugins.end(); pl_iter++) + { + pl = (*pl_iter); + for (lib_iter=pl->m_Libraries.begin(); lib_iter!=pl->m_Libraries.end(); lib_iter++) + { + if ((*req_iter) == (*lib_iter)) + { + found = true; + } + } + } + if (!found) + { + pPlugin->SetErrorState(Plugin_Error, "Library not found: %s", (*req_iter).c_str()); + return; + } + } + /* Find any unbound natives * Right now, these are not allowed */ @@ -1220,8 +1361,10 @@ void CPluginManager::AddFakeNativesToPlugin(CPlugin *pPlugin) { uint32_t idx; pContext->FindNativeByName(native.name, &idx); - if (!(pPlugin->GetContext()->natives[idx].flags & SP_NTVFLAG_OPTIONAL)) + if (pPlugin->GetContext()->natives[idx].flags & SP_NTVFLAG_OPTIONAL) { + WeakNative *wkn = new WeakNative(pPlugin, idx); + GetPluginByCtx(ctx)->m_WeakNatives.push_back(wkn); continue; } /* Add us as a dependency, but we're careful not to do this circularly! */ @@ -1275,20 +1418,6 @@ bool CPluginManager::UnloadPlugin(IPlugin *plugin) pOther->m_dependents.remove(pPlugin); } - /* Remove all of our native functions */ - List::iterator fn_iter; - FakeNative *pNative; - for (fn_iter = pPlugin->m_fakeNatives.begin(); - fn_iter != pPlugin->m_fakeNatives.end(); - fn_iter++) - { - pNative = (*fn_iter); - m_Natives.remove(pNative); - sm_trie_delete(m_pNativeLookup, pNative->name.c_str()); - g_pVM->DestroyFakeNative(pNative->func); - delete pNative; - } - List::iterator iter; IPluginsListener *pListener; @@ -1304,6 +1433,30 @@ bool CPluginManager::UnloadPlugin(IPlugin *plugin) pPlugin->Call_OnPluginEnd(); } + /* Remove all of our native functions */ + List::iterator fn_iter; + FakeNative *pNative; + for (fn_iter = pPlugin->m_fakeNatives.begin(); + fn_iter != pPlugin->m_fakeNatives.end(); + fn_iter++) + { + pNative = (*fn_iter); + m_Natives.remove(pNative); + sm_trie_delete(m_pNativeLookup, pNative->name.c_str()); + g_pVM->DestroyFakeNative(pNative->func); + delete pNative; + } + + /* Unbound weak natives */ + WeakNative *wkn; + List::iterator wk_iter; + for (wk_iter=pPlugin->m_WeakNatives.begin(); wk_iter!=pPlugin->m_WeakNatives.end(); wk_iter++) + { + wkn = (*wk_iter); + sp_context_t *ctx = wkn->pl->GetContext(); + ctx->natives[wkn->idx].status = SP_NATIVE_UNBOUND; + } + for (iter=m_listeners.begin(); iter!=m_listeners.end(); iter++) { /* Notify listeners of destruction */ diff --git a/core/systems/PluginSys.h b/core/systems/PluginSys.h index e9abf70c..031c41f2 100644 --- a/core/systems/PluginSys.h +++ b/core/systems/PluginSys.h @@ -116,6 +116,18 @@ struct AutoConfig bool create; }; +class CPlugin; +struct WeakNative +{ + WeakNative(CPlugin *plugin, uint32_t index) + { + pl = plugin; + idx = index; + } + CPlugin *pl; + uint32_t idx; +}; + class CPlugin : public IPlugin { friend class CPluginManager; @@ -234,9 +246,9 @@ public: void AddConfig(bool autoCreate, const char *cfg, const char *folder); unsigned int GetConfigCount(); AutoConfig *GetConfig(unsigned int i); - inline bool IsInAskPluginLoad() + inline void AddLibrary(const char *name) { - return m_IsInAskPluginLoad; + m_Libraries.push_back(name); } protected: void UpdateInfo(); @@ -259,10 +271,13 @@ private: List m_dependents; List m_dependsOn; List m_fakeNatives; + List m_WeakNatives; + List m_RequiredLibs; + List m_Libraries; Trie *m_pProps; bool m_FakeNativesMissing; + bool m_LibraryMissing; CVector m_configs; - bool m_IsInAskPluginLoad; }; class CPluginManager : @@ -413,6 +428,11 @@ private: */ bool LoadOrRequireExtensions(CPlugin *pPlugin, unsigned int pass, char *error, size_t maxlength); + /** + * Manages required natives. + */ + bool FindOrRequirePluginDeps(CPlugin *pPlugin, char *error, size_t maxlength); + void _SetPauseState(CPlugin *pPlugin, bool pause); void ExecAndGenPluginConfs(); @@ -430,7 +450,7 @@ public: bool AddFakeNative(IPluginFunction *pFunction, const char *name, SPVM_FAKENATIVE_FUNC func); private: void AddFakeNativesToPlugin(CPlugin *pPlugin); - void TryRefreshNatives(CPlugin *pOther); + void TryRefreshDependencies(CPlugin *pOther); private: List m_listeners; List m_plugins; diff --git a/plugins/include/sourcemod.inc b/plugins/include/sourcemod.inc index 196774bd..1f5ff435 100644 --- a/plugins/include/sourcemod.inc +++ b/plugins/include/sourcemod.inc @@ -329,5 +329,24 @@ native GetSysTickCount(); */ native AutoExecConfig(bool:autoCreate=true, const String:name[]="", const String:folder[]="sourcemod"); +/** + * Sets a native as optional, such that if it is unloaded, removed, + * or otherwise non-existent, the plugin will still work. Calling + * removed natives results in a run-time error. + * + * @param name Native name. + * @noreturn + */ +native MarkNativeAsOptional(const String:name[]); + +/** + * Registers a library name for identifying as a dependency to + * other plugins. + * + * @param name Library name. + * @noreturn + */ +native RegPluginLibrary(const String:name[]); + #include #include