diff --git a/core/systems/ExtensionSys.cpp b/core/systems/ExtensionSys.cpp index 88b6ea52..cd110370 100644 --- a/core/systems/ExtensionSys.cpp +++ b/core/systems/ExtensionSys.cpp @@ -41,14 +41,26 @@ CExtensionManager g_Extensions; IdentityType_t g_ExtType; -CExtension::CExtension(const char *filename, char *error, size_t maxlength) +void CExtension::Initialize(const char *filename, const char *path) { - m_File.assign(filename); m_pAPI = NULL; m_pIdentToken = NULL; - m_PlId = 0; unload_code = 0; - m_FullyLoaded = false; + m_bFullyLoaded = false; + m_File.assign(filename); + m_Path.assign(path); +} + +CRemoteExtension::CRemoteExtension(IExtensionInterface *pAPI, const char *filename, const char *path) +{ + m_pAPI = pAPI; + Initialize(filename, path); +} + +CLocalExtension::CLocalExtension(const char *filename) +{ + m_PlId = 0; + m_pLib = NULL; char path[PLATFORM_MAX_PATH]; @@ -73,12 +85,27 @@ CExtension::CExtension(const char *filename, char *error, size_t maxlength) g_SourceMod.BuildPath(Path_SM, path, PLATFORM_MAX_PATH, "extensions/%s", filename); } - m_Path.assign(path); - - m_pLib = NULL; + Initialize(filename, path); } -bool CExtension::Load(char *error, size_t maxlength) +bool CRemoteExtension::Load(char *error, size_t maxlength) +{ + if (!PerformAPICheck(error, maxlength)) + { + m_pAPI = NULL; + return false; + } + + if (!CExtension::Load(error, maxlength)) + { + m_pAPI = NULL; + return false; + } + + return true; +} + +bool CLocalExtension::Load(char *error, size_t maxlength) { m_pLib = g_LibSys.OpenLibrary(m_Path.c_str(), error, maxlength); @@ -99,23 +126,24 @@ bool CExtension::Load(char *error, size_t maxlength) } m_pAPI = pfnGetAPI(); - if (!m_pAPI || m_pAPI->GetExtensionVersion() > SMINTERFACE_EXTENSIONAPI_VERSION) + + /* Check pointer and version */ + if (!PerformAPICheck(error, maxlength)) { m_pLib->CloseLibrary(); m_pLib = NULL; - snprintf(error, maxlength, "Extension version is too new to load (%d, max is %d)", m_pAPI->GetExtensionVersion(), SMINTERFACE_EXTENSIONAPI_VERSION); + m_pAPI = NULL; return false; } + /* Load as MM:S */ if (m_pAPI->IsMetamodExtension()) { bool already; m_PlId = g_pMMPlugins->Load(m_Path.c_str(), g_PLID, already, error, maxlength); } - m_pIdentToken = g_ShareSys.CreateIdentity(g_ExtType, this); - - if (!m_pAPI->OnExtensionLoad(this, &g_ShareSys, error, maxlength, !g_SourceMod.IsMapLoading())) + if (!CExtension::Load(error, maxlength)) { if (m_pAPI->IsMetamodExtension()) { @@ -126,13 +154,71 @@ bool CExtension::Load(char *error, size_t maxlength) m_PlId = 0; } } - m_pAPI = NULL; m_pLib->CloseLibrary(); m_pLib = NULL; - g_ShareSys.DestroyIdentity(m_pIdentToken); - m_pIdentToken = NULL; + m_pAPI = NULL; return false; - } else { + } + + return true; +} + +void CRemoteExtension::Unload() +{ +} + +void CLocalExtension::Unload() +{ + if (m_pAPI != NULL && m_PlId) + { + char temp_buffer[255]; + g_pMMPlugins->Unload(m_PlId, true, temp_buffer, sizeof(temp_buffer)); + m_PlId = 0; + } + + if (m_pLib != NULL) + { + m_pLib->CloseLibrary(); + m_pLib = NULL; + } +} + +bool CRemoteExtension::IsExternal() +{ + return true; +} + +bool CLocalExtension::IsExternal() +{ + return false; +} + +CExtension::~CExtension() +{ + DestroyIdentity(); +} + +bool CExtension::PerformAPICheck(char *error, size_t maxlength) +{ + if (!m_pAPI || m_pAPI->GetExtensionVersion() > SMINTERFACE_EXTENSIONAPI_VERSION) + { + snprintf(error, maxlength, "Extension version is too new to load (%d, max is %d)", m_pAPI->GetExtensionVersion(), SMINTERFACE_EXTENSIONAPI_VERSION); + return false; + } + + return true; +} + +bool CExtension::Load(char *error, size_t maxlength) +{ + CreateIdentity(); + if (!m_pAPI->OnExtensionLoad(this, &g_ShareSys, error, maxlength, !g_SourceMod.IsMapLoading())) + { + DestroyIdentity(); + return false; + } + else + { /* Check if we're past load time */ if (!g_SourceMod.IsMapLoading()) { @@ -143,35 +229,33 @@ bool CExtension::Load(char *error, size_t maxlength) return true; } -CExtension::~CExtension() +void CExtension::CreateIdentity() { - char temp_buffer[255]; - - if (m_pAPI) + if (m_pIdentToken != NULL) { - if (m_PlId) - { - g_pMMPlugins->Unload(m_PlId, true, temp_buffer, sizeof(temp_buffer)); - } + return; } - if (m_pIdentToken) + m_pIdentToken = g_ShareSys.CreateIdentity(g_ExtType, this); +} + +void CExtension::DestroyIdentity() +{ + if (m_pIdentToken == NULL) { - g_ShareSys.DestroyIdentity(m_pIdentToken); + return; } - if (m_pLib) - { - m_pLib->CloseLibrary(); - } + g_ShareSys.DestroyIdentity(m_pIdentToken); + m_pIdentToken = NULL; } void CExtension::MarkAllLoaded() { - assert(m_FullyLoaded == false); - if (!m_FullyLoaded) + assert(m_bFullyLoaded == false); + if (!m_bFullyLoaded) { - m_FullyLoaded = true; + m_bFullyLoaded = true; m_pAPI->OnExtensionsAllLoaded(); } } @@ -221,7 +305,12 @@ IdentityToken_t *CExtension::GetIdentity() return m_pIdentToken; } -bool CExtension::IsLoaded() +bool CRemoteExtension::IsLoaded() +{ + return (m_pAPI != NULL); +} + +bool CLocalExtension::IsLoaded() { return (m_pLib != NULL); } @@ -435,7 +524,7 @@ IExtension *CExtensionManager::LoadAutoExtension(const char *path) } char error[256]; - CExtension *p = new CExtension(path, error, sizeof(error)); + CExtension *p = new CLocalExtension(path); /* We put us in the list beforehand so extensions that check for each other * won't recursively load each other. @@ -456,13 +545,6 @@ IExtension *CExtensionManager::FindExtensionByFile(const char *file) List::iterator iter; CExtension *pExt; - if (!strstr(file, "." PLATFORM_LIB_EXT)) - { - char path[PLATFORM_MAX_PATH]; - UTIL_Format(path, sizeof(path), "%s.%s", file, PLATFORM_LIB_EXT); - return FindExtensionByFile(path); - } - /* Chomp off the path */ char lookup[PLATFORM_MAX_PATH]; g_LibSys.GetFileFromPath(lookup, sizeof(lookup), file); @@ -478,6 +560,16 @@ IExtension *CExtensionManager::FindExtensionByFile(const char *file) } } + /* If we got no results, test if there was a platform extension. + * If not, add one. + */ + if (!strstr(file, "." PLATFORM_LIB_EXT)) + { + char path[PLATFORM_MAX_PATH]; + UTIL_Format(path, sizeof(path), "%s.%s", file, PLATFORM_LIB_EXT); + return FindExtensionByFile(path); + } + return NULL; } @@ -513,7 +605,7 @@ IExtension *CExtensionManager::FindExtensionByName(const char *ext) return NULL; } -IExtension *CExtensionManager::LoadExtension(const char *file, ExtensionLifetime lifetime, char *error, size_t maxlength) +IExtension *CExtensionManager::LoadExtension(const char *file, char *error, size_t maxlength) { IExtension *pAlready; if ((pAlready=FindExtensionByFile(file)) != NULL) @@ -521,12 +613,11 @@ IExtension *CExtensionManager::LoadExtension(const char *file, ExtensionLifetime return pAlready; } - CExtension *pExt = new CExtension(file, error, maxlength); - - /* :NOTE: lifetime is currently ignored */ + CExtension *pExt = new CLocalExtension(file); if (!pExt->Load(error, maxlength) || !pExt->IsLoaded()) { + pExt->Unload(); delete pExt; return NULL; } @@ -790,6 +881,7 @@ bool CExtensionManager::UnloadExtension(IExtension *_pExt) } } + pExt->Unload(); delete pExt; List::iterator iter; @@ -833,7 +925,7 @@ void CExtensionManager::MarkAllLoaded() { continue; } - if (pExt->m_FullyLoaded) + if (pExt->m_bFullyLoaded) { continue; } @@ -891,7 +983,9 @@ void CExtensionManager::OnRootConsoleCommand(const char *cmdname, const CCommand if (!pExt->IsRunning(error, sizeof(error))) { g_RootMenu.ConsolePrint("[%02d] file \"%s\": %s", num, pExt->GetFilename(), error); - } else { + } + else + { IExtensionInterface *pAPI = pExt->GetAPI(); const char *name = pAPI->GetExtensionName(); const char *version = pAPI->GetExtensionVerString(); @@ -903,7 +997,9 @@ void CExtensionManager::OnRootConsoleCommand(const char *cmdname, const CCommand } } return; - } else if (strcmp(cmd, "info") == 0) { + } + else if (strcmp(cmd, "info") == 0) + { if (argcount < 4) { g_RootMenu.ConsolePrint("[SM] Usage: sm info <#>"); @@ -952,25 +1048,43 @@ void CExtensionManager::OnRootConsoleCommand(const char *cmdname, const CCommand { g_RootMenu.ConsolePrint(" File: %s", pExt->GetFilename()); g_RootMenu.ConsolePrint(" Loaded: No (%s)", pExt->m_Error.c_str()); - } else { + } + else + { char error[255]; if (!pExt->IsRunning(error, sizeof(error))) { g_RootMenu.ConsolePrint(" File: %s", pExt->GetFilename()); g_RootMenu.ConsolePrint(" Loaded: Yes"); g_RootMenu.ConsolePrint(" Running: No (%s)", error); - } else { + } + else + { IExtensionInterface *pAPI = pExt->GetAPI(); g_RootMenu.ConsolePrint(" File: %s", pExt->GetFilename()); g_RootMenu.ConsolePrint(" Loaded: Yes (version %s)", pAPI->GetExtensionVerString()); g_RootMenu.ConsolePrint(" Name: %s (%s)", pAPI->GetExtensionName(), pAPI->GetExtensionDescription()); g_RootMenu.ConsolePrint(" Author: %s (%s)", pAPI->GetExtensionAuthor(), pAPI->GetExtensionURL()); g_RootMenu.ConsolePrint(" Binary info: API version %d (compiled %s)", pAPI->GetExtensionVersion(), pAPI->GetExtensionDateString()); - g_RootMenu.ConsolePrint(" Metamod enabled: %s", pAPI->IsMetamodExtension() ? "yes" : "no"); + + if (pExt->IsExternal()) + { + g_RootMenu.ConsolePrint(" Method: Loaded by Metamod:Source, attached to SourceMod"); + } + else if (pAPI->IsMetamodExtension()) + { + g_RootMenu.ConsolePrint(" Method: Loaded by SourceMod, attached to Metamod:Source"); + } + else + { + g_RootMenu.ConsolePrint(" Method: Loaded by SourceMod"); + } } } return; - } else if (strcmp(cmd, "unload") == 0) { + } + else if (strcmp(cmd, "unload") == 0) + { if (argcount < 4) { g_RootMenu.ConsolePrint("[SM] Usage: sm unload <#> [code]"); @@ -1141,3 +1255,29 @@ bool CExtensionManager::LibraryExists(const char *library) return false; } + +IExtension *CExtensionManager::LoadExternal(IExtensionInterface *pInterface, + const char *filepath, + const char *filename, + char *error, + size_t maxlength) +{ + IExtension *pAlready; + if ((pAlready=FindExtensionByFile(filename)) != NULL) + { + return pAlready; + } + + CExtension *pExt = new CRemoteExtension(pInterface, filename, filepath); + + if (!pExt->Load(error, maxlength) || !pExt->IsLoaded()) + { + pExt->Unload(); + delete pExt; + return NULL; + } + + m_Libs.push_back(pExt); + + return pExt; +} diff --git a/core/systems/ExtensionSys.h b/core/systems/ExtensionSys.h index 0816fb0c..1fe29f1c 100644 --- a/core/systems/ExtensionSys.h +++ b/core/systems/ExtensionSys.h @@ -59,13 +59,11 @@ class CExtension : public IExtension { friend class CExtensionManager; public: - CExtension(const char *filename, char *error, size_t maxlen); - ~CExtension(); + virtual ~CExtension(); public: //IExtension IExtensionInterface *GetAPI(); const char *GetFilename(); IdentityToken_t *GetIdentity(); - bool IsLoaded(); ITERATOR *FindFirstDependency(IExtension **pOwner, SMInterface **pInterface); bool FindNextDependency(ITERATOR *iter, IExtension **pOwner, SMInterface **pInterface); void FreeDependencyIterator(ITERATOR *iter); @@ -79,14 +77,20 @@ public: void RemovePlugin(IPlugin *pPlugin); void MarkAllLoaded(); void AddLibrary(const char *library); -private: - bool Load(char *error, size_t maxlength); -private: +public: + virtual bool Load(char *error, size_t maxlength); + virtual bool IsLoaded() =0; + virtual void Unload() =0; +protected: + void Initialize(const char *filename, const char *path); + bool PerformAPICheck(char *error, size_t maxlength); + void CreateIdentity(); + void DestroyIdentity(); +protected: IdentityToken_t *m_pIdentToken; IExtensionInterface *m_pAPI; String m_File; String m_Path; - ILibrary *m_pLib; String m_Error; List m_Deps; /** Dependencies */ List m_ChildDeps; /** Children who might depend on us */ @@ -95,9 +99,33 @@ private: List m_Natives; List m_WeakNatives; List m_Libraries; - PluginId m_PlId; unsigned int unload_code; - bool m_FullyLoaded; + bool m_bFullyLoaded; +}; + +class CLocalExtension : public CExtension +{ +public: + CLocalExtension(const char *filename); +public: + bool Load(char *error, size_t maxlength); + bool IsLoaded(); + void Unload(); + bool IsExternal(); +private: + PluginId m_PlId; + ILibrary *m_pLib; +}; + +class CRemoteExtension : public CExtension +{ +public: + CRemoteExtension(IExtensionInterface *pAPI, const char *filename, const char *path); +public: + bool Load(char *error, size_t maxlength); + bool IsLoaded(); + void Unload(); + bool IsExternal(); }; class CExtensionManager : @@ -114,12 +142,16 @@ public: //SMGlobalClass void OnSourceModShutdown(); public: //IExtensionManager IExtension *LoadExtension(const char *path, - ExtensionLifetime lifetime, char *error, size_t maxlength); bool UnloadExtension(IExtension *pExt); IExtension *FindExtensionByFile(const char *file); IExtension *FindExtensionByName(const char *ext); + IExtension *LoadExternal(IExtensionInterface *pInterface, + const char *filepath, + const char *filename, + char *error, + size_t maxlength); public: //IPluginsListener void OnPluginDestroyed(IPlugin *plugin); public: //IRootConsoleCommand diff --git a/public/IExtensionSys.h b/public/IExtensionSys.h index 3a7fe58c..9667095d 100644 --- a/public/IExtensionSys.h +++ b/public/IExtensionSys.h @@ -65,7 +65,9 @@ namespace SourceMod /** * @brief Returns the filename of the extension, relative to the - * extension folder. + * extension folder. If the extension is an "external" extension, + * the file path is specified by the extension itself, and may be + * arbitrary (not real). * * @return A string containing the extension file name. */ @@ -112,6 +114,15 @@ namespace SourceMod * @return True if extension is okay, false if not okay. */ virtual bool IsRunning(char *error, size_t maxlength) =0; + + /** + * @brief Returns whether the extension is local (from the extensions + * folder), or is from an external source (such as Metamod:Source). + * + * @return True if from an external source, + * false if local to SourceMod. + */ + virtual bool IsExternal() =0; }; /** @@ -142,10 +153,10 @@ namespace SourceMod * @return True if load should continue, false otherwise. */ virtual bool OnExtensionLoad(IExtension *me, - IShareSys *sys, - char *error, - size_t maxlength, - bool late) =0; + IShareSys *sys, + char *error, + size_t maxlength, + bool late) =0; /** * @brief Called when the extension is about to be unloaded. @@ -166,8 +177,10 @@ namespace SourceMod virtual void OnExtensionPauseChange(bool pause) =0; /** - * @brief Asks the extension whether it's safe to remove an external interface it's using. - * If it's not safe, return false, and the extension will be unloaded afterwards. + * @brief Asks the extension whether it's safe to remove an external + * interface it's using. If it's not safe, return false, and the + * extension will be unloaded afterwards. + * * NOTE: It is important to also hook NotifyInterfaceDrop() in order to clean up resources. * * @param pInterface Pointer to interface being dropped. @@ -199,27 +212,84 @@ namespace SourceMod return true; } public: + /** + * @brief For extensions loaded through SourceMod, this should return true + * if the extension needs to attach to Metamod:Source. If the extension + * is loaded through Metamod:Source, and uses SourceMod optionally, it must + * return false. + * + * @return True if Metamod:Source is needed. + */ virtual bool IsMetamodExtension() =0; + + /** + * @brief Must return a string containing the extension's short name. + * + * @return String containing extension name. + */ virtual const char *GetExtensionName() =0; + + /** + * @brief Must return a string containing the extension's URL. + * + * @return String containing extension URL. + */ virtual const char *GetExtensionURL() =0; + + /** + * @brief Must return a string containing a short identifier tag. + * + * @return String containing extension tag. + */ virtual const char *GetExtensionTag() =0; + + /** + * @brief Must return a string containing a short author identifier. + * + * @return String containing extension author. + */ virtual const char *GetExtensionAuthor() =0; + + /** + * @brief Must return a string containing version information. + * + * Any version string format can be used, however, SourceMod + * makes a special guarantee version numbers in the form of + * A.B.C.D will always be fully displayed, where: + * + * A is a major version number of at most one digit. + * B is a minor version number of at most two digits. + * C is a minor version number of at most two digits. + * D is a build number of at most 5 digits. + * + * Thus, thirteen characters of display is guaranteed. + * + * @return String containing extension version. + */ virtual const char *GetExtensionVerString() =0; + + /** + * @brief Must return a string containing description text. + * + * The description text may be longer than the other identifiers, + * as it is only displayed when viewing one extension at a time. + * However, it should not have newlines, or any other characters + * which would otherwise disrupt the display pattern. + * + * @return String containing extension description. + */ virtual const char *GetExtensionDescription() =0; + + /** + * @brief Must return a string containing the compilation date. + * + * @return String containing the compilation date. + */ virtual const char *GetExtensionDateString() =0; }; #define SMINTERFACE_EXTENSIONMANAGER_NAME "IExtensionManager" - #define SMINTERFACE_EXTENSIONMANAGER_VERSION 1 - - /** - * @brief Not currently used. - */ - enum ExtensionLifetime - { - ExtLifetime_Forever, //Extension will never be unloaded automatically - ExtLifetime_Map, //Extension will be unloaded at the end of the map - }; + #define SMINTERFACE_EXTENSIONMANAGER_VERSION 2 /** * @brief Manages the loading/unloading of extensions. @@ -235,26 +305,66 @@ namespace SourceMod { return SMINTERFACE_EXTENSIONMANAGER_VERSION; } + virtual bool IsVersionCompatible(unsigned int version) + { + if (version < 2) + { + return false; + } + return SMInterface::IsVersionCompatible(version); + } public: /** * @brief Loads a extension into the extension system. * - * @param path Path to extension file, relative to the extensions folder. - * @param lifetime Lifetime of the extension (currently ignored). - * @param error Error buffer. - * @param maxlength Maximum error buffer length. - * @return New IExtension on success, NULL on failure. + * @param path Path to extension file, relative to the + * extensions folder. + * @param lifetime Lifetime of the extension (currently ignored). + * @param error Error buffer. + * @param maxlength Maximum error buffer length. + * @return New IExtension on success, NULL on failure. + * If NULL is returned, the error buffer will be + * filled with a null-terminated string. */ virtual IExtension *LoadExtension(const char *path, - ExtensionLifetime lifetime, - char *error, - size_t maxlength) =0; + char *error, + size_t maxlength) =0; /** - * @brief Attempts to unload a module. + * @brief Loads an extension into the extension system, directly, + * as an external extension. * - * @param pExt IExtension pointer. - * @return True if successful, false otherwise. + * The extension receives all normal callbacks. However, it is + * never opened via LoadLibrary/dlopen or closed via FreeLibrary + * or dlclose. + * + * @param pInterface Pointer to an IExtensionInterface instance. + * @param filepath Absolute path to the extension's file. + * @param filename Name to use to uniquely identify the extension. + * The name should be generic, without any + * platform-specific suffices. For example, + * sdktools.ext instead of sdktools.ext.so. + * This filename is used to detect if the + * extension is already loaded, and to verify + * plugins that require the same extension. + * @param error Buffer to store error message. + * @param maxlength Maximum size of the error buffer. + * @return IExtension pointer on success, NULL on failure. + * If NULL is returned, the error buffer will be + * filled with a null-terminated string. + */ + virtual IExtension *LoadExternal(IExtensionInterface *pInterface, + const char *filepath, + const char *filename, + char *error, + size_t maxlength) =0; + + /** + * @brief Attempts to unload an extension. External extensions must + * call this before unloading. + * + * @param pExt IExtension pointer. + * @return True if successful, false otherwise. */ virtual bool UnloadExtension(IExtension *pExt) =0; };