From 3564cd0da002cc81b543d90627126c585952ed69 Mon Sep 17 00:00:00 2001 From: wootguy Date: Tue, 5 Nov 2024 21:18:02 -0800 Subject: [PATCH] add plugin update commands and cvars added command "updateplugin" and "updateplugins". The path at cvar "plugin_update_path" is searched for updated plugins when running one of these commands. If a matching plugin in the update path is found, a backup of the current plugin is made, then the updated plugin is moved into place. If the updated plugin fails to load, the backup is restored. updateplugins will also check plugins.txt for added/removed plugins if "plugin_auto_update" is "1" then plugins will be updated every map change. --- dlls/PluginManager.cpp | 173 +++++++++++++++++++++++++++++++++-------- dlls/PluginManager.h | 12 ++- dlls/game.cpp | 25 ++++++ dlls/game.h | 2 + 4 files changed, 178 insertions(+), 34 deletions(-) diff --git a/dlls/PluginManager.cpp b/dlls/PluginManager.cpp index c5f83713..f2c2b66b 100644 --- a/dlls/PluginManager.cpp +++ b/dlls/PluginManager.cpp @@ -78,6 +78,8 @@ bool PluginManager::AddPlugin(const char* fpath, bool isMapPlugin) { } bool PluginManager::LoadPlugin(Plugin& plugin) { + g_engfuncs.pfnServerPrint(UTIL_VarArgs("Loading plugin '%s'\n", plugin.fpath.c_str())); + #ifdef _WIN32 plugin.h_module = LoadLibraryA(plugin.fpath.c_str()); #else @@ -96,7 +98,7 @@ bool PluginManager::LoadPlugin(Plugin& plugin) { if (apiFunc) { int apiVersion = HLCOOP_API_VERSION; if (apiFunc(&plugin, apiVersion)) { - g_engfuncs.pfnServerPrint(UTIL_VarArgs("Loaded plugin '%s'\n", plugin.fpath.c_str())); + // success } else { ALERT(at_error, "PluginInit call failed in plugin '%s'.\n", plugin.fpath.c_str()); @@ -114,6 +116,8 @@ bool PluginManager::LoadPlugin(Plugin& plugin) { } void PluginManager::UnloadPlugin(const Plugin& plugin) { + g_engfuncs.pfnServerPrint(UTIL_VarArgs("Removing plugin: '%s'\n", plugin.fpath.c_str())); + PLUGIN_EXIT_FUNCTION apiFunc = (PLUGIN_EXIT_FUNCTION)GetProcAddress((HMODULE)plugin.h_module, "PluginExit"); @@ -127,7 +131,6 @@ void PluginManager::UnloadPlugin(const Plugin& plugin) { g_Scheduler.RemoveTimers(plugin.name); FreeLibrary((HMODULE)plugin.h_module); - g_engfuncs.pfnServerPrint(UTIL_VarArgs("Removed plugin: '%s'\n", plugin.fpath.c_str())); } void PluginManager::RemovePlugin(const Plugin& plugin) { @@ -142,51 +145,127 @@ void PluginManager::RemovePlugin(const Plugin& plugin) { } void PluginManager::RemovePlugin(const char* name) { - int bestIdx = -1; - int numFound = 0; + Plugin* plug = FindPlugin(name); - std::string lowerName = toLowerCase(name); + if (plug) { + RemovePlugin(*plug); + } +} - for (int i = 0; i < (int)plugins.size(); i++) { - if (toLowerCase(plugins[i].fpath).find(lowerName) != std::string::npos) { - bestIdx = i; - numFound++; - } +void PluginManager::ReloadPlugin(const char* name) { + Plugin* plug = FindPlugin(name); + + if (plug) { + UnloadPlugin(*plug); + LoadPlugin(*plug); } +} - if (numFound == 1) { - RemovePlugin(plugins[bestIdx]); +std::string PluginManager::GetUpdatedPluginPath(Plugin& plugin) { + std::string updatePath = plugin.fpath; + updatePath = updatePath.substr(updatePath.find("/") + 1); // strip content folder + return std::string(pluginupdatepath.string) + updatePath; +} + +bool PluginManager::UpdatePlugin(const char* name) { + Plugin* plug = FindPlugin(name); + + if (plug) + return UpdatePlugin(*plug); + + return false; +} + +bool PluginManager::UpdatePlugin(Plugin& plugin) { + std::string updatePath = GetUpdatedPluginPath(plugin); + std::string currentPath = plugin.fpath; + + if (!fileExists(updatePath.c_str())) { + g_engfuncs.pfnServerPrint(UTIL_VarArgs("Update aborted. Updated plugin file not found \"%s\"\n", updatePath.c_str())); + return false; } - else if (numFound > 1) { - g_engfuncs.pfnServerPrint(UTIL_VarArgs("Multiple plugins contain '%s'. Be more specific.\n", name)); + if (!fileExists(currentPath.c_str())) { + g_engfuncs.pfnServerPrint(UTIL_VarArgs("Update aborted. Current plugin file not found \"%s\"\n", currentPath.c_str())); + return false; } - else { - g_engfuncs.pfnServerPrint(UTIL_VarArgs("No plugin found by name '%s'\n", name)); + + std::string backupPath = currentPath + ".backup"; + + errno = 0; + if (fileExists(backupPath.c_str()) && remove(backupPath.c_str())) { + g_engfuncs.pfnServerPrint(UTIL_VarArgs("Update aborted. Failed to delete backup plugin file \"%s\" (error %d)\n", backupPath.c_str(), errno)); + return false; + } + if (rename(currentPath.c_str(), backupPath.c_str()) == -1) { + g_engfuncs.pfnServerPrint(UTIL_VarArgs("Update aborted. Failed to create backup plugin file \"%s\" (error %d)\n", backupPath.c_str(), errno)); + return false; } -} -void PluginManager::ReloadPlugin(const char* name) { - int bestIdx = -1; - int numFound = 0; + UnloadPlugin(plugin); - std::string lowerName = toLowerCase(name); + if (rename(updatePath.c_str(), currentPath.c_str()) == -1) { + g_engfuncs.pfnServerPrint(UTIL_VarArgs("Update failed. File move from \"%s\" -> \"%s\" failed (error %d)\n", + updatePath.c_str(), currentPath.c_str(), errno)); - for (int i = 0; i < (int)plugins.size(); i++) { - if (toLowerCase(plugins[i].fpath).find(lowerName) != std::string::npos) { - bestIdx = i; - numFound++; + if (rename(backupPath.c_str(), currentPath.c_str()) == -1) { + g_engfuncs.pfnServerPrint(UTIL_VarArgs("Failed to restore backup plugin file \"%s\" (error %d)\n", backupPath.c_str(), errno)); } + else if (!LoadPlugin(plugin)) { + g_engfuncs.pfnServerPrint("Failed to reload the plugin\n"); + } + + return false; } - if (numFound == 1) { - UnloadPlugin(plugins[bestIdx]); - LoadPlugin(plugins[bestIdx]); + g_engfuncs.pfnServerPrint(UTIL_VarArgs("Moved \"%s\" -> \"%s\" \n", + updatePath.c_str(), currentPath.c_str(), errno)); + + if (!LoadPlugin(plugin)) { + g_engfuncs.pfnServerPrint("Update failed. Something went wrong when loading the updated plugin.\n"); + + if (rename(currentPath.c_str(), updatePath.c_str()) == -1) { + g_engfuncs.pfnServerPrint(UTIL_VarArgs("Failed to move the plugin back to the update path \"%s\" (error %d)\n", updatePath.c_str(), errno)); + if (remove(currentPath.c_str())) { + g_engfuncs.pfnServerPrint(UTIL_VarArgs("Failed to delete the updated plugin \"%s\" (error %d)\n", currentPath.c_str(), errno)); + } + } + + if (rename(backupPath.c_str(), currentPath.c_str()) == -1) { + g_engfuncs.pfnServerPrint(UTIL_VarArgs("Failed to restore backup plugin file \"%s\" (error %d)\n", backupPath.c_str(), errno)); + } + else { + if (!LoadPlugin(plugin)) { + g_engfuncs.pfnServerPrint("Failed to load the backup plugin\n"); + } + } + + return false; } - else if (numFound > 1) { - g_engfuncs.pfnServerPrint(UTIL_VarArgs("Multiple plugins contain '%s'. Be more specific.\n", name)); + + return true; +} + +bool PluginManager::UpdatePlugins() { + int updatecount = 0; + + for (Plugin& plugin : plugins) { + std::string updatePath = GetUpdatedPluginPath(plugin); + + if (!fileExists(updatePath.c_str())) { + continue; + } + + if (UpdatePlugin(plugin)) { + updatecount++; + } + } + + if (updatecount) { + g_engfuncs.pfnServerPrint(UTIL_VarArgs("Updated %d plugins\n", updatecount)); + return true; } else { - g_engfuncs.pfnServerPrint(UTIL_VarArgs("No plugin found by name '%s'\n", name)); + return false; } } @@ -205,7 +284,7 @@ void PluginManager::RemovePlugins(bool mapPluginsOnly) { plugins = newPluginList; } -void PluginManager::UpdateServerPlugins(bool forceUpdate) { +void PluginManager::UpdatePluginsFromList(bool forceUpdate) { static uint64_t lastEditTime = 0; const char* configPath = CVAR_GET_STRING("pluginlistfile"); @@ -230,6 +309,7 @@ void PluginManager::UpdateServerPlugins(bool forceUpdate) { lastEditTime = editTime; + std::vector relativePluginPaths; std::vector pluginPaths; int lineNum = 0; @@ -255,6 +335,7 @@ void PluginManager::UpdateServerPlugins(bool forceUpdate) { continue; } + relativePluginPaths.push_back(pluginPath); pluginPaths.push_back(gamePath); } @@ -345,7 +426,7 @@ void PluginManager::ReloadPlugins() { } plugins = loadedMapPlugins; - UpdateServerPlugins(true); + UpdatePluginsFromList(true); } void PluginManager::ListPlugins(edict_t* plr) { @@ -397,6 +478,32 @@ Plugin* PluginManager::FindPlugin(int id) { return NULL; } +Plugin* PluginManager::FindPlugin(const char* name) { + int bestIdx = -1; + int numFound = 0; + + std::string lowerName = toLowerCase(name); + + for (int i = 0; i < (int)plugins.size(); i++) { + if (toLowerCase(plugins[i].fpath).find(lowerName) != std::string::npos) { + bestIdx = i; + numFound++; + } + } + + if (numFound == 1) { + return &plugins[bestIdx]; + } + else if (numFound > 1) { + g_engfuncs.pfnServerPrint(UTIL_VarArgs("Multiple plugins contain '%s'. Be more specific.\n", name)); + } + else { + g_engfuncs.pfnServerPrint(UTIL_VarArgs("No plugin found by name '%s'\n", name)); + } + + return NULL; +} + ENTITYINIT PluginManager::GetCustomEntityInitFunc(const char* pname) { for (const Plugin& plugin : plugins) { ENTITYINIT initFunc = (ENTITYINIT)GetProcAddress((HMODULE)plugin.h_module, pname); diff --git a/dlls/PluginManager.h b/dlls/PluginManager.h index a9ee1013..7506e310 100644 --- a/dlls/PluginManager.h +++ b/dlls/PluginManager.h @@ -44,10 +44,18 @@ class PluginManager { void ReloadPlugin(const char* name); + std::string GetUpdatedPluginPath(Plugin& plugin); + + bool UpdatePlugin(const char* name); + + bool UpdatePlugin(Plugin& plugin); + + bool UpdatePlugins(); + void RemovePlugins(bool mapPluginsOnly); // will conditionally load/unload plugins if the config has been updated since the last call, unless forceUpdate=true - void UpdateServerPlugins(bool forceUpdate=false); + void UpdatePluginsFromList(bool forceUpdate=false); // reloads server plugins (not map plugins) void ReloadPlugins(); @@ -57,6 +65,8 @@ class PluginManager { Plugin* FindPlugin(int id); + Plugin* FindPlugin(const char* name); + template HOOK_RETURN_DATA CallHooks(Func hookFunction, Args&&... args) { HOOK_RETURN_DATA totalRet = {0, 0}; diff --git a/dlls/game.cpp b/dlls/game.cpp index ebb939da..cecfafa0 100644 --- a/dlls/game.cpp +++ b/dlls/game.cpp @@ -61,6 +61,8 @@ cvar_t mp_mergemodels ={"mp_mergemodels","0", FCVAR_SERVER, 0, 0 }; cvar_t mp_killfeed ={"mp_killfeed","1", FCVAR_SERVER, 0, 0 }; cvar_t pluginlistfile ={"pluginlistfile","plugins.txt", FCVAR_SERVER, 0, 0 }; cvar_t adminlistfile ={"adminlistfile","admins.txt", FCVAR_SERVER, 0, 0 }; +cvar_t pluginupdatepath ={"plugin_update_path","valve_pending/", FCVAR_SERVER, 0, 0 }; +cvar_t pluginautoupdate ={"plugin_auto_update", "0", FCVAR_SERVER, 0, 0 }; cvar_t soundvariety={"mp_soundvariety","0", FCVAR_SERVER, 0, 0 }; @@ -228,6 +230,25 @@ void reload_plugin() { g_pluginManager.ReloadPlugin(CMD_ARGV(1)); } +void update_plugin() { + if (CMD_ARGC() < 2) { + return; + } + + if (g_pluginManager.UpdatePlugin(CMD_ARGV(1))) { + g_engfuncs.pfnServerPrint("Plugin updated\n"); + } +} + +void update_plugins() { + g_pluginManager.UpdatePluginsFromList(); + + g_engfuncs.pfnServerPrint(UTIL_VarArgs("Searching update path \"%s\"\n", pluginupdatepath.string)); + if (!g_pluginManager.UpdatePlugins()) { + g_engfuncs.pfnServerPrint("Plugins are up-to-date\n"); + } +} + void test_command() { } @@ -248,6 +269,8 @@ void GameDLLInit( void ) g_engfuncs.pfnAddServerCommand("listplugins", list_plugins); g_engfuncs.pfnAddServerCommand("removeplugin", remove_plugin); g_engfuncs.pfnAddServerCommand("reloadplugin", reload_plugin); + g_engfuncs.pfnAddServerCommand("updateplugin", update_plugin); + g_engfuncs.pfnAddServerCommand("updateplugins", update_plugins); // Register cvars here: g_psv_gravity = CVAR_GET_POINTER( "sv_gravity" ); @@ -302,6 +325,8 @@ void GameDLLInit( void ) CVAR_REGISTER (&mp_killfeed); CVAR_REGISTER (&pluginlistfile); CVAR_REGISTER (&adminlistfile); + CVAR_REGISTER (&pluginupdatepath); + CVAR_REGISTER (&pluginautoupdate); CVAR_REGISTER (&mp_chattime); diff --git a/dlls/game.h b/dlls/game.h index e5e0eea3..2a496603 100644 --- a/dlls/game.h +++ b/dlls/game.h @@ -70,6 +70,8 @@ EXPORT extern cvar_t mp_mergemodels; // used merged models to save on model slot EXPORT extern cvar_t mp_killfeed; // 0 = off, 1 = player deaths, 2 = player kills/deaths, 3 = player + monster kills/deaths EXPORT extern cvar_t pluginlistfile; // name of the plugin list file EXPORT extern cvar_t adminlistfile; // name of the admin list file +EXPORT extern cvar_t pluginupdatepath; // root path for plugin file updates to be searched +EXPORT extern cvar_t pluginautoupdate; // root path for plugin file updates to be searched // Enables classic func_pushable physics (which is horribly broken, but fun) // The higher your FPS, the faster you can boost pushables. You also get boosted.