mirror of
https://github.com/alliedmodders/metamod-source.git
synced 2024-12-05 17:24:15 +01:00
1258 lines
26 KiB
C++
1258 lines
26 KiB
C++
/**
|
|
* vim: set ts=4 sw=4 tw=99 noet :
|
|
* ======================================================
|
|
* Metamod:Source
|
|
* Copyright (C) 2004-2010 AlliedModders LLC and authors.
|
|
* All rights reserved.
|
|
* ======================================================
|
|
*
|
|
* This software is provided 'as-is', without any express or implied warranty.
|
|
* In no event will the authors be held liable for any damages arising from
|
|
* the use of this software.
|
|
*
|
|
* Permission is granted to anyone to use this software for any purpose,
|
|
* including commercial applications, and to alter it and redistribute it
|
|
* freely, subject to the following restrictions:
|
|
*
|
|
* 1. The origin of this software must not be misrepresented; you must not
|
|
* claim that you wrote the original software. If you use this software in a
|
|
* product, an acknowledgment in the product documentation would be
|
|
* appreciated but is not required.
|
|
* 2. Altered source versions must be plainly marked as such, and must not be
|
|
* misrepresented as being the original software.
|
|
* 3. This notice may not be removed or altered from any source distribution.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include "metamod_oslink.h"
|
|
#include "metamod.h"
|
|
#include <interface.h>
|
|
#include <eiface.h>
|
|
#include "metamod_provider.h"
|
|
#include "metamod_plugins.h"
|
|
#include "metamod_util.h"
|
|
#include "metamod_console.h"
|
|
#include "provider/provider_ep2.h"
|
|
#if defined __linux__
|
|
#include <sys/stat.h>
|
|
#endif
|
|
|
|
using namespace SourceMM;
|
|
using namespace SourceHook;
|
|
using namespace SourceHook::Impl;
|
|
|
|
/**
|
|
* @brief Implementation of main SourceMM GameDLL functionality
|
|
* @file sourcemm.cpp
|
|
*/
|
|
|
|
SH_DECL_MANUALHOOK0(SGD_GameInit, 0, 0, 0, bool);
|
|
SH_DECL_MANUALHOOK6(SGD_LevelInit, 0, 0, 0, bool, const char *, const char *, const char *, const char *, bool, bool);
|
|
SH_DECL_MANUALHOOK0_void(SGD_LevelShutdown, 0, 0, 0);
|
|
|
|
static void
|
|
Handler_LevelShutdown();
|
|
|
|
static bool
|
|
Handler_LevelInit(char const *pMapName,
|
|
char const *pMapEntities,
|
|
char const *pOldLevel,
|
|
char const *pLandmarkName,
|
|
bool loadGame,
|
|
bool background);
|
|
|
|
static bool
|
|
Handler_GameInit();
|
|
|
|
static void
|
|
InitializeVSP();
|
|
|
|
static int
|
|
LoadPluginsFromFile(const char *filepath, int &skipped);
|
|
|
|
static int
|
|
LoadVDFPluginsFromDir(const char *dir, int &skipped);
|
|
|
|
struct game_dll_t
|
|
{
|
|
CreateInterfaceFn factory;
|
|
};
|
|
|
|
static String mod_path;
|
|
static String metamod_path;
|
|
static String full_bin_path;
|
|
static int vsp_version = 0;
|
|
static int gamedll_version = 0;
|
|
static int engine_build = SOURCE_ENGINE_UNKNOWN;
|
|
static List<game_dll_t *> gamedll_list;
|
|
static bool is_gamedll_loaded = false;
|
|
static bool in_first_level = true;
|
|
static bool is_game_init = false;
|
|
static bool vsp_load_requested = false;
|
|
static bool vsp_loaded = false;
|
|
static game_dll_t gamedll_info;
|
|
static ConVar *metamod_version = NULL;
|
|
static ConVar *mm_pluginsfile = NULL;
|
|
static ConVar *mm_basedir = NULL;
|
|
static CreateInterfaceFn engine_factory = NULL;
|
|
static CreateInterfaceFn physics_factory = NULL;
|
|
static CreateInterfaceFn filesystem_factory = NULL;
|
|
static CGlobalVars *gpGlobals = NULL;
|
|
static CHookManagerAutoGen g_SH_HookManagerAutoGen(&g_SourceHook);
|
|
static META_RES last_meta_res;
|
|
static IServerPluginCallbacks *vsp_callbacks = NULL;
|
|
static bool were_plugins_loaded = false;
|
|
static bool g_bIsVspBridged = false;
|
|
|
|
MetamodSource g_Metamod;
|
|
PluginId g_PLID = Pl_Console;
|
|
CSourceHookImpl g_SourceHook;
|
|
ISourceHook *g_SHPtr = &g_SourceHook;
|
|
SourceMM::ISmmAPI *g_pMetamod = &g_Metamod;
|
|
|
|
/* Helper Macro */
|
|
#define IFACE_MACRO(orig,nam) \
|
|
CPluginManager::CPlugin *pl; \
|
|
SourceHook::List<IMetamodListener *>::iterator event; \
|
|
IMetamodListener *api; \
|
|
int mret = 0; \
|
|
void *val = NULL; \
|
|
for (PluginIter iter = g_PluginMngr._begin(); iter != g_PluginMngr._end(); iter++) { \
|
|
pl = (*iter); \
|
|
for (event=pl->m_Events.begin(); event!=pl->m_Events.end(); event++) { \
|
|
api = (*event); \
|
|
mret = IFACE_FAILED; \
|
|
if ( (val=api->On##nam##Query(iface, &mret)) != NULL ) { \
|
|
if (ret) *ret = mret; \
|
|
return val; \
|
|
} \
|
|
} \
|
|
} \
|
|
return (orig)(iface, ret);
|
|
|
|
#define ITER_EVENT(evn, args) \
|
|
CPluginManager::CPlugin *pl; \
|
|
SourceHook::List<IMetamodListener *>::iterator event; \
|
|
IMetamodListener *api; \
|
|
for (PluginIter iter = g_PluginMngr._begin(); iter != g_PluginMngr._end(); iter++) { \
|
|
pl = (*iter); \
|
|
for (event=pl->m_Events.begin(); event!=pl->m_Events.end(); event++) { \
|
|
api = (*event); \
|
|
api->evn args; \
|
|
} \
|
|
}
|
|
|
|
/* Initialize everything here */
|
|
void
|
|
mm_InitializeForLoad()
|
|
{
|
|
char full_path[PATH_SIZE] = {0};
|
|
GetFileOfAddress((void *)gamedll_info.factory, full_path, sizeof(full_path));
|
|
full_bin_path.assign(full_path);
|
|
|
|
/* Like Metamod, reload plugins at the end of the map.
|
|
* This is so plugins can hook everything on load, BUT, new plugins will be reloaded
|
|
* if the server is shut down (silly, but rare case).
|
|
*/
|
|
in_first_level = true;
|
|
|
|
SourceHook::MemFuncInfo info;
|
|
|
|
if (!provider->GetHookInfo(ProvidedHook_GameInit, &info))
|
|
{
|
|
provider->DisplayError("Metamod:Source could not find a valid hook for IServerGameDLL::GameInit");
|
|
}
|
|
SH_MANUALHOOK_RECONFIGURE(SGD_GameInit, info.vtblindex, info.vtbloffs, info.thisptroffs);
|
|
SH_ADD_MANUALHOOK_STATICFUNC(SGD_GameInit, server, Handler_GameInit, false);
|
|
|
|
if (!provider->GetHookInfo(ProvidedHook_LevelInit, &info))
|
|
{
|
|
provider->DisplayError("Metamod:Source could not find a valid hook for IServerGameDLL::LevelInit");
|
|
}
|
|
SH_MANUALHOOK_RECONFIGURE(SGD_LevelInit, info.vtblindex, info.vtbloffs, info.thisptroffs);
|
|
SH_ADD_MANUALHOOK_STATICFUNC(SGD_LevelInit, server, Handler_LevelInit, true);
|
|
|
|
if (!provider->GetHookInfo(ProvidedHook_LevelShutdown, &info))
|
|
{
|
|
provider->DisplayError("Metamod:Source could not find a valid hook for IServerGameDLL::LevelShutdown");
|
|
}
|
|
SH_MANUALHOOK_RECONFIGURE(SGD_LevelShutdown, info.vtblindex, info.vtbloffs, info.thisptroffs);
|
|
SH_ADD_MANUALHOOK_STATICFUNC(SGD_LevelShutdown, server, Handler_LevelShutdown, true);
|
|
}
|
|
|
|
bool
|
|
mm_DetectGameInformation()
|
|
{
|
|
char game_path[PATH_SIZE];
|
|
|
|
/* Get value of -game from command line, defaulting to hl2 as engine seems to do */
|
|
const char *game_dir = provider->GetCommandLineValue("-game");
|
|
|
|
if (game_dir)
|
|
{
|
|
/* Get absolute path */
|
|
abspath(game_path, game_dir);
|
|
}
|
|
else
|
|
{
|
|
/* Get absolute path for current directory */
|
|
abspath(game_path, ".");
|
|
}
|
|
|
|
mod_path.assign(game_path);
|
|
|
|
engine_build = provider->DetermineSourceEngine(game_dir);
|
|
|
|
return true;
|
|
}
|
|
|
|
void *
|
|
ServerFactory(const char *iface, int *ret)
|
|
{
|
|
IFACE_MACRO(gamedll_info.factory, GameDLL);
|
|
}
|
|
|
|
static int
|
|
LoadPluginsFromFile(const char *filepath, int &skipped)
|
|
{
|
|
FILE *fp;
|
|
int total = 0;
|
|
PluginId id;
|
|
bool already;
|
|
|
|
skipped = 0;
|
|
|
|
fp = fopen(filepath, "rt");
|
|
if (!fp)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
char buffer[255], error[255], full_path[PATH_SIZE];
|
|
const char *file;
|
|
size_t length;
|
|
while (!feof(fp) && fgets(buffer, sizeof(buffer), fp) != NULL)
|
|
{
|
|
UTIL_TrimLeft(buffer);
|
|
UTIL_TrimRight(buffer);
|
|
|
|
length = strlen(buffer);
|
|
if (!length)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (buffer[0] == '\0' || buffer[0] == ';' || strncmp(buffer, "//", 2) == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
file = buffer;
|
|
if (buffer[0] == '"')
|
|
{
|
|
char *cptr = buffer;
|
|
file = ++cptr;
|
|
|
|
while (*cptr)
|
|
{
|
|
if (*cptr == '"')
|
|
{
|
|
*cptr = '\0';
|
|
break;
|
|
}
|
|
cptr++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
char *cptr = buffer;
|
|
while (*cptr)
|
|
{
|
|
if (isspace(*cptr))
|
|
{
|
|
char *optr = cptr;
|
|
while (*cptr && isspace(*cptr))
|
|
{
|
|
cptr++;
|
|
}
|
|
*optr = '\0';
|
|
UTIL_TrimRight(cptr);
|
|
if (*cptr && isalpha(*cptr))
|
|
{
|
|
g_PluginMngr.SetAlias(buffer, cptr);
|
|
file = cptr;
|
|
}
|
|
break;
|
|
}
|
|
cptr++;
|
|
}
|
|
}
|
|
if (!file[0])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
g_Metamod.GetFullPluginPath(file, full_path, sizeof(full_path));
|
|
|
|
id = g_PluginMngr.Load(full_path, Pl_File, already, error, sizeof(error));
|
|
if (id < Pl_MinId || g_PluginMngr.FindById(id)->m_Status < Pl_Paused)
|
|
{
|
|
mm_LogMessage("[META] Failed to load plugin %s. %s", buffer, error);
|
|
}
|
|
else
|
|
{
|
|
if (already)
|
|
skipped++;
|
|
else
|
|
total++;
|
|
}
|
|
}
|
|
fclose(fp);
|
|
|
|
return total;
|
|
}
|
|
|
|
void InitializeVSP()
|
|
{
|
|
if (g_bIsVspBridged)
|
|
return;
|
|
|
|
size_t len;
|
|
char engine_file[PATH_SIZE];
|
|
char engine_path[PATH_SIZE];
|
|
char rel_path[PATH_SIZE * 2];
|
|
|
|
GetFileOfAddress((void *)engine_factory, engine_file, sizeof(engine_file));
|
|
|
|
/* Chop off the "engine" file part */
|
|
len = strlen(engine_file);
|
|
for (size_t i = len - 1; i < len; i--)
|
|
{
|
|
if (engine_file[i] == '/' || engine_file[i] == '\\')
|
|
{
|
|
engine_file[i] = '\0';
|
|
break;
|
|
}
|
|
}
|
|
abspath(engine_path, engine_file);
|
|
|
|
const char *usepath = metamod_path.c_str();
|
|
if (UTIL_Relatize(rel_path, sizeof(rel_path), engine_path, metamod_path.c_str()))
|
|
{
|
|
usepath = rel_path;
|
|
}
|
|
|
|
char command[PATH_SIZE * 2];
|
|
UTIL_Format(command, sizeof(command), "plugin_load \"%s\"\n", usepath);
|
|
provider->ServerCommand(command);
|
|
}
|
|
|
|
/* Wrapper function. This is called when the GameDLL thinks it's using
|
|
* the engine's real engineFactory.
|
|
*/
|
|
static void *
|
|
EngineFactory(const char *iface, int *ret)
|
|
{
|
|
IFACE_MACRO(engine_factory, Engine);
|
|
}
|
|
|
|
/* Wrapper function. This is called when the GameDLL thinks it's using
|
|
* the engine's real physicsFactory.
|
|
*/
|
|
static void *
|
|
PhysicsFactory(const char *iface, int *ret)
|
|
{
|
|
IFACE_MACRO(physics_factory, Physics);
|
|
}
|
|
|
|
/* Wrapper function. This is called when the GameDLL thinks it's using
|
|
* the engine's real fileSystemFactory.
|
|
*/
|
|
static void *
|
|
FileSystemFactory(const char *iface, int *ret)
|
|
{
|
|
IFACE_MACRO(filesystem_factory, FileSystem);
|
|
}
|
|
|
|
void
|
|
mm_LogMessage(const char *msg, ...)
|
|
{
|
|
va_list ap;
|
|
static char buffer[2048];
|
|
|
|
va_start(ap, msg);
|
|
size_t len = vsnprintf(buffer, sizeof(buffer) - 2, msg, ap);
|
|
va_end(ap);
|
|
|
|
buffer[len++] = '\n';
|
|
buffer[len] = '\0';
|
|
|
|
if (!provider->LogMessage(buffer))
|
|
{
|
|
fprintf(stdout, "%s", buffer);
|
|
}
|
|
}
|
|
|
|
static void
|
|
DoInitialPluginLoads()
|
|
{
|
|
const char *pluginFile = provider->GetCommandLineValue("mm_pluginsfile", NULL);
|
|
const char *mmBaseDir = provider->GetCommandLineValue("mm_basedir", NULL);
|
|
if (!pluginFile)
|
|
{
|
|
pluginFile = provider->GetConVarString(mm_pluginsfile);
|
|
if (pluginFile == NULL)
|
|
{
|
|
pluginFile = "addons/metamod/metaplugins.ini";
|
|
}
|
|
}
|
|
if (!mmBaseDir)
|
|
{
|
|
mmBaseDir = provider->GetConVarString(mm_basedir);
|
|
if (mmBaseDir == NULL)
|
|
{
|
|
mmBaseDir = "addons/metamod";
|
|
}
|
|
}
|
|
|
|
char filepath[PATH_SIZE], vdfpath[PATH_SIZE];
|
|
|
|
g_Metamod.PathFormat(filepath, sizeof(filepath), "%s/%s", mod_path.c_str(), pluginFile);
|
|
g_Metamod.PathFormat(vdfpath, sizeof(vdfpath), "%s/%s", mod_path.c_str(), mmBaseDir);
|
|
mm_LoadPlugins(filepath, vdfpath);
|
|
}
|
|
|
|
void
|
|
mm_StartupMetamod(bool is_vsp_load)
|
|
{
|
|
char buffer[255];
|
|
|
|
UTIL_Format(buffer,
|
|
sizeof(buffer),
|
|
"%s%s",
|
|
MMS_FULL_VERSION,
|
|
is_vsp_load ? "V" : "");
|
|
|
|
metamod_version = provider->CreateConVar("metamod_version",
|
|
MMS_FULL_VERSION,
|
|
"Metamod:Source Version",
|
|
ConVarFlag_Notify|ConVarFlag_SpOnly);
|
|
|
|
provider->SetConVarString(metamod_version, buffer);
|
|
|
|
mm_pluginsfile = provider->CreateConVar("mm_pluginsfile",
|
|
#if defined WIN32 || defined _WIN32
|
|
"addons\\metamod\\metaplugins.ini",
|
|
#else
|
|
"addons/metamod/metaplugins.ini",
|
|
#endif
|
|
"Metamod:Source Plugins File",
|
|
ConVarFlag_SpOnly);
|
|
|
|
mm_basedir = provider->CreateConVar("mm_basedir",
|
|
#if defined __linux__ || defined __APPLE__
|
|
"addons/metamod",
|
|
#else
|
|
"addons\\metamod",
|
|
#endif
|
|
"Metamod:Source Base Folder",
|
|
ConVarFlag_SpOnly);
|
|
|
|
g_bIsVspBridged = is_vsp_load;
|
|
|
|
if (!is_vsp_load)
|
|
{
|
|
DoInitialPluginLoads();
|
|
in_first_level = true;
|
|
}
|
|
}
|
|
|
|
void
|
|
mm_InitializeGlobals(CreateInterfaceFn engineFactory,
|
|
CreateInterfaceFn physicsFactory,
|
|
CreateInterfaceFn filesystemFactory,
|
|
CGlobalVars *pGlobals)
|
|
{
|
|
engine_factory = engineFactory;
|
|
physics_factory = physicsFactory;
|
|
filesystem_factory = filesystemFactory;
|
|
gpGlobals = pGlobals;
|
|
provider->Notify_DLLInit_Pre(engineFactory, gamedll_info.factory);
|
|
}
|
|
|
|
static bool
|
|
Handler_GameInit()
|
|
{
|
|
if (is_game_init)
|
|
return true;
|
|
|
|
if (vsp_load_requested)
|
|
InitializeVSP();
|
|
|
|
if (g_bIsVspBridged && !were_plugins_loaded)
|
|
{
|
|
DoInitialPluginLoads();
|
|
g_PluginMngr.SetAllLoaded();
|
|
were_plugins_loaded = true;
|
|
}
|
|
|
|
is_game_init = true;
|
|
|
|
RETURN_META_VALUE(MRES_IGNORED, true);
|
|
}
|
|
|
|
void
|
|
mm_UnloadMetamod()
|
|
{
|
|
/* Unload plugins */
|
|
g_PluginMngr.UnloadAll();
|
|
|
|
provider->Notify_DLLShutdown_Pre();
|
|
|
|
g_SourceHook.CompleteShutdown();
|
|
}
|
|
|
|
static void
|
|
Handler_LevelShutdown(void)
|
|
{
|
|
if (g_bIsVspBridged && !were_plugins_loaded)
|
|
{
|
|
DoInitialPluginLoads();
|
|
g_PluginMngr.SetAllLoaded();
|
|
were_plugins_loaded = true;
|
|
in_first_level = true;
|
|
}
|
|
|
|
if (!in_first_level)
|
|
{
|
|
char filepath[PATH_SIZE], vdfpath[PATH_SIZE];
|
|
|
|
g_Metamod.PathFormat(filepath,
|
|
sizeof(filepath),
|
|
"%s/%s",
|
|
mod_path.c_str(),
|
|
provider->GetConVarString(mm_pluginsfile));
|
|
g_Metamod.PathFormat(vdfpath,
|
|
sizeof(vdfpath),
|
|
"%s/%s",
|
|
mod_path.c_str(),
|
|
provider->GetConVarString(mm_basedir));
|
|
mm_LoadPlugins(filepath, vdfpath);
|
|
}
|
|
else
|
|
{
|
|
in_first_level = false;
|
|
}
|
|
|
|
ITER_EVENT(OnLevelShutdown, ());
|
|
|
|
RETURN_META(MRES_IGNORED);
|
|
}
|
|
|
|
static bool
|
|
Handler_LevelInit(char const *pMapName,
|
|
char const *pMapEntities,
|
|
char const *pOldLevel,
|
|
char const *pLandmarkName,
|
|
bool loadGame,
|
|
bool background)
|
|
{
|
|
ITER_EVENT(OnLevelInit, (pMapName, pMapEntities, pOldLevel, pLandmarkName, loadGame, background));
|
|
|
|
RETURN_META_VALUE(MRES_IGNORED, false);
|
|
}
|
|
|
|
void MetamodSource::LogMsg(ISmmPlugin *pl, const char *msg, ...)
|
|
{
|
|
va_list ap;
|
|
char buffer[2048];
|
|
|
|
va_start(ap, msg);
|
|
UTIL_FormatArgs(buffer, sizeof(buffer), msg, ap);
|
|
va_end(ap);
|
|
|
|
mm_LogMessage("[%s] %s", pl->GetLogTag(), buffer);
|
|
}
|
|
|
|
CreateInterfaceFn MetamodSource::GetEngineFactory(bool syn/* =true */)
|
|
{
|
|
if (syn)
|
|
return EngineFactory;
|
|
return engine_factory;
|
|
}
|
|
|
|
CreateInterfaceFn MetamodSource::GetPhysicsFactory(bool syn/* =true */)
|
|
{
|
|
if (syn)
|
|
return PhysicsFactory;
|
|
return physics_factory;
|
|
}
|
|
|
|
CreateInterfaceFn MetamodSource::GetFileSystemFactory(bool syn/* =true */)
|
|
{
|
|
if (syn)
|
|
return FileSystemFactory;
|
|
return filesystem_factory;
|
|
}
|
|
|
|
CreateInterfaceFn MetamodSource::GetServerFactory(bool syn/* =true */)
|
|
{
|
|
if (syn)
|
|
return ServerFactory;
|
|
return gamedll_info.factory;
|
|
}
|
|
|
|
CGlobalVars *MetamodSource::GetCGlobals()
|
|
{
|
|
return gpGlobals;
|
|
}
|
|
|
|
void MetamodSource::SetLastMetaReturn(META_RES res)
|
|
{
|
|
last_meta_res = res;
|
|
}
|
|
|
|
META_RES MetamodSource::GetLastMetaReturn()
|
|
{
|
|
return last_meta_res;
|
|
}
|
|
|
|
void MetamodSource::ConPrint(const char *str)
|
|
{
|
|
provider->ConsolePrint(str);
|
|
}
|
|
|
|
void MetamodSource::ConPrintf(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
char buffer[2048];
|
|
|
|
va_start(ap, fmt);
|
|
UTIL_FormatArgs(buffer, sizeof(buffer), fmt, ap);
|
|
va_end(ap);
|
|
|
|
provider->ConsolePrint(buffer);
|
|
}
|
|
|
|
void MetamodSource::GetApiVersions(int &major, int &minor, int &plvers, int &plmin)
|
|
{
|
|
major = METAMOD_API_MAJOR;
|
|
minor = METAMOD_API_MINOR;
|
|
plvers = METAMOD_PLAPI_VERSION;
|
|
plmin = PLAPI_MIN_VERSION;
|
|
}
|
|
|
|
void MetamodSource::GetShVersions(int &shvers, int &shimpl)
|
|
{
|
|
shvers = SH_IFACE_VERSION;
|
|
shimpl = SH_IMPL_VERSION;
|
|
}
|
|
|
|
int MetamodSource::FormatIface(char iface[], unsigned int maxlength)
|
|
{
|
|
int length = (int)strlen(iface);
|
|
int i;
|
|
int num = 0;
|
|
|
|
for (i = length - 1; i >= 0; i--)
|
|
{
|
|
if (!isdigit(iface[i]))
|
|
{
|
|
if (i != length - 1)
|
|
{
|
|
num = 1;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if ( (num && ((int)maxlength <= length)) || (!num && ((int)maxlength <= length + 3)) )
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
if (i != length - 1)
|
|
{
|
|
num = atoi(&(iface[++i]));
|
|
}
|
|
|
|
num++;
|
|
|
|
snprintf(&(iface[i]), 4, "%03d", num);
|
|
|
|
return num;
|
|
}
|
|
|
|
void *MetamodSource::InterfaceSearch(CreateInterfaceFn fn, const char *iface, int max, int *ret)
|
|
{
|
|
char _if[256]; /* assume no interface goes beyond this */
|
|
size_t len = strlen(iface);
|
|
int num = 0;
|
|
void *pf = NULL;
|
|
|
|
if (max > 999)
|
|
{
|
|
max = 999;
|
|
}
|
|
|
|
if (len + 4 > sizeof(_if))
|
|
{
|
|
if (ret)
|
|
{
|
|
*ret = IFACE_FAILED;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
strcpy(_if, iface);
|
|
|
|
do
|
|
{
|
|
if ((pf = (fn)(_if, ret)) != NULL)
|
|
{
|
|
break;
|
|
}
|
|
if (num > max)
|
|
{
|
|
break;
|
|
}
|
|
} while ((num = FormatIface(_if, len+1)));
|
|
|
|
return pf;
|
|
}
|
|
|
|
void *MetamodSource::VInterfaceMatch(CreateInterfaceFn fn, const char *iface, int min)
|
|
{
|
|
char buffer[256]; /* assume no interface will go beyond this */
|
|
size_t len = strlen(iface);
|
|
int ret; /* just in case something doesn't handle NULL properly */
|
|
|
|
if (len > sizeof(buffer) - 4)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
strcpy(buffer, iface);
|
|
|
|
if (min != -1)
|
|
{
|
|
char *ptr = &buffer[len - 1];
|
|
int digits = 0;
|
|
while (isdigit(*ptr) && digits <=3)
|
|
{
|
|
*ptr = '\0';
|
|
digits++;
|
|
ptr--;
|
|
}
|
|
if (digits != 3)
|
|
{
|
|
/* for now, assume this is an error */
|
|
strcpy(buffer, iface);
|
|
}
|
|
else
|
|
{
|
|
char num[4];
|
|
min = (min == 0) ? 1 : min;
|
|
snprintf(num, sizeof(num), "%03d", min);
|
|
strcat(buffer, num);
|
|
}
|
|
}
|
|
|
|
return InterfaceSearch(fn, buffer, IFACE_MAXNUM, &ret);
|
|
}
|
|
|
|
const char *MetamodSource::GetBaseDir()
|
|
{
|
|
return mod_path.c_str();
|
|
}
|
|
|
|
size_t MetamodSource::PathFormat(char *buffer, size_t len, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
size_t mylen = UTIL_FormatArgs(buffer, len, fmt, ap);
|
|
va_end(ap);
|
|
|
|
for (size_t i = 0; i < mylen; i++)
|
|
{
|
|
if (buffer[i] == ALT_SEP_CHAR)
|
|
{
|
|
buffer[i] = PATH_SEP_CHAR;
|
|
}
|
|
}
|
|
|
|
return mylen;
|
|
}
|
|
|
|
void MetamodSource::ClientConPrintf(edict_t *client, const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
char buffer[2048];
|
|
|
|
va_start(ap, fmt);
|
|
UTIL_FormatArgs(buffer, sizeof(buffer), fmt, ap);
|
|
va_end(ap);
|
|
|
|
provider->ClientConsolePrint(client, buffer);
|
|
}
|
|
|
|
void MetamodSource::EnableVSPListener()
|
|
{
|
|
if (is_game_init && !vsp_load_requested && !vsp_loaded)
|
|
{
|
|
InitializeVSP();
|
|
}
|
|
|
|
vsp_load_requested = true;
|
|
}
|
|
|
|
int MetamodSource::GetVSPVersion()
|
|
{
|
|
return vsp_version;
|
|
}
|
|
|
|
int MetamodSource::GetGameDLLVersion()
|
|
{
|
|
return gamedll_version;
|
|
}
|
|
|
|
bool MetamodSource::RemotePrintingAvailable()
|
|
{
|
|
return provider->IsRemotePrintingAvailable();
|
|
}
|
|
|
|
void *MetamodSource::MetaFactory(const char *iface, int *ret, PluginId *id)
|
|
{
|
|
if (id)
|
|
{
|
|
*id = 0;
|
|
}
|
|
|
|
if (!iface)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* First check ours... we get first chance! */
|
|
if (strcmp(iface, MMIFACE_SOURCEHOOK) == 0)
|
|
{
|
|
if (ret)
|
|
{
|
|
*ret = IFACE_OK;
|
|
}
|
|
return static_cast<void *>(static_cast<SourceHook::ISourceHook *>(&g_SourceHook));
|
|
}
|
|
else if (strcmp(iface, MMIFACE_PLMANAGER) == 0)
|
|
{
|
|
if (ret)
|
|
{
|
|
*ret = IFACE_OK;
|
|
}
|
|
return static_cast<void *>(static_cast<ISmmPluginManager *>(&g_PluginMngr));
|
|
}
|
|
else if (strcmp(iface, MMIFACE_SH_HOOKMANAUTOGEN) == 0)
|
|
{
|
|
if (ret)
|
|
{
|
|
*ret = IFACE_OK;
|
|
}
|
|
return static_cast<void *>(static_cast<SourceHook::IHookManagerAutoGen *>(&g_SH_HookManagerAutoGen));
|
|
}
|
|
|
|
CPluginManager::CPlugin *pl;
|
|
List<IMetamodListener *>::iterator event;
|
|
IMetamodListener *api;
|
|
void *value;
|
|
|
|
int subret = 0;
|
|
for (PluginIter iter = g_PluginMngr._begin();
|
|
iter != g_PluginMngr._end();
|
|
iter++)
|
|
{
|
|
pl = (*iter);
|
|
for (event = pl->m_Events.begin(); event != pl->m_Events.end(); event++)
|
|
{
|
|
api = (*event);
|
|
subret = IFACE_FAILED;
|
|
if ((value = api->OnMetamodQuery(iface, &subret)) != NULL)
|
|
{
|
|
if (ret)
|
|
{
|
|
*ret = subret;
|
|
}
|
|
if (id)
|
|
{
|
|
*id = pl->m_Id;
|
|
}
|
|
return value;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ret)
|
|
{
|
|
*ret = IFACE_FAILED;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void MetamodSource::AddListener(ISmmPlugin *plugin, IMetamodListener *pListener)
|
|
{
|
|
CPluginManager::CPlugin *pl = g_PluginMngr.FindByAPI(plugin);
|
|
|
|
pl->m_Events.push_back(pListener);
|
|
}
|
|
|
|
const char *MetamodSource::GetGameBinaryPath()
|
|
{
|
|
return full_bin_path.c_str();
|
|
}
|
|
|
|
const char *MetamodSource::GetPluginsFile()
|
|
{
|
|
return provider->GetConVarString(mm_pluginsfile);
|
|
}
|
|
|
|
const char *MetamodSource::GetVDFDir()
|
|
{
|
|
return provider->GetConVarString(mm_basedir);
|
|
}
|
|
|
|
IConCommandBaseAccessor *MetamodSource::GetCvarBaseAccessor()
|
|
{
|
|
return provider->GetConCommandBaseAccessor();
|
|
}
|
|
|
|
bool MetamodSource::RegisterConCommandBase(ISmmPlugin *plugin, ConCommandBase *pCommand)
|
|
{
|
|
if (provider->IsConCommandBaseACommand(pCommand))
|
|
{
|
|
g_PluginMngr.AddPluginCmd(plugin, pCommand);
|
|
}
|
|
else
|
|
{
|
|
g_PluginMngr.AddPluginCvar(plugin, pCommand);
|
|
}
|
|
|
|
return provider->RegisterConCommandBase(pCommand);
|
|
}
|
|
|
|
void MetamodSource::UnregisterConCommandBase(ISmmPlugin *plugin, ConCommandBase *pCommand)
|
|
{
|
|
if (provider->IsConCommandBaseACommand(pCommand))
|
|
{
|
|
g_PluginMngr.RemovePluginCmd(plugin, pCommand);
|
|
}
|
|
else
|
|
{
|
|
g_PluginMngr.RemovePluginCvar(plugin, pCommand);
|
|
}
|
|
|
|
CPluginManager::CPlugin *pOrig = g_PluginMngr.FindByAPI(plugin);
|
|
UnregisterConCommandBase(pOrig ? pOrig->m_Id : 0, pCommand);
|
|
}
|
|
|
|
void MetamodSource::UnregisterConCommandBase(PluginId id, ConCommandBase *pCommand)
|
|
{
|
|
PluginIter iter;
|
|
CPluginManager::CPlugin *pPlugin;
|
|
List<IMetamodListener *>::iterator event;
|
|
IMetamodListener *pML;
|
|
for (iter=g_PluginMngr._begin(); iter!=g_PluginMngr._end(); iter++)
|
|
{
|
|
pPlugin = (*iter);
|
|
if (pPlugin->m_Status < Pl_Paused)
|
|
{
|
|
continue;
|
|
}
|
|
/* Only valid for plugins >= 12 (v1:6, SourceMM 1.5) */
|
|
if (pPlugin->m_API->GetApiVersion() < 12)
|
|
{
|
|
continue;
|
|
}
|
|
for (event=pPlugin->m_Events.begin();
|
|
event!=pPlugin->m_Events.end();
|
|
event++)
|
|
{
|
|
pML = (*event);
|
|
pML->OnUnlinkConCommandBase(id, pCommand);
|
|
}
|
|
}
|
|
|
|
return provider->UnregisterConCommandBase(pCommand);
|
|
}
|
|
|
|
int MetamodSource::GetUserMessageCount()
|
|
{
|
|
return provider->GetUserMessageCount();
|
|
}
|
|
|
|
int MetamodSource::FindUserMessage(const char *name, int *size/* =NULL */)
|
|
{
|
|
return provider->FindUserMessage(name, size);
|
|
}
|
|
|
|
const char *MetamodSource::GetUserMessage(int index, int *size/* =NULL */)
|
|
{
|
|
return provider->GetUserMessage(index, size);
|
|
}
|
|
|
|
int MetamodSource::GetSourceEngineBuild()
|
|
{
|
|
return engine_build;
|
|
}
|
|
|
|
void MetamodSource::NotifyVSPListening(IServerPluginCallbacks *callbacks, int version)
|
|
{
|
|
if (version != -1)
|
|
vsp_version = version;
|
|
|
|
vsp_callbacks = callbacks;
|
|
ITER_EVENT(OnVSPListening, (callbacks));
|
|
|
|
if (is_gamedll_loaded)
|
|
{
|
|
/*
|
|
* MM:S is loaded as a game DLL so we need to set these for mm_IsVspBridged() and
|
|
* mm_IsVspLoadComplete()
|
|
*/
|
|
g_bIsVspBridged = true;
|
|
were_plugins_loaded = true;
|
|
}
|
|
}
|
|
|
|
IServerPluginCallbacks *MetamodSource::GetVSPInfo(int *pVersion)
|
|
{
|
|
if (pVersion)
|
|
{
|
|
*pVersion = vsp_version;
|
|
}
|
|
|
|
return vsp_callbacks;
|
|
}
|
|
|
|
size_t MetamodSource::Format(char *buffer, size_t maxlength, const char *format, ...)
|
|
{
|
|
va_list ap;
|
|
size_t result;
|
|
|
|
va_start(ap, format);
|
|
result = FormatArgs(buffer, maxlength, format, ap);
|
|
va_end(ap);
|
|
|
|
return result;
|
|
}
|
|
|
|
size_t MetamodSource::FormatArgs(char *buffer, size_t maxlength, const char *format, va_list ap)
|
|
{
|
|
return UTIL_FormatArgs(buffer, maxlength, format, ap);
|
|
}
|
|
|
|
bool MetamodSource::IsLoadedAsGameDLL()
|
|
{
|
|
return is_gamedll_loaded;
|
|
}
|
|
|
|
void MetamodSource::SetGameDLLInfo(CreateInterfaceFn serverFactory, int version, bool loaded)
|
|
{
|
|
gamedll_info.factory = serverFactory;
|
|
gamedll_version = version;
|
|
is_gamedll_loaded = loaded;
|
|
}
|
|
|
|
void MetamodSource::SetVSPListener(const char *path)
|
|
{
|
|
metamod_path.assign(path);
|
|
}
|
|
|
|
size_t MetamodSource::GetFullPluginPath(const char *plugin, char *buffer, size_t len)
|
|
{
|
|
const char *pext, *ext;
|
|
size_t num;
|
|
|
|
/* First find if it's an absolute path or not... */
|
|
if (plugin[0] == '/' || strncmp(&(plugin[1]), ":\\", 2) == 0)
|
|
{
|
|
return UTIL_Format(buffer, len, plugin);
|
|
}
|
|
|
|
/* Attempt to find a file extension */
|
|
pext = UTIL_GetExtension(plugin);
|
|
/* Add an extension if there's none there */
|
|
if (!pext)
|
|
{
|
|
#if defined WIN32 || defined _WIN32
|
|
ext = ".dll";
|
|
#elif defined __APPLE__
|
|
ext = ".dylib";
|
|
#else
|
|
ext = "_i486.so";
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
ext = "";
|
|
}
|
|
|
|
/* Format the new path */
|
|
num = PathFormat(buffer, len, "%s/%s%s", mod_path.c_str(), plugin, ext);
|
|
|
|
#if defined __linux__
|
|
/* If path was passed without extension and it doesn't exist with "_i486.so" try ".so" */
|
|
struct stat s;
|
|
if (!pext && stat(buffer, &s) != 0)
|
|
{
|
|
num = PathFormat(buffer, len, "%s/%s.so", mod_path.c_str(), plugin);
|
|
}
|
|
#endif
|
|
|
|
return num;
|
|
}
|
|
|
|
static bool
|
|
ProcessVDF(const char *path, bool &skipped)
|
|
{
|
|
PluginId id;
|
|
bool already;
|
|
char alias[24], file[255], full_path[255], error[255];
|
|
|
|
if (!provider->ProcessVDF(path, file, sizeof(file), alias, sizeof(alias)))
|
|
{
|
|
skipped = false;
|
|
return false;
|
|
}
|
|
|
|
if (alias[0] != '\0')
|
|
g_PluginMngr.SetAlias(alias, file);
|
|
|
|
g_Metamod.GetFullPluginPath(file, full_path, sizeof(full_path));
|
|
|
|
id = g_PluginMngr.Load(full_path, Pl_File, already, error, sizeof(error));
|
|
skipped = already;
|
|
if (id < Pl_MinId || g_PluginMngr.FindById(id)->m_Status < Pl_Paused)
|
|
{
|
|
mm_LogMessage("[META] Failed to load plugin %s: %s", file, error);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static int
|
|
LoadVDFPluginsFromDir(const char *dir, int &skipped)
|
|
{
|
|
bool success, skip;
|
|
int total = 0;
|
|
char path[MAX_PATH];
|
|
char relpath[MAX_PATH * 2];
|
|
|
|
skipped = 0;
|
|
|
|
#if defined _MSC_VER
|
|
HANDLE hFind;
|
|
WIN32_FIND_DATA fd;
|
|
char error[255];
|
|
|
|
g_Metamod.PathFormat(path, sizeof(path), "%s\\*.vdf", dir);
|
|
if ((hFind = FindFirstFile(path, &fd)) == INVALID_HANDLE_VALUE)
|
|
{
|
|
DWORD dw = GetLastError();
|
|
if (dw == ERROR_FILE_NOT_FOUND)
|
|
return 0;
|
|
|
|
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,
|
|
NULL,
|
|
dw,
|
|
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
|
error,
|
|
sizeof(error),
|
|
NULL);
|
|
mm_LogMessage("[META] Could not open folder \"%s\" (%s)", dir, error);
|
|
return 0;
|
|
}
|
|
|
|
do
|
|
{
|
|
g_Metamod.PathFormat(path, sizeof(path), "%s\\%s", dir, fd.cFileName);
|
|
UTIL_Relatize(relpath, sizeof(relpath), mod_path.c_str(), path);
|
|
success = ProcessVDF(relpath, skip);
|
|
if (skip)
|
|
skipped++;
|
|
else if (success)
|
|
total++;
|
|
} while (FindNextFile(hFind, &fd));
|
|
|
|
FindClose(hFind);
|
|
#else
|
|
DIR *pDir;
|
|
struct dirent *pEnt;
|
|
int extidx;
|
|
|
|
if ((pDir = opendir(dir)) == NULL)
|
|
{
|
|
mm_LogMessage("[META] Could not open folder \"%s\" (%s)", dir, strerror(errno));
|
|
return 0;
|
|
}
|
|
|
|
while ((pEnt = readdir(pDir)) != NULL)
|
|
{
|
|
if (strcmp(pEnt->d_name, ".") == 0
|
|
|| strcmp(pEnt->d_name, "..") == 0)
|
|
{
|
|
continue;
|
|
}
|
|
extidx = strlen(pEnt->d_name) - 4;
|
|
if (extidx < 0 || stricmp(&pEnt->d_name[extidx], ".vdf"))
|
|
{
|
|
continue;
|
|
}
|
|
g_Metamod.PathFormat(path, sizeof(path), "%s/%s", dir, pEnt->d_name);
|
|
UTIL_Relatize(relpath, sizeof(relpath), mod_path.c_str(), path);
|
|
success = ProcessVDF(relpath, skip);
|
|
if (skip)
|
|
skipped++;
|
|
else if (success)
|
|
total++;
|
|
}
|
|
|
|
closedir(pDir);
|
|
#endif
|
|
|
|
return total;
|
|
}
|
|
|
|
int
|
|
mm_LoadPlugins(const char *filepath, const char *vdfpath)
|
|
{
|
|
int total, skipped, fskipped, vskipped;
|
|
const char *s = "";
|
|
|
|
total = LoadPluginsFromFile(filepath, fskipped);
|
|
total += LoadVDFPluginsFromDir(vdfpath, vskipped);
|
|
skipped = fskipped + vskipped;
|
|
|
|
if (total == 0 || total > 1)
|
|
s = "s";
|
|
|
|
if (skipped)
|
|
mm_LogMessage("[META] Loaded %d plugin%s (%d already loaded)", total, s, skipped);
|
|
else
|
|
mm_LogMessage("[META] Loaded %d plugin%s.", total, s);
|
|
|
|
return total;
|
|
}
|
|
|
|
bool
|
|
mm_IsVspBridged()
|
|
{
|
|
return g_bIsVspBridged;
|
|
}
|
|
|
|
bool
|
|
mm_IsVspLoadComplete()
|
|
{
|
|
return were_plugins_loaded;
|
|
}
|
|
|