mirror of
https://github.com/rehlds/metamod-r.git
synced 2025-03-30 14:19:00 +03:00
1263 lines
40 KiB
C++
1263 lines
40 KiB
C++
#include "precompiled.h"
|
|
|
|
const char *MPlugin::s_rPrintLoadTime[][4] = {
|
|
// SL_SIMPLE // SL_SHOW // SL_ALLOWED // SL_NOW
|
|
{ "never", "Never", "never", "never" }, // PT_NEVER
|
|
{ "startup", "Start", "at server startup", "during server startup" }, // PT_STARTUP
|
|
{ "changelevel", "Chlvl", "at changelevel", "during changelevel" }, // PT_CHANGELEVEL
|
|
{ "anytime", "ANY", "at any time", "during map" }, // PT_ANYTIME
|
|
{ "pausable", "Pause", "at any time, and pausable", "for requested pause" }, // PT_ANYPAUSE
|
|
};
|
|
|
|
// ReSharper disable once CppPossiblyUninitializedMember
|
|
MPlugin::MPlugin()
|
|
{
|
|
}
|
|
|
|
// ReSharper disable once CppPossiblyUninitializedMember
|
|
MPlugin::MPlugin(int index) : m_index(index)
|
|
{
|
|
}
|
|
|
|
// Parse a line from plugins.ini into a plugin.
|
|
bool MPlugin::ini_parseline(char *line)
|
|
{
|
|
char buf[1024];
|
|
strncpy(buf, line, sizeof buf - 1);
|
|
buf[sizeof buf - 1] = '\0';
|
|
|
|
trimbuf(buf);
|
|
|
|
// skip empty lines
|
|
if (buf[0] == '\0') {
|
|
return false;
|
|
}
|
|
|
|
// skip comments
|
|
if (buf[0] == '#' || buf[0] == ';' || !Q_strncmp(buf, "//", 2)) {
|
|
return false;
|
|
}
|
|
|
|
// grab platform ("win32" or "linux")
|
|
char* ptr_token;
|
|
char* token = strtok_r(buf, " \t", &ptr_token);
|
|
if (!token) {
|
|
return false;
|
|
}
|
|
|
|
if (!strcmp(token, "linux"))
|
|
m_platform = SP_LINUX;
|
|
else
|
|
m_platform = SP_WINDOWS;
|
|
|
|
// grab filename
|
|
token = strtok_r(nullptr, " \t\r\n", &ptr_token);
|
|
if (!token) {
|
|
return false;
|
|
}
|
|
|
|
Q_strncpy(m_filename, token, sizeof m_filename - 1);
|
|
m_filename[sizeof m_filename - 1] = '\0';
|
|
normalize_path(m_filename);
|
|
|
|
// Store name of just the actual _file_, without dir components.
|
|
char* cp = Q_strrchr(m_filename, '/');
|
|
if (cp)
|
|
m_file = cp + 1;
|
|
else
|
|
m_file = m_filename;
|
|
|
|
// Grab description.
|
|
// Just get the the rest of the line, minus line-termination.
|
|
token = strtok_r(nullptr, "\n\r", &ptr_token);
|
|
if (token) {
|
|
token = token + strspn(token, " \t"); // skip whitespace
|
|
Q_strncpy(m_desc, token, sizeof m_desc - 1);
|
|
m_desc[sizeof m_desc - 1] = '\0';
|
|
}
|
|
else {
|
|
// If no description is specified, temporarily use plugin file,
|
|
// until plugin can be queried, and desc replaced with info->name.
|
|
Q_snprintf(m_desc, sizeof m_desc, "<%s>", m_file);
|
|
}
|
|
|
|
// Make full pathname (from gamedir if relative, remove "..",
|
|
// backslashes, etc).
|
|
full_gamedir_path(m_filename, m_pathname);
|
|
|
|
m_source = PS_INI;
|
|
m_status = PL_VALID;
|
|
return true;
|
|
}
|
|
|
|
// Parse a line from console "load" command into a plugin.
|
|
bool MPlugin::cmd_parseline(const char *line)
|
|
{
|
|
char buf[NAME_MAX + PATH_MAX + MAX_DESC_LEN];
|
|
char *ptr_token;
|
|
|
|
Q_strncpy(buf, line, sizeof buf - 1);
|
|
buf[sizeof buf - 1] = '\0';
|
|
|
|
// remove "load"
|
|
char* token = strtok_r(buf, " \t", &ptr_token);
|
|
if (!token)
|
|
return false;
|
|
|
|
// grab filename
|
|
token = strtok_r(nullptr, " \t", &ptr_token);
|
|
if (!token)
|
|
return false;
|
|
|
|
Q_strncpy(m_filename, token, sizeof m_filename - 1);
|
|
m_filename[sizeof m_filename - 1] = '\0';
|
|
normalize_path(m_filename);
|
|
|
|
// store name of just the actual _file_, without dir components
|
|
char* cp = Q_strrchr(m_filename, '/');
|
|
if (cp)
|
|
m_file = cp + 1;
|
|
else
|
|
m_file = m_filename;
|
|
|
|
// Grab description.
|
|
// Specify no delimiter chars, as we just want the rest of the line.
|
|
token = strtok_r(nullptr, "", &ptr_token);
|
|
if (token) {
|
|
token = token + strspn(token, " \t"); // skip whitespace
|
|
Q_strncpy(m_desc, token, sizeof m_desc - 1);
|
|
m_desc[sizeof m_desc - 1] = '\0';
|
|
}
|
|
else {
|
|
// if no description is specified, temporarily use plugin file,
|
|
// until plugin can be queried, and desc replaced with info->name.
|
|
Q_snprintf(m_desc, sizeof m_desc, "<%s>", m_file);
|
|
}
|
|
|
|
// Make full pathname (from gamedir if relative, remove "..",
|
|
// backslashes, etc).
|
|
full_gamedir_path(m_filename, m_pathname);
|
|
|
|
m_source = PS_CMD;
|
|
m_status = PL_VALID;
|
|
return true;
|
|
}
|
|
|
|
// Parse a filename string from PEXT_LOAD_PLUGIN_BY_ *function into a plugin.
|
|
bool MPlugin::plugin_parseline(const char *fname, int loader_index)
|
|
{
|
|
m_source_plugin_index = loader_index;
|
|
|
|
Q_strncpy(m_filename, fname, sizeof m_filename - 1);
|
|
m_filename[sizeof m_filename - 1] = '\0';
|
|
normalize_path(m_filename);
|
|
|
|
//store just name of the actual _file, without path
|
|
char* cp = Q_strrchr(m_filename, '/');
|
|
if (cp)
|
|
m_file = cp + 1;
|
|
else
|
|
m_file = m_filename;
|
|
|
|
//grab description
|
|
//temporarily use plugin file until plugin can be queried
|
|
Q_snprintf(m_desc, sizeof m_desc, "<%s>", m_file);
|
|
|
|
//make full pathname
|
|
full_gamedir_path(m_filename, m_pathname);
|
|
|
|
m_source = PS_PLUGIN;
|
|
m_status = PL_VALID;
|
|
|
|
return true;
|
|
}
|
|
|
|
// Make sure this plugin has the necessary minimal information.
|
|
bool MPlugin::check_input()
|
|
{
|
|
// doublecheck our input/state
|
|
if (m_status < PL_VALID) {
|
|
META_ERROR("dll: Tried to operate on plugin[%d] with a non-valid status (%d)", m_index, str_status());
|
|
return false;
|
|
}
|
|
if (!m_file || !m_file[0]) {
|
|
META_ERROR("dll: Tried to operate on plugin[%d] with an empty file", m_index);
|
|
return false;
|
|
}
|
|
if (!m_filename[0]) {
|
|
META_ERROR("dll: Tried to operate on plugin[%d] with an empty filename", m_index);
|
|
return false;
|
|
}
|
|
if (!m_pathname[0]) {
|
|
META_ERROR("dll: Tried to operate on plugin[%d] with an empty pathname", m_index);
|
|
return false;
|
|
}
|
|
if (!m_desc[0]) {
|
|
// if no description is specified, temporarily use plugin file,
|
|
// until plugin can be queried, and desc replaced with info->name.
|
|
Q_snprintf(m_desc, sizeof m_desc, "<%s>", m_file);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Try to resolve a plugin's filename as a (possibly partial) path to an
|
|
// actual filename on disk, to facilitate easier console load-command
|
|
// arguments. Uses resolve_dirs and resolve_suffix below.
|
|
// Example paths that it tries:
|
|
// filename
|
|
// Gamedir/filename.dll, Gamedir/filename.so
|
|
// Gamedir/filename_i386.so
|
|
// Gamedir/dlls/filename_mm_i386.so
|
|
// Gamedir/dlls/filename_MM_i386.so
|
|
bool MPlugin::resolve()
|
|
{
|
|
if (!check_input()) {
|
|
return false;
|
|
}
|
|
|
|
char* found, tempbuf[MAX_PATH];
|
|
if (is_abs_path(m_filename))
|
|
found = resolve_suffix(m_filename, tempbuf, sizeof tempbuf);
|
|
else
|
|
found = resolve_dirs(m_filename, tempbuf, sizeof tempbuf);
|
|
|
|
if (!found) {
|
|
META_DEBUG(2, "Couldn't resolve '%s' to file", m_filename);
|
|
return false;
|
|
}
|
|
|
|
META_DEBUG(2, "Resolved '%s' to file '%s'", m_filename, found);
|
|
|
|
// store pathname: the resolved path (should be absolute)
|
|
Q_strncpy(m_pathname, found, sizeof m_pathname - 1);
|
|
m_pathname[sizeof m_pathname - 1] = '\0';
|
|
|
|
// store file: the name of the file (without dir)
|
|
char* cp = Q_strrchr(m_pathname, '/');
|
|
if (cp)
|
|
m_file = cp + 1;
|
|
else
|
|
m_file = m_pathname;
|
|
|
|
const char* filename = m_pathname;
|
|
|
|
// store pathname: the gamedir relative path, or an absolute path
|
|
size_t len = Q_strlen(g_GameDLL.gamedir);
|
|
if (!Q_strnicmp(m_pathname, g_GameDLL.gamedir, len))
|
|
filename += len + 1;
|
|
|
|
Q_strncpy(m_filename, filename, sizeof m_filename - 1);
|
|
m_filename[sizeof m_filename - 1] = '\0';
|
|
return true;
|
|
}
|
|
|
|
// For the given path, tries to find file in several possible
|
|
// directories.
|
|
// Try:
|
|
// GAMEDIR/filename
|
|
// GAMEDIR/dlls/filename
|
|
char *MPlugin::resolve_dirs(const char *path, char *tempbuf, size_t maxlen) const
|
|
{
|
|
// try in gamedir
|
|
Q_snprintf(tempbuf, maxlen, "%s/%s", g_GameDLL.gamedir, path);
|
|
if (is_valid_path(tempbuf))
|
|
return tempbuf;
|
|
|
|
// try other file prefixes in gamedir
|
|
char buf[MAX_PATH];
|
|
strncpy(buf, tempbuf, sizeof buf - 1);
|
|
buf[sizeof buf - 1] = '\0';
|
|
|
|
char* found = resolve_suffix(buf, tempbuf, maxlen);
|
|
if (found)
|
|
return found;
|
|
|
|
// try in gamedir/dlls/
|
|
Q_snprintf(tempbuf, maxlen, "%s/dlls/%s", g_GameDLL.gamedir, path);
|
|
if (is_valid_path(tempbuf))
|
|
return tempbuf;
|
|
|
|
strncpy(buf, tempbuf, sizeof buf - 1);
|
|
buf[sizeof buf - 1] = '\0';
|
|
|
|
// try other file prefixes for this path
|
|
return resolve_suffix(buf, tempbuf, maxlen);
|
|
}
|
|
|
|
// For the given path, tries several different filename suffixes.
|
|
// Try:
|
|
// path
|
|
// path_mm
|
|
// path_MM
|
|
// path.so (linux), path.dll (win32)
|
|
// path_i386.so, path_i486.so, etc (if linux)
|
|
char* MPlugin::resolve_suffix(const char *path, char *tempbuf, size_t bufsize) const
|
|
{
|
|
if (Q_strstr(path, PLATFORM_DLEXT) && is_valid_path(path)) {
|
|
strncpy(tempbuf, path, bufsize - 1);
|
|
tempbuf[bufsize - 1] = '\0';
|
|
return tempbuf;
|
|
}
|
|
|
|
// try with ext
|
|
Q_snprintf(tempbuf, bufsize, "%s%s", path, PLATFORM_DLEXT);
|
|
if (is_valid_path(tempbuf))
|
|
return tempbuf;
|
|
|
|
// try add suffix
|
|
if (!Q_strstr(path, "_mm")) {
|
|
char buf[MAX_PATH];
|
|
Q_snprintf(buf, sizeof buf, "%s_mm", path);
|
|
|
|
char* found = resolve_suffix(buf, tempbuf, bufsize);
|
|
if (found)
|
|
return found;
|
|
}
|
|
|
|
#ifndef _WIN32
|
|
for (size_t i = 0; i < arraysize(g_platform_postfixes); i++) {
|
|
Q_snprintf(tempbuf, bufsize, "%s%s", path, g_platform_postfixes[i]);
|
|
if (is_valid_path(tempbuf))
|
|
return tempbuf;
|
|
}
|
|
#endif
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// Check if a given plugin is the same but possibly for a
|
|
// different platform. A match is considered to be found if
|
|
// 1. the filename without the path is the same, or
|
|
// 2a. for an attached plugin the logtag is the same, or
|
|
// 2b. the description is the same, or
|
|
// 3. a significant part of the filename is the same.
|
|
// A significant part of a plugin name is currently defined to:
|
|
// the part up to a known platform postfix as determined by
|
|
// the is_platform_postfix() function (see above), or
|
|
// the part up to the last dot, if one exists.
|
|
bool MPlugin::platform_match(MPlugin *other) const
|
|
{
|
|
if (m_status == PL_EMPTY || other->status() == PL_EMPTY)
|
|
return false;
|
|
|
|
if (!Q_strcmp(m_file, other->file()))
|
|
return true;
|
|
|
|
if (m_status >= PL_OPENED && other->status() >= PL_OPENED && !Q_strcmp(m_info->logtag, other->info()->logtag))
|
|
return true;
|
|
|
|
if (m_desc[0] != '\0' && other->description()[0] != '0' && !Q_stricmp(m_desc, other->description()))
|
|
return true;
|
|
|
|
const char* end = Q_strrchr(m_file, '_');
|
|
if (!end || !is_platform_postfix(end))
|
|
end = Q_strrchr(m_file, '.');
|
|
|
|
const char* other_end = Q_strrchr(other->file(), '_');
|
|
if (!other_end || !is_platform_postfix(other_end))
|
|
other_end = Q_strrchr(other->file(), '.');
|
|
|
|
if (!end || !other_end)
|
|
return false;
|
|
|
|
int prefixlen = end - m_file;
|
|
if (other_end - other->file() != prefixlen)
|
|
return false;
|
|
|
|
if (!Q_strncmp(m_file, other->file(), prefixlen))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
// Load a plugin; query, check allowed time, attach.
|
|
bool MPlugin::load(PLUG_LOADTIME now)
|
|
{
|
|
if (!check_input()) {
|
|
return false;
|
|
}
|
|
if (m_status >= PL_RUNNING) {
|
|
META_ERROR("dll: Not loading plugin '%s'; already loaded (status=%s)", m_desc, str_status());
|
|
return false;
|
|
}
|
|
if (m_action != PA_LOAD && m_action != PA_ATTACH) {
|
|
META_ERROR("dll: Not loading plugin '%s'; not marked for load (action=%s)", m_desc, str_action());
|
|
return false;
|
|
}
|
|
|
|
if (m_status < PL_OPENED) {
|
|
// query plugin; open file and get info about it
|
|
if (!query()) {
|
|
META_ERROR("dll: Skipping plugin '%s'; couldn't query", m_desc);
|
|
if (!m_sys_module.unload()) {
|
|
META_ERROR("dll: Couldn't close plugin file '%s': %s", m_file, "invalid handle");
|
|
}
|
|
m_status = PL_BADFILE;
|
|
m_info = nullptr; //prevent crash
|
|
return false;
|
|
}
|
|
m_status = PL_OPENED;
|
|
}
|
|
|
|
// are we allowed to attach this plugin at this time?
|
|
if (m_info->loadable < now) {
|
|
if (m_info->loadable > PT_STARTUP) {
|
|
// will try to attach again at next opportunity
|
|
META_DEBUG(2, "dll: Delaying load plugin '%s'; can't attach now: allowed=%s; now=%s", m_desc, str_loadable(), str_loadtime(now, SL_SIMPLE));
|
|
return false;
|
|
}
|
|
|
|
META_DEBUG(2, "dll: Failed load plugin '%s'; can't attach now: allowed=%s; now=%s", m_desc, str_loadable(), str_loadtime(now, SL_SIMPLE));
|
|
// don't try to attach again later
|
|
m_action = PA_NONE;
|
|
return false;
|
|
}
|
|
|
|
// attach plugin; get function tables
|
|
if (!attach(now)) {
|
|
META_ERROR("dll: Failed to attach plugin '%s'", m_desc);
|
|
// Note we don't dlclose() here, since we're returning PL_FAILED,
|
|
// which implies that it's been dlopened and queried successfully.
|
|
// Doing so causes crashes, because things like "meta list" try to
|
|
// look at *info, which is in the DLL memory space and unaccessible
|
|
// (segfault) after dlclosed.
|
|
m_status = PL_FAILED;
|
|
return false;
|
|
}
|
|
|
|
m_status = PL_RUNNING;
|
|
m_action = PA_NONE;
|
|
|
|
// If not loading at server startup, then need to call plugin's
|
|
// GameInit, since we've passed that.
|
|
if (now != PT_STARTUP && m_dllapi_table && m_dllapi_table->pfnGameInit) {
|
|
m_dllapi_table->pfnGameInit();
|
|
}
|
|
// If loading during map, then I'd like to call plugin's
|
|
// ServerActivate, since we've passed that. However, I'm not sure what
|
|
// arguments to give it... So, we'll just have to say for the
|
|
// moment that plugins that are loadable during a map can't need to
|
|
// hook ServerActivate.
|
|
|
|
META_LOG("dll: Loaded plugin '%s': %s v%s %s, %s", m_desc, m_info->name, m_info->version, m_info->date, m_info->author);
|
|
return true;
|
|
}
|
|
|
|
// Unload a plugin. Check time, detach.
|
|
bool MPlugin::unload(PLUG_LOADTIME now, PL_UNLOAD_REASON reason)
|
|
{
|
|
if (!check_input()) {
|
|
return false;
|
|
}
|
|
if (m_status < PL_RUNNING) {
|
|
if (reason != PNL_CMD_FORCED && reason != PNL_RELOAD) {
|
|
META_ERROR("dll: Not unloading plugin '%s'; already unloaded (status=%s)", m_desc, str_status());
|
|
return false;
|
|
}
|
|
}
|
|
if (m_action != PA_UNLOAD && m_action != PA_RELOAD) {
|
|
META_WARNING("dll: Not unloading plugin '%s'; not marked for unload (action=%s)", m_desc, str_action());
|
|
return false;
|
|
}
|
|
|
|
// Are we allowed to detach this plugin at this time?
|
|
// If forcing unload, we disregard when plugin wants to be unloaded.
|
|
if (m_info && m_info->unloadable < now) {
|
|
if (reason == PNL_CMD_FORCED) {
|
|
META_DEBUG(2, "dll: Forced unload plugin '%s' overriding allowed times: allowed=%s; now=%s", m_desc, str_unloadable(), str_loadtime(now, SL_SIMPLE));
|
|
}
|
|
else {
|
|
if (m_info->unloadable > PT_STARTUP) {
|
|
META_DEBUG(2, "dll: Delaying unload plugin '%s'; can't detach now: allowed=%s; now=%s", m_desc, str_unloadable(), str_loadtime(now, SL_SIMPLE));
|
|
// caller should give message to user
|
|
// try to unload again at next opportunity
|
|
return false;
|
|
}
|
|
|
|
META_DEBUG(2, "dll: Failed unload plugin '%s'; can't detach now: allowed=%s; now=%s", m_desc, str_unloadable(), str_loadtime(now, SL_SIMPLE));
|
|
// don't try to unload again later
|
|
m_action = PA_NONE;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// If unloading during map, then I'd like to call plugin's
|
|
// ServerDeactivate. However, I don't want to do this until I start
|
|
// calling ServerActivate when loading during map, since the SDK
|
|
// indicates these two routines should match call for call.
|
|
|
|
// detach plugin
|
|
if (!detach(now, reason)) {
|
|
if (reason == PNL_RELOAD) {
|
|
META_DEBUG(2, "dll: Reload plugin '%s' overriding failed detach", m_desc);
|
|
}
|
|
else if (reason == PNL_CMD_FORCED) {
|
|
META_DEBUG(2, "dll: Forced unload plugin '%s' overriding failed detach");
|
|
}
|
|
else {
|
|
META_WARNING("dll: Failed to detach plugin '%s'; ", m_desc);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
g_plugins->clear_source_plugin_index(m_index);
|
|
|
|
// successful detach, or forced unload
|
|
|
|
// Unmark registered commands for this plugin (by index number).
|
|
g_regCmds->remove(m_index);
|
|
// Unmark registered cvars for this plugin (by index number).
|
|
g_regCvars->disable(m_index);
|
|
|
|
// Close the file. Note: after this, attempts to reference any memory
|
|
// locations in the file will produce a segfault.
|
|
if (!m_sys_module.unload()) {
|
|
// If DLL cannot be closed, OS is badly broken or we are giving invalid handle.
|
|
// So we don't return here but instead remove plugin from our listings.
|
|
META_WARNING("dll: Couldn't close plugin file '%s': %s", m_file, "invalid handle");
|
|
}
|
|
|
|
if (m_action == PA_UNLOAD) {
|
|
m_status = PL_EMPTY;
|
|
}
|
|
else if (m_action == PA_RELOAD) {
|
|
m_status = PL_VALID;
|
|
m_action = PA_LOAD;
|
|
}
|
|
|
|
clear();
|
|
|
|
META_LOG("dll: Unloaded plugin '%s' for reason '%s'", m_desc, str_reason(reason));
|
|
meta_rebuild_callbacks();
|
|
return true;
|
|
}
|
|
|
|
// Reload a plugin; unload and load again.
|
|
bool MPlugin::reload(PLUG_LOADTIME now, PL_UNLOAD_REASON reason)
|
|
{
|
|
if (!check_input()) {
|
|
return false;
|
|
}
|
|
// Are we allowed to load this plugin at this time?
|
|
// If we cannot load the plugin after unloading it, we keep it.
|
|
if (m_info && m_info->loadable < now) {
|
|
if (m_info->loadable > PT_STARTUP) {
|
|
META_DEBUG(2, "dll: Delaying reload plugin '%s'; would not be able to reattach now: allowed=%s; now=%s", m_desc, str_loadable(), str_loadtime(now, SL_SIMPLE));
|
|
// caller should give message to user
|
|
// try to reload again at next opportunity
|
|
return false;
|
|
}
|
|
|
|
META_DEBUG(2, "dll: Failed reload plugin '%s'; would not be able to reattach now: allowed=%s; now=%s", m_desc, str_loadable(), str_loadtime(now, SL_SIMPLE));
|
|
// don't try to reload again later
|
|
m_action = PA_NONE;
|
|
return false;
|
|
}
|
|
|
|
if (m_status < PL_RUNNING) {
|
|
META_WARNING("dll: Plugin '%s' isn't running; Forcing unload plugin for reloading", m_desc);
|
|
reason = PNL_RELOAD;
|
|
}
|
|
|
|
if (!unload(now, reason)) {
|
|
META_WARNING("dll: Failed to unload plugin '%s' for reloading", m_desc);
|
|
return false;
|
|
}
|
|
|
|
if (!load(now)) {
|
|
META_WARNING("dll: Failed to reload plugin '%s' after unloading", m_desc);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Pause a plugin; temporarily disabled for API routines.
|
|
bool MPlugin::pause()
|
|
{
|
|
if (m_status == PL_PAUSED) {
|
|
META_ERROR("Not pausing plugin '%s'; already paused", m_desc);
|
|
return false;
|
|
}
|
|
if (m_status != PL_RUNNING) {
|
|
META_ERROR("Cannot pause plugin '%s'; not currently running (status=%s)", m_desc, str_status());
|
|
return false;
|
|
}
|
|
|
|
// are we allowed to pause this plugin?
|
|
if (m_info->unloadable < PT_ANYPAUSE) {
|
|
META_ERROR("Cannot pause plugin '%s'; not allowed by plugin (allowed=%s)", m_desc, str_unloadable());
|
|
m_action = PA_NONE;
|
|
return false;
|
|
}
|
|
|
|
m_status = PL_PAUSED;
|
|
META_LOG("Paused plugin '%s'", m_desc);
|
|
return true;
|
|
}
|
|
|
|
// Unpause a plugin.
|
|
bool MPlugin::unpause()
|
|
{
|
|
if (m_status != PL_PAUSED) {
|
|
META_ERROR("Cannot unpause plugin '%s'; not currently paused (status=%s)", m_desc, str_status());
|
|
return false;
|
|
}
|
|
|
|
m_status = PL_RUNNING;
|
|
META_LOG("Unpaused plugin '%s'", m_desc);
|
|
return true;
|
|
}
|
|
|
|
// Retry pending action, presumably from a previous failure.
|
|
bool MPlugin::retry(PLUG_LOADTIME now, PL_UNLOAD_REASON reason)
|
|
{
|
|
if (m_action == PA_LOAD || m_action == PA_ATTACH)
|
|
return load(now);
|
|
if (m_action == PA_UNLOAD)
|
|
return unload(now, reason);
|
|
if (m_action == PA_RELOAD)
|
|
return reload(now, reason);
|
|
|
|
META_ERROR("No pending action to retry for plugin '%s'; (status=%s, action=%s)", m_desc, str_status(), str_action());
|
|
return false;
|
|
}
|
|
|
|
template<typename T>
|
|
void free_table(T& table)
|
|
{
|
|
if (table) {
|
|
Q_free(table);
|
|
table = nullptr;
|
|
}
|
|
}
|
|
|
|
// Clear a plugin (it failed a previous action and should be
|
|
// removed from the list, or it's being unloaded).
|
|
bool MPlugin::clear()
|
|
{
|
|
if (m_status != PL_FAILED && m_status != PL_BADFILE && m_status != PL_EMPTY && m_status != PL_OPENED) {
|
|
META_ERROR("Cannot clear plugin '%s'; not marked as failed, empty, or open (status=%s)", m_desc, str_status());
|
|
return false;
|
|
}
|
|
// If file is open, close the file. Note: after this, attempts to
|
|
// reference any memory locations in the file will produce a segfault.
|
|
if (!m_sys_module.unload()) {
|
|
META_ERROR("dll: Couldn't close plugin file '%s': %s", m_file, "invalid handle");
|
|
m_status = PL_FAILED;
|
|
return false;
|
|
}
|
|
|
|
free_table(m_gamedll_funcs.dllapi_table);
|
|
free_table(m_gamedll_funcs.newapi_table);
|
|
free_table(m_dllapi_table);
|
|
free_table(m_dllapi_post_table);
|
|
free_table(m_newapi_table);
|
|
free_table(m_newapi_post_table);
|
|
free_table(m_engine_table);
|
|
free_table(m_engine_post_table);
|
|
|
|
m_status = PL_EMPTY;
|
|
m_action = PA_NULL;
|
|
m_info = nullptr;
|
|
m_time_loaded = 0;
|
|
m_dllapi_table = nullptr;
|
|
m_dllapi_post_table = nullptr;
|
|
m_newapi_table = nullptr;
|
|
m_newapi_post_table = nullptr;
|
|
m_engine_table = nullptr;
|
|
m_engine_post_table = nullptr;
|
|
m_gamedll_funcs.dllapi_table = nullptr;
|
|
m_gamedll_funcs.newapi_table = nullptr;
|
|
m_source_plugin_index = 0;
|
|
m_unloader_index = 0;
|
|
m_is_unloader = false;
|
|
|
|
return true;
|
|
}
|
|
|
|
// Unload a plugin from plugin request
|
|
bool MPlugin::plugin_unload(plid_t plid, PLUG_LOADTIME now, PL_UNLOAD_REASON reason)
|
|
{
|
|
MPlugin *pl_unloader = g_plugins->find(plid);
|
|
|
|
if (!pl_unloader) {
|
|
META_WARNING("dll: Not unloading plugin '%s'; plugin that requested unload is not found.", m_desc);
|
|
return false;
|
|
}
|
|
if (pl_unloader->index() == m_index) {
|
|
META_WARNING("dll: Not unloading plugin '%s'; Plugin tried to unload itself.", m_desc);
|
|
return false;
|
|
}
|
|
if (m_is_unloader) {
|
|
META_WARNING("dll: Not unloading plugin '%s'; Plugin is unloading plugin that tried to unload it.", m_desc);
|
|
return false;
|
|
}
|
|
|
|
m_unloader_index = pl_unloader->index();
|
|
|
|
//block unloader from being unloaded by other plugin
|
|
pl_unloader->m_is_unloader = true;
|
|
|
|
//try unload
|
|
PLUG_ACTION old_action = m_action;
|
|
m_action = PA_UNLOAD;
|
|
|
|
if (unload(now, reason)) {
|
|
META_DEBUG(1, "Unloaded plugin '%s'", m_desc);
|
|
pl_unloader->m_is_unloader = false;
|
|
return true;
|
|
}
|
|
|
|
pl_unloader->m_is_unloader = false;
|
|
m_action = old_action;
|
|
|
|
return false;
|
|
}
|
|
|
|
// Query a plugin:
|
|
// - dlopen() the file, store the handle
|
|
// - dlsym() and call:
|
|
// Meta_Init (if present) - tell dll it'll be used as a metamod plugin
|
|
// GiveFnptrsToDll - give engine function ptrs
|
|
// Meta_Query - say "hi" and get info about plugin
|
|
bool MPlugin::query()
|
|
{
|
|
// open the plugin DLL
|
|
if (!m_sys_module.load(m_pathname)) {
|
|
META_ERROR("dll: Failed query plugin '%s'; Couldn't open file '%s': %s", m_desc, m_pathname, m_sys_module.getloaderror());
|
|
return false;
|
|
}
|
|
|
|
// First, we check to see if they have a Meta_Query. We would normally
|
|
// dlsym this just prior to calling it, after having called
|
|
// GiveFnptrsToDll, but things like standalone-DLL bots do all their
|
|
// startup in GiveFnptrsToDll, and trying to load them as metamod
|
|
// plugins creates all sorts of crashes. So, we do a trivial check
|
|
// first to see if the DLL looks like a metamod plugin before
|
|
// proceeding with the normal order. Note that we still have to call
|
|
// GiveFnptrsToDll before Meta_Query, because the latter typically uses
|
|
// engine functions like AlertMessage, which have to be passed along via
|
|
// GiveFnptrsToDll.
|
|
auto pfn_query = (META_QUERY_FN)m_sys_module.getsym("Meta_Query");
|
|
if (!pfn_query) {
|
|
META_ERROR("dll: Failed query plugin '%s'; Couldn't find Meta_Query(): %s", m_desc, "function not found");
|
|
// caller will dlclose()
|
|
return false;
|
|
}
|
|
|
|
// Call Meta_Init, if present. This is an optional plugin routine to
|
|
// allow plugin to do any special initializing or other processing
|
|
// prior to the standard query/attach procedure.
|
|
//
|
|
// In particular, this should allow for DLL's that can operate as both
|
|
// a standalone dll AND a metamod plugin. This routine has to be
|
|
// called _before_ GiveFnptrsToDll, since the dll will usually do all
|
|
// its startup processing in GiveFn, and has to know at that point
|
|
// whether it needs to do just mm plugin startup, or all the extra
|
|
// startup needed for a standalone DLL.
|
|
//
|
|
// This passes nothing and returns nothing, and the routine in the
|
|
// plugin can NOT use any g_engine functions, as they haven't been
|
|
// provided yet (done next, in GiveFnptrsToDll).
|
|
auto pfn_init = (META_INIT_FN)m_sys_module.getsym("Meta_Init");
|
|
if (pfn_init) {
|
|
pfn_init();
|
|
META_DEBUG(6, "dll: Plugin '%s': Called Meta_Init()", m_desc);
|
|
}
|
|
else {
|
|
META_DEBUG(5, "dll: no Meta_Init present in plugin '%s'", m_desc);
|
|
// don't return; not an error
|
|
}
|
|
|
|
// pass on engine function table and globals to plugin
|
|
auto pfn_give_engfuncs = (GIVE_ENGINE_FUNCTIONS_FN)m_sys_module.getsym("GiveFnptrsToDll");
|
|
if (!pfn_give_engfuncs) {
|
|
META_ERROR("dll: Failed query plugin '%s'; Couldn't find GiveFnptrsToDll(): %s", m_desc, "function not found");
|
|
return false;
|
|
}
|
|
|
|
pfn_give_engfuncs(g_engine.pl_funcs, g_engine.globals);
|
|
META_DEBUG(6, "dll: Plugin '%s': Called GiveFnptrsToDll()", m_desc);
|
|
|
|
// Call plugin's Meta_Query(), to pass our meta interface version, and get
|
|
// plugin's info structure.
|
|
m_info = nullptr;
|
|
|
|
// Make a copy of the meta_util function table for each plugin, for the
|
|
// same reason.
|
|
Q_memcpy(&m_mutil_funcs, &g_MetaUtilFunctions, sizeof m_mutil_funcs);
|
|
|
|
if (pfn_query(META_INTERFACE_VERSION, &m_info, &m_mutil_funcs) != TRUE) {
|
|
META_ERROR("dll: Failed query plugin '%s'; Meta_Query returned error", m_desc);
|
|
}
|
|
else {
|
|
META_DEBUG(6, "dll: Plugin '%s': Called Meta_Query() successfully", m_desc);
|
|
}
|
|
|
|
// Check for interface differences... Generally, a difference in major
|
|
// version will be incompatible, and a plugin that expects a later
|
|
// minor version will be incompatible (it's expecting things that this
|
|
// Metamod won't be supplying). g_plugins that use an older minor
|
|
// version will still work, as backward-compability within major
|
|
// version is expected (forward-compatibility is not).
|
|
//
|
|
// Note, this check is done regardless of whether meta_query returns an
|
|
// error.
|
|
if (!m_info) {
|
|
META_ERROR("dll: Failed query plugin '%s'; Empty info structure", m_desc);
|
|
// caller will dlclose()
|
|
return false;
|
|
}
|
|
|
|
if (Q_strcmp(m_info->ifvers, META_INTERFACE_VERSION)) {
|
|
int mmajor, mminor, pmajor, pminor;
|
|
META_DEBUG(3, "dll: Note: Plugin '%s' interface version didn't match; expected %s, found %s", m_desc, META_INTERFACE_VERSION, m_info->ifvers);
|
|
sscanf(META_INTERFACE_VERSION, "%i:%i", &mmajor, &mminor);
|
|
sscanf(m_info->ifvers, "%i:%i", &pmajor, &pminor);
|
|
// If plugin has later interface version, it's incompatible
|
|
// (update metamod).
|
|
if (pmajor > mmajor || (pmajor == mmajor && pminor > mminor)) {
|
|
META_ERROR("dll: Plugin '%s' requires a newer version of Metamod (Metamod needs at least interface %s not the current %s)", m_desc, m_info->ifvers, META_INTERFACE_VERSION);
|
|
return false;
|
|
}
|
|
// If plugin has older major interface version, it's incompatible
|
|
// (update plugin).
|
|
if (pmajor < mmajor) {
|
|
META_ERROR("dll: Plugin '%s' is out of date and incompatible with this version of Metamod; please find a newer version of the plugin (plugin needs at least interface %s not the current %s)", m_desc, META_INTERFACE_VERSION, m_info->ifvers);
|
|
return false;
|
|
}
|
|
// Plugin has identical major, with older minor. This is supposed to be
|
|
// backwards compatible, so we warn, but we still accept it.
|
|
if (pmajor == mmajor && pminor < mminor)
|
|
META_LOG("dll: Note: plugin '%s' is using an older interface version (%s), not the latest interface version (%s); there might be an updated version of the plugin", m_desc, m_info->ifvers, META_INTERFACE_VERSION);
|
|
else
|
|
META_LOG("dll: Plugin '%s': unexpected version comparision; metavers=%s, mmajor=%d, mminor=%d; plugvers=%s, pmajor=%d, pminor=%d", m_desc, META_INTERFACE_VERSION, mmajor, mminor, m_info->ifvers, pmajor, pminor);
|
|
|
|
if (pmajor != mmajor) {
|
|
META_ERROR("dll: Rejected plugin '%s' due to interface version incompatibility (mm=%s, pl=%s)", m_desc, META_INTERFACE_VERSION, m_info->ifvers);
|
|
// caller will dlclose()
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Replace temporary desc with plugin's internal name.
|
|
if (m_desc[0] == '<') {
|
|
Q_strncpy(m_desc, m_info->name, sizeof m_desc - 1);
|
|
m_desc[sizeof m_desc - 1] = '\0';
|
|
}
|
|
|
|
META_DEBUG(6, "dll: Plugin '%s': Query successful", m_desc);
|
|
return true;
|
|
}
|
|
|
|
template<typename getter_t, typename table_t>
|
|
bool get_function_table_from_plugin(const char* pl_desc, int ifvers_mm, getter_t getter, const char* getter_name, table_t*& table, size_t table_size = sizeof(table_t))
|
|
{
|
|
int ifvers_pl = ifvers_mm;
|
|
|
|
if (getter) {
|
|
if (!table)
|
|
table = (table_t *)Q_calloc(1, table_size);
|
|
if (getter(table, &ifvers_pl)) {
|
|
META_DEBUG(3, "dll: Plugin '%s': Found %s", pl_desc, getter_name);
|
|
return true;
|
|
}
|
|
|
|
META_ERROR("dll: Failure calling %s in plugin '%s'", getter_name, pl_desc);
|
|
if (ifvers_pl != ifvers_mm)
|
|
META_ERROR("dll: Interface version didn't match; expected %d, found %d", ifvers_mm, ifvers_pl);
|
|
}
|
|
else {
|
|
META_DEBUG(5, "dll: Plugin '%s': No %s", pl_desc, getter_name);
|
|
if (table)
|
|
Q_free(table);
|
|
table = nullptr;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
template<typename getter_t, typename table_t>
|
|
bool get_function_table_from_plugin_old(const char* pl_desc, int ifvers_mm, getter_t getter, const char* getter_name, table_t*& table, size_t table_size = sizeof(table_t))
|
|
{
|
|
if (getter) {
|
|
if (!table)
|
|
table = (table_t *)Q_calloc(1, table_size);
|
|
if (getter(table, ifvers_mm)) {
|
|
META_DEBUG(3, "dll: Plugin '%s': Found %s", pl_desc, getter_name);
|
|
return true;
|
|
}
|
|
|
|
META_ERROR("dll: Failure calling %s in plugin '%s'", getter_name, pl_desc);
|
|
}
|
|
else {
|
|
META_DEBUG(5, "dll: Plugin '%s': No %s", pl_desc, getter_name);
|
|
if (table)
|
|
Q_free(table);
|
|
table = nullptr;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Attach a plugin:
|
|
// - dlsym() and call:
|
|
// Meta_Attach - get table of api tables, give meta_globals
|
|
// - if provided by plugin, call various "Get" function pointers,
|
|
// and store resulting function tables:
|
|
// GetEntityAPI (std)
|
|
// GetEntityAPI2 (std sdk2)
|
|
// GetNewDLLFunctions (std sdk2)
|
|
//
|
|
// GetEntityAPI_Post (meta)
|
|
// GetEntityAPI2_Post (meta)
|
|
// GetNewDLLFunctions_Post (meta)
|
|
//
|
|
// GetEngineFunctions (meta)
|
|
// GetEngineFunctions_Post (meta)
|
|
bool MPlugin::attach(PLUG_LOADTIME now)
|
|
{
|
|
// Make copy of gameDLL's function tables for each plugin, so we don't
|
|
// risk the plugins screwing with the tables everyone uses.
|
|
if (g_GameDLL.funcs.dllapi_table && !m_gamedll_funcs.dllapi_table) {
|
|
m_gamedll_funcs.dllapi_table = (DLL_FUNCTIONS *)Q_malloc(sizeof(DLL_FUNCTIONS));
|
|
if (!m_gamedll_funcs.dllapi_table) {
|
|
META_ERROR("dll: Failed attach plugin '%s': Failed malloc() for dllapi_table");
|
|
return false;
|
|
}
|
|
Q_memcpy(m_gamedll_funcs.dllapi_table, g_GameDLL.funcs.dllapi_table, sizeof(DLL_FUNCTIONS));
|
|
}
|
|
if (g_GameDLL.funcs.newapi_table && !m_gamedll_funcs.newapi_table) {
|
|
m_gamedll_funcs.newapi_table = (NEW_DLL_FUNCTIONS *)Q_malloc(sizeof(NEW_DLL_FUNCTIONS));
|
|
if (!m_gamedll_funcs.newapi_table) {
|
|
META_ERROR("dll: Failed attach plugin '%s': Failed malloc() for newapi_table");
|
|
return false;
|
|
}
|
|
Q_memcpy(m_gamedll_funcs.newapi_table, g_GameDLL.funcs.newapi_table, sizeof(NEW_DLL_FUNCTIONS));
|
|
}
|
|
auto pfn_attach = (META_ATTACH_FN)m_sys_module.getsym("Meta_Attach");
|
|
if (!pfn_attach) {
|
|
META_ERROR("dll: Failed attach plugin '%s': Couldn't find Meta_Attach(): %s", m_desc, "function not found");
|
|
return false;
|
|
}
|
|
|
|
META_FUNCTIONS meta_table = {};
|
|
|
|
// get table of function tables,
|
|
// give public meta globals
|
|
qboolean ret = pfn_attach(now, &meta_table, &g_metaGlobals, &m_gamedll_funcs);
|
|
if (ret != TRUE) {
|
|
META_ERROR("dll: Failed attach plugin '%s': Error from Meta_Attach(): %d", m_desc, ret);
|
|
return false;
|
|
}
|
|
|
|
META_DEBUG(6, "dll: Plugin '%s': Called Meta_Attach() successfully", m_desc);
|
|
|
|
// Look for API-NEW interface in plugin. We do this before API2/API, because
|
|
// that's what the engine appears to do..
|
|
// [But we only do this if the gamedll provides these, so that we don't
|
|
// give a plugin the idea that we'll call them, when in fact we won't
|
|
// (since we don't export them to the engine when the gamedll doesn't
|
|
// provide them).]
|
|
// We now do this even when the gamedll doesn't provide these because
|
|
// the plugins might want to use new functions like CvarValue() also
|
|
// with older gamedlls which do not use the API-NEW themselves.
|
|
// It is yet unknown if this causes any problems in the engine.
|
|
get_function_table_from_plugin(m_desc, NEW_DLL_FUNCTIONS_VERSION, meta_table.pfnGetNewDLLFunctions, "GetNewDLLFunctions", m_newapi_table);
|
|
get_function_table_from_plugin(m_desc, NEW_DLL_FUNCTIONS_VERSION, meta_table.pfnGetNewDLLFunctions_Post, "GetNewDLLFunctions_Post", m_newapi_post_table);
|
|
|
|
// Look for API2 interface in plugin; preferred over API-1.
|
|
get_function_table_from_plugin(m_desc, INTERFACE_VERSION, meta_table.pfnGetEntityAPI2, "GetEntityAPI2", m_dllapi_table);
|
|
get_function_table_from_plugin(m_desc, INTERFACE_VERSION, meta_table.pfnGetEntityAPI2_Post, "GetEntityAPI2_Post", m_dllapi_post_table);
|
|
|
|
// Look for old-style API in plugin, if API2 interface wasn't found.
|
|
if (!m_dllapi_table && !m_dllapi_post_table) {
|
|
get_function_table_from_plugin_old(m_desc, INTERFACE_VERSION, meta_table.pfnGetEntityAPI, "GetEntityAPI", m_dllapi_table);
|
|
get_function_table_from_plugin_old(m_desc, INTERFACE_VERSION, meta_table.pfnGetEntityAPI_Post, "GetEntityAPI_Post", m_dllapi_post_table);
|
|
}
|
|
|
|
get_function_table_from_plugin(m_desc, ENGINE_INTERFACE_VERSION, meta_table.pfnGetEngineFunctions, "GetEngineFunctions", m_engine_table);
|
|
get_function_table_from_plugin(m_desc, ENGINE_INTERFACE_VERSION, meta_table.pfnGetEngineFunctions_Post, "GetEngineFunctions_Post", m_engine_post_table);
|
|
|
|
m_time_loaded = time(nullptr);
|
|
return true;
|
|
}
|
|
|
|
// Inform plugin we're going to unload it.
|
|
bool MPlugin::detach(PLUG_LOADTIME now, PL_UNLOAD_REASON reason)
|
|
{
|
|
// If we have no handle, i.e. no dll loaded, we return true because the
|
|
// dll is obviously detached. We shouldn't call DLSYM() with a nullptr
|
|
// handle since this will DLSYM() ourself.
|
|
if (!m_sys_module.gethandle())
|
|
return true;
|
|
|
|
auto pfn_detach = (META_DETACH_FN)m_sys_module.getsym("Meta_Detach");
|
|
if (!pfn_detach) {
|
|
META_ERROR("dll: Error detach plugin '%s': Couldn't find Meta_Detach(): %s", m_desc, "function not found");
|
|
// caller will dlclose()
|
|
return false;
|
|
}
|
|
|
|
int ret = pfn_detach(now, reason);
|
|
if (ret != TRUE) {
|
|
META_ERROR("dll: Failed detach plugin '%s': Error from Meta_Detach(): %d", m_desc, ret);
|
|
return false;
|
|
}
|
|
|
|
META_DEBUG(6, "dll: Plugin '%s': Called Meta_Detach() successfully", m_desc);
|
|
return true;
|
|
}
|
|
|
|
template<typename table_t, typename info_t>
|
|
void show_table(const char* table_name, table_t* table, info_t* info_begin, bool post)
|
|
{
|
|
if (table) {
|
|
META_CONS("%s functions:", table_name);
|
|
|
|
size_t count = 0;
|
|
|
|
for (auto n = info_begin; n->name[0] != '\0'; n++) {
|
|
if (*(size_t *)(size_t(table) + n->offset)) {
|
|
META_CONS(" %s%s", Q_strstr(n->name, "::") + 2, post ? "_Post" : "");
|
|
count++;
|
|
}
|
|
}
|
|
|
|
META_CONS("%d functions (%s)", count, table_name);
|
|
}
|
|
else
|
|
META_CONS("No %s functions.", table_name);
|
|
}
|
|
|
|
// List information about plugin to console.
|
|
void MPlugin::show()
|
|
{
|
|
char *cp;
|
|
const int width = 13;
|
|
|
|
META_CONS("%*s: %s", width, "name", m_info ? m_info->name : "(nil)");
|
|
META_CONS("%*s: %s", width, "desc", m_desc);
|
|
META_CONS("%*s: %s", width, "status", str_status());
|
|
META_CONS("%*s: %s", width, "action", str_action());
|
|
META_CONS("%*s: %s", width, "filename", m_filename);
|
|
META_CONS("%*s: %s", width, "file", m_file);
|
|
META_CONS("%*s: %s", width, "pathname", m_pathname);
|
|
META_CONS("%*s: %d", width, "index", m_index);
|
|
META_CONS("%*s: %s", width, "source", str_source());
|
|
META_CONS("%*s: %s", width, "loadable", str_loadable(SL_ALLOWED));
|
|
META_CONS("%*s: %s", width, "unloadable", str_unloadable(SL_ALLOWED));
|
|
META_CONS("%*s: %s", width, "version", m_info ? m_info->version : "(nil)");
|
|
META_CONS("%*s: %s", width, "date", m_info ? m_info->date : "(nil)");
|
|
META_CONS("%*s: %s", width, "author", m_info ? m_info->author : "(nil)");
|
|
META_CONS("%*s: %s", width, "url", m_info ? m_info->url : "(nil)");
|
|
META_CONS("%*s: %s", width, "logtag", m_info ? m_info->logtag : "(nil)");
|
|
META_CONS("%*s: %s", width, "ifvers", m_info ? m_info->ifvers : "(nil)");
|
|
|
|
// ctime() includes newline at EOL
|
|
char* tstr = ctime(&m_time_loaded);
|
|
if ((cp = Q_strchr(tstr, '\n')))
|
|
*cp = '\0';
|
|
|
|
META_CONS("%*s: %s", width, "last loaded", tstr);
|
|
// XXX show file time ?
|
|
|
|
show_table("DLLAPI", m_dllapi_table, &g_dllapi_info.pfnGameInit, false);
|
|
show_table("DLLAPI Post", m_dllapi_post_table, &g_dllapi_info.pfnGameInit, true);
|
|
|
|
show_table("NEWAPI", m_newapi_table, &g_newapi_info.pfnOnFreeEntPrivateData, false);
|
|
show_table("NEWAPI Post", m_newapi_post_table, &g_newapi_info.pfnOnFreeEntPrivateData, true);
|
|
|
|
show_table("Engine", m_engine_table, &g_engineapi_info.pfnPrecacheModel, false);
|
|
show_table("Engine Post", m_engine_post_table, &g_engineapi_info.pfnPrecacheModel, true);
|
|
|
|
g_regCmds->show(m_index);
|
|
g_regCvars->show(m_index);
|
|
|
|
if (g_plugins->found_child_plugins(m_index))
|
|
g_plugins->show(m_index);
|
|
else
|
|
META_CONS("No child plugins.");
|
|
}
|
|
|
|
// Check whether the file on disk looks like it's been updated since we
|
|
// last loaded the plugin.
|
|
bool MPlugin::newer_file() const
|
|
{
|
|
struct stat st;
|
|
|
|
if (stat(m_pathname, &st) != 0) {
|
|
META_ERROR("ini: Skipping plugin, couldn't stat file '%s': %s", m_pathname, strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
time_t file_time = max(st.st_ctime, st.st_mtime);
|
|
META_DEBUG(5, "newer_file? file=%s; load=%d, file=%d; ctime=%d, mtime=%d", m_file, m_time_loaded, file_time, st.st_ctime, st.st_mtime);
|
|
if (file_time > m_time_loaded)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
// Return a string describing status of plugin.
|
|
// SIMPLE is the default.
|
|
// SHOW is max 4 chars, for "show" output.
|
|
const char *MPlugin::str_status(STR_STATUS fmt) const
|
|
{
|
|
bool show = fmt == ST_SHOW;
|
|
switch (m_status) {
|
|
case PL_EMPTY: return show ? "empt" : "empty";
|
|
case PL_VALID: return show ? "info" : "valid";
|
|
case PL_BADFILE: return show ? "badf" : "badfile";
|
|
case PL_OPENED: return show ? "open" : "opened";
|
|
case PL_FAILED: return show ? "fail" : "failed";
|
|
case PL_RUNNING: return show ? "RUN" : "running";
|
|
case PL_PAUSED: return show ? "PAUS" : "paused";
|
|
default: return UTIL_VarArgs(show ? "UNK%d" : "unknown (%d)", m_status);
|
|
}
|
|
}
|
|
|
|
// Return a string (one word) describing requested action for plugin.
|
|
// SIMPLE is the default.
|
|
// SHOW is max 4 chars, for "show" output.
|
|
const char *MPlugin::str_action(STR_ACTION fmt) const
|
|
{
|
|
bool show = fmt == ST_SHOW;
|
|
switch (m_action) {
|
|
case PA_NULL: return "null";
|
|
case PA_NONE: return show ? " - " : "none";
|
|
case PA_KEEP: return "keep";
|
|
case PA_LOAD: return "load";
|
|
case PA_ATTACH: return show ? "atch" : "attach";
|
|
case PA_UNLOAD: return show ? "unld" : "unload";
|
|
case PA_RELOAD: return show ? "relo" : "reload";
|
|
default: return UTIL_VarArgs(show ? "UNK%d" : "unknown (%d)", m_action);
|
|
}
|
|
}
|
|
|
|
// Return a string describing how the plugin was loaded.
|
|
const char *MPlugin::str_source(STR_SOURCE fmt) const
|
|
{
|
|
bool show = fmt == SO_SHOW;
|
|
switch (m_source) {
|
|
case PS_INI: return show ? "ini" : "ini file";
|
|
case PS_CMD: return show ? "cmd" : "console command";
|
|
case PS_PLUGIN:
|
|
if (m_source_plugin_index <= 0)
|
|
return show ? "plUN" : "unloaded plugin";
|
|
return show ? UTIL_VarArgs("pl%d", m_source_plugin_index) : UTIL_VarArgs("plugin [%s]", g_plugins->find(m_source_plugin_index)->description());
|
|
default: return UTIL_VarArgs(show ? "UNK%d" : "unknown (%d)", m_source);
|
|
}
|
|
}
|
|
|
|
// Return a string describing why a plugin is to be unloaded.
|
|
const char *MPlugin::str_reason(PL_UNLOAD_REASON reason) const
|
|
{
|
|
switch (reason) {
|
|
case PNL_NULL: return "null";
|
|
case PNL_INI_DELETED: return "deleted from ini file";
|
|
case PNL_FILE_NEWER: return "file on disk is newer";
|
|
case PNL_COMMAND: return "server command";
|
|
case PNL_CMD_FORCED: return "forced by server command";
|
|
case PNL_PLUGIN: return UTIL_VarArgs("%s (request from plugin[%d])", str_reason(PNL_NULL), m_unloader_index);
|
|
case PNL_PLG_FORCED: return UTIL_VarArgs("%s (forced request from plugin[%d])", str_reason(PNL_NULL), m_unloader_index);
|
|
case PNL_RELOAD: return "reloading";
|
|
default: return UTIL_VarArgs("unknown (%d)", reason);
|
|
}
|
|
}
|
|
|
|
// Return a string describing given plugin loadtime.
|
|
// SIMPLE is the default.
|
|
// SHOW is max 3 chars, for "show" output.
|
|
// ALLOWED is in terms of when it's allowed to load/unload.
|
|
// NOW is to describe current situation of load/unload attempt.
|
|
const char *MPlugin::str_loadtime(PLUG_LOADTIME ptime, STR_LOADTIME fmt)
|
|
{
|
|
if (ptime >= PT_NEVER || ptime <= PT_ANYPAUSE)
|
|
return s_rPrintLoadTime[ptime][fmt];
|
|
|
|
return UTIL_VarArgs((fmt == SL_SHOW) ? "UNK-%i" : "unknown (%i)", ptime);
|
|
}
|
|
|
|
const char* MPlugin::str_loadable() const
|
|
{
|
|
return m_info ? str_loadtime(m_info->loadable, SL_SIMPLE) : " -";
|
|
}
|
|
|
|
const char* MPlugin::str_unloadable() const
|
|
{
|
|
return m_info ? str_loadtime(m_info->unloadable, SL_SIMPLE) : " -";
|
|
}
|
|
|
|
const char* MPlugin::str_loadable(STR_LOADTIME fmt) const
|
|
{
|
|
return m_info ? str_loadtime(m_info->loadable, fmt) : " -";
|
|
}
|
|
|
|
const char* MPlugin::str_unloadable(STR_LOADTIME fmt) const
|
|
{
|
|
return m_info ? str_loadtime(m_info->unloadable, fmt) : " -";
|
|
}
|
|
|
|
PLUG_STATUS MPlugin::status() const
|
|
{
|
|
return m_status;
|
|
}
|
|
|
|
PLUG_ACTION MPlugin::action() const
|
|
{
|
|
return m_action;
|
|
}
|
|
|
|
const char* MPlugin::description() const
|
|
{
|
|
return m_desc;
|
|
}
|
|
|
|
plugin_info_t* MPlugin::info() const
|
|
{
|
|
return m_info;
|
|
}
|
|
|
|
int MPlugin::index() const
|
|
{
|
|
return m_index;
|
|
}
|
|
|
|
int MPlugin::source_index() const
|
|
{
|
|
return m_source_plugin_index;
|
|
}
|
|
|
|
const char* MPlugin::file() const
|
|
{
|
|
return m_file;
|
|
}
|
|
|
|
const char* MPlugin::filename() const
|
|
{
|
|
return m_filename;
|
|
}
|
|
|
|
const char* MPlugin::pathname() const
|
|
{
|
|
return m_pathname;
|
|
}
|
|
|
|
const CSysModule& MPlugin::sys_module() const
|
|
{
|
|
return m_sys_module;
|
|
}
|
|
|
|
size_t MPlugin::status_ptr()
|
|
{
|
|
return size_t(&m_status);
|
|
}
|
|
|
|
void MPlugin::set_action(PLUG_ACTION action)
|
|
{
|
|
m_action = action;
|
|
}
|