Skip to content

Commit

Permalink
add plugin update commands and cvars
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
wootguy committed Nov 6, 2024
1 parent 574e3b1 commit 3564cd0
Show file tree
Hide file tree
Showing 4 changed files with 178 additions and 34 deletions.
173 changes: 140 additions & 33 deletions dlls/PluginManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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());
Expand All @@ -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");

Expand All @@ -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) {
Expand All @@ -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;
}
}

Expand All @@ -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");

Expand All @@ -230,6 +309,7 @@ void PluginManager::UpdateServerPlugins(bool forceUpdate) {

lastEditTime = editTime;

std::vector<std::string> relativePluginPaths;
std::vector<std::string> pluginPaths;

int lineNum = 0;
Expand All @@ -255,6 +335,7 @@ void PluginManager::UpdateServerPlugins(bool forceUpdate) {
continue;
}

relativePluginPaths.push_back(pluginPath);
pluginPaths.push_back(gamePath);
}

Expand Down Expand Up @@ -345,7 +426,7 @@ void PluginManager::ReloadPlugins() {
}
plugins = loadedMapPlugins;

UpdateServerPlugins(true);
UpdatePluginsFromList(true);
}

void PluginManager::ListPlugins(edict_t* plr) {
Expand Down Expand Up @@ -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);
Expand Down
12 changes: 11 additions & 1 deletion dlls/PluginManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -57,6 +65,8 @@ class PluginManager {

Plugin* FindPlugin(int id);

Plugin* FindPlugin(const char* name);

template<typename Func, typename... Args>
HOOK_RETURN_DATA CallHooks(Func hookFunction, Args&&... args) {
HOOK_RETURN_DATA totalRet = {0, 0};
Expand Down
25 changes: 25 additions & 0 deletions dlls/game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 };

Expand Down Expand Up @@ -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() {
}

Expand All @@ -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" );
Expand Down Expand Up @@ -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);

Expand Down
2 changes: 2 additions & 0 deletions dlls/game.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 3564cd0

Please sign in to comment.