1
0
mirror of https://github.com/alliedmodders/metamod-source.git synced 2024-12-01 13:24:25 +01:00
HLMetaModOfficial/core-legacy/sourcemm.cpp

737 lines
17 KiB
C++

/* ======== SourceMM ========
* Copyright (C) 2004-2008 Metamod:Source Development Team
* No warranties of any kind
*
* License: zlib/libpng
*
* Author(s): David "BAILOPAN" Anderson
* Contributor(s): Scott "Damaged Soul" Ehlert
* : Pavol "PM OnoTo" Marko
* ============================
*/
#include <interface.h>
#include <eiface.h>
#include <tier0/icommandline.h>
#include "sourcemm.h"
#include "concommands.h"
#include "CSmmAPI.h"
#include "CPlugin.h"
#include "util.h"
#include "vsp_listener.h"
#include "iplayerinfo.h"
#include <filesystem.h>
using namespace SourceMM;
/**
* @brief Implementation of main SourceMM GameDLL functionality
* @file sourcemm.cpp
*/
#undef CommandLine
DLL_IMPORT ICommandLine *CommandLine();
SH_DECL_HOOK0_void(IServerGameDLL, LevelShutdown, SH_NOATTRIB, false);
SH_DECL_HOOK6(IServerGameDLL, LevelInit, SH_NOATTRIB, false, bool, const char *, const char *, const char *, const char *, bool, bool);
SH_DECL_HOOK1_void(IServerGameClients, ClientCommand, SH_NOATTRIB, 0, edict_t *);
SH_DECL_HOOK0(IServerGameDLL, GameInit, SH_NOATTRIB, false, bool);
void LevelShutdown_handler();
bool LevelInit_handler(char const *pMapName,
char const *pMapEntities,
char const *pOldLevel,
char const *pLandmarkName,
bool loadGame,
bool background);
bool GameInit_handler();
void LookForVDFs(const char *dir);
bool KVLoadFromFile(KeyValues *kv,
IBaseFileSystem *filesystem,
const char *resourceName,
const char *pathID = NULL);
GameDllInfo g_GameDll = {false, NULL, NULL, NULL};
EngineInfo g_Engine;
SourceHook::CSourceHookImpl g_SourceHook;
SourceHook::ISourceHook *g_SHPtr = &g_SourceHook;
SourceHook::String g_ModPath;
PluginId g_PLID = Pl_Console; /* Technically, SourceMM is the "Console" plugin... :p */
static bool bInFirstLevel = true;
bool g_bGameInit = false;
SourceHook::CallClass<IServerGameDLL> *g_GameDllPatch;
SourceHook::CallClass<ICvar> *g_CvarPatch;
int g_GameDllVersion = 0;
static const char VSPIFACE_001[] = "ISERVERPLUGINCALLBACKS001";
static const char VSPIFACE_002[] = "ISERVERPLUGINCALLBACKS002";
static const char GAMEINFO_PATH[] = "|gameinfo_path|";
IFileSystem *baseFs = NULL;
bool g_bLevelChanged = false;
IServerPluginCallbacks *g_pRealVspCallbacks = &g_VspListener;
#define ITER_EVENT(evn, args) \
CPluginManager::CPlugin *pl; \
SourceHook::List<CPluginEventHandler>::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).event; \
api->evn args; \
} \
}
///////////////////////////////////
// Main code for HL2 Interaction //
///////////////////////////////////
/* Initialize everything here */
void InitMainStates()
{
/* 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).
*/
bInFirstLevel = true;
char smm_path[PATH_SIZE];
const char *game_dir = CommandLine()->ParmValue("-game", "hl2");
abspath(smm_path, game_dir);
g_ModPath.assign(smm_path);
SH_ADD_HOOK_STATICFUNC(IServerGameDLL, LevelShutdown, g_GameDll.pGameDLL, LevelShutdown_handler, true);
SH_ADD_HOOK_STATICFUNC(IServerGameDLL, LevelInit, g_GameDll.pGameDLL, LevelInit_handler, true);
SH_ADD_HOOK_STATICFUNC(IServerGameDLL, GameInit, g_GameDll.pGameDLL, GameInit_handler, false);
}
void DoInitialPluginLoads()
{
const char *pluginFile = g_Engine.icvar->GetCommandLineValue("mm_pluginsfile");
const char *mmBaseDir = g_Engine.icvar->GetCommandLineValue("mm_basedir");
if (!pluginFile)
{
pluginFile = GetPluginsFile();
}
if (!mmBaseDir)
{
mmBaseDir = GetMetamodBaseDir();
}
char full_path[260];
g_SmmAPI.PathFormat(full_path, sizeof(full_path), "%s/%s", g_ModPath.c_str(), pluginFile);
LoadPluginsFromFile(full_path);
g_SmmAPI.PathFormat(full_path, sizeof(full_path), "%s/%s", g_ModPath.c_str(), mmBaseDir);
LookForVDFs(full_path);
}
SMM_API void *
CreateInterface(const char *iface, int *ret)
{
void *ptr = NULL;
if (!g_bIsBridgedAsVsp && strncmp(iface, "ISERVERPLUGINCALLBACKS", 22) == 0)
{
if (g_VspListener.IsLoaded())
{
if (ret != NULL)
*ret = IFACE_FAILED;
return NULL;
}
assert(&g_VspListener == g_pRealVspCallbacks);
int vsp_version = atoi(&iface[22]);
if (vsp_version < 1 || vsp_version > 2)
{
if (ret != NULL)
*ret = IFACE_FAILED;
return NULL;
}
return g_pRealVspCallbacks;
}
if (ret)
*ret = (ptr != NULL) ? IFACE_OK : IFACE_FAILED;
return ptr;
}
bool StartupMetamod(CreateInterfaceFn engineFactory, bool bWaitForGameInit)
{
g_Engine.engine = (IVEngineServer *)((engineFactory)(INTERFACEVERSION_VENGINESERVER, NULL));
if (!g_Engine.engine)
{
Error("Could not find IVEngineServer! Metamod cannot load.");
return false;
}
g_Engine.icvar = (ICvar *)((engineFactory)(VENGINE_CVAR_INTERFACE_VERSION , NULL));
if (!g_Engine.icvar)
{
Error("Could not find ICvar! Metamod cannot load.");
return false;
}
g_Engine.loaded = true;
/* The Ship is the only game known at this time that uses the pre-Episode One engine */
g_Engine.original = strcmp(CommandLine()->ParmValue("-game", "hl2"), "ship") == 0;
ConCommandBaseMgr::OneTimeInit(static_cast<IConCommandBaseAccessor *>(&g_SMConVarAccessor));
g_GameDllPatch = SH_GET_CALLCLASS(g_GameDll.pGameDLL);
g_CvarPatch = SH_GET_CALLCLASS(g_Engine.icvar);
if (g_GameDll.pGameClients)
{
SH_ADD_HOOK_STATICFUNC(IServerGameClients, ClientCommand, g_GameDll.pGameClients, ClientCommand_handler, false);
}
else
{
/* If IServerGameClients isn't found, this really isn't a fatal error so... */
LogMessage("[META] Warning: Could not find IServerGameClients!");
LogMessage("[META] Warning: The 'meta' command will not be available to clients.");
}
if (!g_SmmAPI.CacheCmds())
{
LogMessage("[META] Warning: Failed to initialize Con_Printf. Defaulting to Msg().");
LogMessage("[META] Warning: Console messages will not be redirected to rcon console.");
}
if (!g_SmmAPI.CacheUserMessages())
{
/* Don't know of a mod that has stripped out user messages completely,
* but perhaps should do something different here?
*/
LogMessage("[META] Warning: Failed to get list of user messages.");
LogMessage("[META] Warning: The 'meta game' command will not display user messages.");
}
baseFs = (IFileSystem *)((engineFactory)(FILESYSTEM_INTERFACE_VERSION, NULL));
if (baseFs == NULL)
{
LogMessage("[META] Failed to find filesystem interface, .vdf files will not be parsed.");
}
if (!g_SMConVarAccessor.InitConCommandBaseList())
{
/* This is very unlikely considering it's old engine */
LogMessage("[META] Warning: Failed to find ConCommandBase list!");
LogMessage("[META] Warning: ConVars and ConCommands cannot be unregistered properly! Please file a bug report.");
}
if (!bWaitForGameInit)
{
DoInitialPluginLoads();
bInFirstLevel = true;
}
return true;
}
void LoadAsGameDLL(const gamedll_bridge_info *info)
{
g_Engine.engineFactory = (CreateInterfaceFn)info->engineFactory;
g_Engine.fileSystemFactory = (CreateInterfaceFn)info->fsFactory;
g_Engine.physicsFactory = (CreateInterfaceFn)info->physicsFactory;
g_Engine.pGlobals = (CGlobalVars*)info->pGlobals;
g_GameDll.loaded = true;
g_GameDll.factory = (CreateInterfaceFn)info->gsFactory;
g_GameDll.pGameDLL = (IServerGameDLL*)info->isgd;
g_GameDllVersion = info->dllVersion;
InitMainStates();
StartupMetamod(g_Engine.engineFactory, false);
}
bool AlternatelyLoadMetamod(CreateInterfaceFn ifaceFactory, CreateInterfaceFn serverFactory)
{
g_Engine.engineFactory = ifaceFactory;
g_Engine.fileSystemFactory = ifaceFactory;
g_Engine.physicsFactory = ifaceFactory;
IPlayerInfoManager *playerInfoManager = (IPlayerInfoManager *)serverFactory("PlayerInfoManager002", NULL);
if (playerInfoManager == NULL)
{
Error("Metamod:Source requires gameinfo.txt modification to load on this game.");
return false;
}
g_Engine.pGlobals = playerInfoManager->GetGlobalVars();
/* Now find the server */
g_GameDll.factory = serverFactory;
char gamedll_iface[] = "ServerGameDLL000";
for (unsigned int i = 3; i <= 50; i++)
{
gamedll_iface[15] = '0' + i;
g_GameDll.pGameDLL = (IServerGameDLL *)serverFactory(gamedll_iface, NULL);
if (g_GameDll.pGameDLL != NULL)
{
g_GameDllVersion = i;
break;
}
}
if (g_GameDll.pGameDLL == NULL)
{
Error("Metamod:Source requires gameinfo.txt modification to load on this game.");
return false;
}
char gameclients_iface[] = "ServerGameClients000";
for (unsigned int i = 3; i <= 4; i++)
{
gameclients_iface[19] = '0' + i;
g_GameDll.pGameClients = (IServerGameClients *)serverFactory(gameclients_iface, NULL);
if (g_GameDll.pGameClients != NULL)
{
break;
}
}
InitMainStates();
if (!StartupMetamod(ifaceFactory, true))
{
return false;
}
g_PluginMngr.SetAllLoaded();
return true;
}
bool GameInit_handler()
{
if (g_bGameInit)
RETURN_META_VALUE(MRES_IGNORED, true);
if (g_SmmAPI.VSPEnabled() && !g_bIsBridgedAsVsp)
g_SmmAPI.LoadAsVSP();
if (g_bIsBridgedAsVsp)
DoInitialPluginLoads();
g_bGameInit = true;
RETURN_META_VALUE(MRES_IGNORED, true);
}
void UnloadMetamod(bool shutting_down)
{
/* Unload plugins */
g_PluginMngr.UnloadAll();
if (shutting_down)
{
/* Add the FCVAR_GAMEDLL flag to our cvars so the engine removes them properly */
g_SMConVarAccessor.MarkCommandsAsGameDLL();
g_Engine.icvar->UnlinkVariables(FCVAR_GAMEDLL);
}
SH_RELEASE_CALLCLASS(g_GameDllPatch);
SH_RELEASE_CALLCLASS(g_CvarPatch);
g_GameDllPatch = NULL;
g_CvarPatch = NULL;
g_SourceHook.CompleteShutdown();
}
void LoadFromVDF(const char *file)
{
PluginId id;
bool already, kvfileLoaded;
KeyValues *pValues;
const char *plugin_file, *alias;
char full_path[256], error[256];
pValues = new KeyValues("Metamod Plugin");
if (g_Engine.original)
{
/* The Ship must use a special version of this function */
kvfileLoaded = KVLoadFromFile(pValues, baseFs, file);
}
else
{
kvfileLoaded = pValues->LoadFromFile(baseFs, file);
}
if (!kvfileLoaded)
{
pValues->deleteThis();
return;
}
if ((plugin_file = pValues->GetString("file", NULL)) == NULL)
{
pValues->deleteThis();
return;
}
if ((alias = pValues->GetString("alias", NULL)) != NULL)
{
g_PluginMngr.SetAlias(alias, plugin_file);
}
/* Attempt to find a file extension */
if (UTIL_GetExtension(plugin_file) == NULL)
{
g_SmmAPI.PathFormat(full_path,
sizeof(full_path),
"%s/%s%s",
g_ModPath.c_str(),
plugin_file,
#if defined WIN32 || defined _WIN32
".dll"
#else
"_i486.so"
#endif
);
}
else
{
g_SmmAPI.PathFormat(full_path,
sizeof(full_path),
"%s/%s",
g_ModPath.c_str(),
plugin_file);
}
id = g_PluginMngr.Load(full_path, Pl_File, already, error, sizeof(error));
if (id < Pl_MinId || g_PluginMngr.FindById(id)->m_Status < Pl_Paused)
{
LogMessage("[META] Failed to load plugin %s: %s", plugin_file, error);
}
pValues->deleteThis();
}
void LookForVDFs(const char *dir)
{
char path[MAX_PATH];
#if defined _MSC_VER
HANDLE hFind;
WIN32_FIND_DATA fd;
char error[255];
g_SmmAPI.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;
FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
error,
sizeof(error),
NULL);
LogMessage("[META] Could not open folder \"%s\" (%s)", dir, error);
return;
}
do
{
g_SmmAPI.PathFormat(path, sizeof(path), "%s\\%s", dir, fd.cFileName);
LoadFromVDF(path);
} while (FindNextFile(hFind, &fd));
FindClose(hFind);
#else
DIR *pDir;
struct dirent *pEnt;
int extidx;
if ((pDir = opendir(dir)) == NULL)
{
LogMessage("[META] Could not open folder \"%s\" (%s)", dir, strerror(errno));
return;
}
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_SmmAPI.PathFormat(path, sizeof(path), "%s/%s", dir, pEnt->d_name);
LoadFromVDF(path);
}
closedir(pDir);
#endif
}
bool KVLoadFromFile(KeyValues *kv, IBaseFileSystem *filesystem, const char *resourceName, const char *pathID)
{
Assert(filesystem);
#ifdef _MSC_VER
Assert(_heapchk() == _HEAPOK);
#endif
FileHandle_t f = filesystem->Open(resourceName, "rb", pathID);
if (!f)
return false;
// load file into a null-terminated buffer
int fileSize = filesystem->Size(f);
char *buffer = (char *)MemAllocScratch(fileSize + 1);
Assert(buffer);
filesystem->Read(buffer, fileSize, f); // read into local buffer
buffer[fileSize] = 0; // null terminate file as EOF
filesystem->Close( f ); // close file after reading
bool retOK = kv->LoadFromBuffer( resourceName, buffer, filesystem );
MemFreeScratch();
return retOK;
}
int LoadPluginsFromFile(const char *_file)
{
FILE *fp;
int total = 0, skipped=0;
PluginId id;
bool already;
fp = fopen(_file, "rt");
if (!fp)
{
LogMessage("[META] Could not open plugins file %s\n", _file);
return -1;
}
char buffer[255], error[255], full_path[255];
const char *ptr, *ext, *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;
}
/* First find if it's an absolute path or not... */
if (file[0] == '/' || strncmp(&(file[1]), ":\\", 2) == 0)
{
/* If we're in an absolute path, ignore our normal heuristics */
id = g_PluginMngr.Load(file, Pl_File, already, error, sizeof(error));
if (id < Pl_MinId || g_PluginMngr.FindById(id)->m_Status < Pl_Paused)
{
LogMessage("[META] Failed to load plugin %s. %s", buffer, error);
}
else
{
if (already)
{
skipped++;
}
else
{
total++;
}
}
}
else
{
/* Attempt to find a file extension */
ptr = UTIL_GetExtension(file);
/* Add an extension if there's none there */
if (!ptr)
{
#if defined WIN32 || defined _WIN32
ext = ".dll";
#else
ext = "_i486.so";
#endif
}
else
{
ext = "";
}
/* Format the new path */
g_SmmAPI.PathFormat(full_path, sizeof(full_path), "%s/%s%s", g_ModPath.c_str(), file, ext);
id = g_PluginMngr.Load(full_path, Pl_File, already, error, sizeof(error));
if (id < Pl_MinId || g_PluginMngr.FindById(id)->m_Status < Pl_Paused)
{
LogMessage("[META] Failed to load plugin %s. %s", buffer, error);
}
else
{
if (already)
{
skipped++;
}
else
{
total++;
}
}
}
}
fclose(fp);
if (skipped)
{
LogMessage("[META] Loaded %d plugins from file (%d already loaded)", total, skipped);
}
else
{
LogMessage("[META] Loaded %d plugins from file.", total);
}
return total;
}
void 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 (!g_Engine.engine)
{
fprintf(stdout, "%s", buffer);
} else {
g_Engine.engine->LogPrint(buffer);
}
}
void LevelShutdown_handler(void)
{
if (!bInFirstLevel)
{
char full_path[255];
g_SmmAPI.PathFormat(full_path, sizeof(full_path), "%s/%s", g_ModPath.c_str(), GetPluginsFile());
LoadPluginsFromFile(full_path);
g_SmmAPI.PathFormat(full_path, sizeof(full_path), "%s/%s", g_ModPath.c_str(), GetMetamodBaseDir());
LookForVDFs(full_path);
}
else
{
bInFirstLevel = false;
}
g_bLevelChanged = true;
ITER_EVENT(OnLevelShutdown, ());
RETURN_META(MRES_IGNORED);
}
bool LevelInit_handler(char const *pMapName, char const *pMapEntities, char const *pOldLevel, char const *pLandmarkName, bool loadGame, bool background)
{
if (!g_SmmAPI.CmdCacheSuccessful())
{
LogMessage("[META] Warning: Failed to initialize Con_Printf. Defaulting to Msg().");
LogMessage("[META] Warning: Console messages will not be redirected to rcon console.");
}
ITER_EVENT(OnLevelInit, (pMapName, pMapEntities, pOldLevel, pLandmarkName, loadGame, background));
RETURN_META_VALUE(MRES_IGNORED, false);
}
#if defined __GNUC__ && (__GNUC__ == 3 || __GNUC__ == 4)
void * operator new(size_t size) {
return(calloc(1, size));
}
void * operator new[](size_t size) {
return(calloc(1, size));
}
void operator delete(void * ptr) {
if(ptr)
free(ptr);
}
void operator delete[](void * ptr) {
if(ptr)
free(ptr);
}
#endif