1
0
mirror of https://github.com/alliedmodders/metamod-source.git synced 2024-11-29 11:24:19 +01:00
HLMetaModOfficial/sourcemm/sourcemm.cpp
Scott Ehlert 98caa6168f Removed small bit of unneeded code in CreateInterface as it's already taken care of by UTIL_TrimRight
Removed some options in project files as the default values are already set to these - I hope someone understood that.

--HG--
extra : convert_revision : svn%3Ac2935e3e-5518-0410-8daf-afa5dab7d4e3/trunk%40418
2007-07-20 22:21:14 +00:00

711 lines
18 KiB
C++

/* ======== SourceMM ========
* Copyright (C) 2004-2007 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"
using namespace SourceMM;
/**
* @brief Implementation of main SourceMM GameDLL functionality
* @file sourcemm.cpp
*/
#undef CommandLine
DLL_IMPORT ICommandLine *CommandLine();
SH_DECL_HOOK4(IServerGameDLL, DLLInit, SH_NOATTRIB, false, bool, CreateInterfaceFn, CreateInterfaceFn, CreateInterfaceFn, CGlobalVars *);
SH_DECL_HOOK0_void(IServerGameDLL, DLLShutdown, SH_NOATTRIB, false);
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);
bool DLLInit(CreateInterfaceFn engineFactory, CreateInterfaceFn physicsFactory, CreateInterfaceFn filesystemFactory, CGlobalVars *pGlobals);
bool DLLInit_Post(CreateInterfaceFn engineFactory, CreateInterfaceFn physicsFactory, CreateInterfaceFn filesystemFactory, CGlobalVars *pGlobals);
void DLLShutdown_handler();
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();
GameDllInfo g_GameDll = {false, NULL, NULL, NULL, NULL};
EngineInfo g_Engine;
SourceHook::CSourceHookImpl g_SourceHook;
SourceHook::ISourceHook *g_SHPtr = &g_SourceHook;
SourceHook::String g_ModPath;
SourceHook::String g_BinPath;
SourceHook::String g_SmmPath;
PluginId g_PLID = Pl_Console; /* Technically, SourceMM is the "Console" plugin... :p */
bool bInFirstLevel = true;
bool gParsedGameInfo = false;
bool bGameInit = false;
SourceHook::List<GameDllInfo *> gamedll_list;
int g_GameDllVersion = 0;
int g_GameClientsVersion = 0;
int g_VspVersion = 0;
const char VSPIFACE[] = "ISERVERPLUGINCALLBACKS";
const char GAMEINFO_PATH[] = "|gameinfo_path|";
void ClearGamedllList();
/* 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; \
} \
}
///////////////////////////////////
// Main code for HL2 Interaction //
///////////////////////////////////
/* Initialize everything here */
void InitMainStates()
{
char full_path[PATH_SIZE] = {0};
GetFileOfAddress((void *)g_GameDll.factory, full_path, sizeof(full_path));
g_BinPath.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).
*/
bInFirstLevel = true;
SH_ADD_HOOK_STATICFUNC(IServerGameDLL, DLLInit, g_GameDll.pGameDLL, DLLInit, false);
SH_ADD_HOOK_STATICFUNC(IServerGameDLL, DLLInit, g_GameDll.pGameDLL, DLLInit_Post, true);
SH_ADD_HOOK_STATICFUNC(IServerGameDLL, DLLShutdown, g_GameDll.pGameDLL, DLLShutdown_handler, false);
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);
}
bool DLLInit(CreateInterfaceFn engineFactory, CreateInterfaceFn physicsFactory, CreateInterfaceFn filesystemFactory, CGlobalVars *pGlobals)
{
g_Engine.engineFactory = engineFactory;
g_Engine.fileSystemFactory = filesystemFactory;
g_Engine.physicsFactory = physicsFactory;
g_Engine.pGlobals = pGlobals;
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;
/* Initialize our console hooks */
ConCommandBaseMgr::OneTimeInit(static_cast<IConCommandBaseAccessor *>(&g_SMConVarAccessor));
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.");
}
const char *pluginFile = g_Engine.icvar->GetCommandLineValue("mm_pluginsfile");
if (!pluginFile)
{
pluginFile = GetPluginsFile();
}
char full_path[260];
g_SmmAPI.PathFormat(full_path, sizeof(full_path), "%s/%s", g_ModPath.c_str(), pluginFile);
LoadPluginsFromFile(full_path);
bInFirstLevel = true;
RETURN_META_VALUE(MRES_IGNORED, true);
}
bool GameInit_handler()
{
if (bGameInit)
{
return true;
}
if (g_SmmAPI.VSPEnabled())
{
g_SmmAPI.LoadAsVSP();
}
bGameInit = true;
RETURN_META_VALUE(MRES_IGNORED, true);
}
bool DLLInit_Post(CreateInterfaceFn engineFactory, CreateInterfaceFn physicsFactory, CreateInterfaceFn filesystemFactory, CGlobalVars *pGlobals)
{
g_PluginMngr.SetAllLoaded();
RETURN_META_VALUE(MRES_IGNORED, true);
}
/* This is where the magic happens */
SMM_API void *CreateInterface(const char *iface, int *ret)
{
/* Prevent loading of self as a SourceMM plugin or Valve server plugin :x */
if (strcmp(iface, PLAPI_NAME) == 0)
{
Warning("Do not try loading Metamod:Source as a SourceMM or Valve server plugin.\n");
if (ret)
{
*ret = IFACE_FAILED;
}
return NULL;
}
if (strncmp(iface, VSPIFACE, 22) == 0)
{
g_VspVersion = atoi(&(iface[22]));
if (g_VspVersion <= MAX_VSP_VERSION)
{
if (ret)
{
*ret = IFACE_OK;
}
return &g_VspListener;
}
}
if (!gParsedGameInfo)
{
gParsedGameInfo = true;
const char *gameDir = NULL;
char gamePath[PATH_SIZE];
char smmPath[PATH_SIZE];
/* Get path to SourceMM DLL */
if (!GetFileOfAddress((void *)CreateInterface, smmPath, sizeof(smmPath)))
{
Error("GetFileOfAddress() failed! Metamod cannot load.\n");
return NULL;
}
g_SmmPath.assign(smmPath);
/* Get value of -game from command line, defaulting to hl2 as engine seems to do */
gameDir = CommandLine()->ParmValue("-game", "hl2");
/* Get absolute path */
abspath(gamePath, gameDir);
g_ModPath.assign(gamePath);
char tempPath[PATH_SIZE];
/* Path to gameinfo.txt */
g_SmmAPI.PathFormat(tempPath, PATH_SIZE, "%s/%s", g_ModPath.c_str(), "gameinfo.txt");
FILE *fp = fopen(tempPath, "rt");
if (!fp)
{
Error("Unable to open gameinfo.txt! Metamod cannot load.\n");
return NULL;
}
char buffer[255];
char key[128], val[128];
bool search = false;
bool gamebin = false;
char *ptr;
const char *lptr;
char curPath[PATH_SIZE];
getcwd(curPath, PATH_SIZE);
while (!feof(fp))
{
buffer[0] = '\0';
fgets(buffer, sizeof(buffer), fp);
UTIL_TrimComments(buffer);
UTIL_TrimLeft(buffer);
UTIL_TrimRight(buffer);
if (stricmp(buffer, "SearchPaths") == 0)
search = true;
if (!search)
continue;
UTIL_KeySplit(buffer, key, sizeof(key) - 1, val, sizeof(val) - 1);
if (stricmp(key, "Game") == 0 || stricmp(key, "GameBin") == 0)
{
if (stricmp(key, "Game") == 0)
gamebin = false;
else
gamebin = true;
if (strncmp(val, GAMEINFO_PATH, sizeof(GAMEINFO_PATH) - 1) == 0)
{
ptr = &(val[sizeof(GAMEINFO_PATH) - 1]);
if (ptr[0] == '.')
ptr++;
lptr = g_ModPath.c_str();
} else {
ptr = val;
lptr = curPath;
}
size_t ptr_len = strlen(ptr);
if (ptr[ptr_len] == '/' || ptr[ptr_len] == '\\')
ptr[--ptr_len] = '\0';
/* No need to append "bin" if key is GameBin */
if (gamebin)
{
g_SmmAPI.PathFormat(tempPath, PATH_SIZE, "%s/%s/%s", lptr, ptr, SERVER_DLL);
} else if (!ptr[0]) {
g_SmmAPI.PathFormat(tempPath, PATH_SIZE, "%s/%s/%s", lptr, "bin", SERVER_DLL);
} else {
g_SmmAPI.PathFormat(tempPath, PATH_SIZE, "%s/%s/%s/%s", lptr, ptr, "bin", SERVER_DLL);
}
/* If not path to SourceMM... */
if (!UTIL_PathCmp(smmPath, tempPath))
{
FILE *fp = fopen(tempPath, "rb");
if (!fp)
continue;
//:TODO: Optimize this a bit!
SourceHook::List<GameDllInfo *>::iterator iter;
GameDllInfo *pCheck;
bool found = false;
for (iter=gamedll_list.begin(); iter!=gamedll_list.end(); iter++)
{
pCheck = (*iter);
if (GetFileOfAddress((void *)pCheck->factory, buffer, sizeof(buffer)))
{
if (UTIL_PathCmp(tempPath, buffer))
{
found = true;
break;
}
}
}
if (found)
continue;
fclose(fp);
HINSTANCE gamedll = dlmount(tempPath);
if (gamedll == NULL)
continue;
CreateInterfaceFn fn = (CreateInterfaceFn)dlsym(gamedll, "CreateInterface");
if (fn == NULL)
{
dlclose(gamedll);
continue;
}
GameDllInfo *pInfo = new GameDllInfo;
pInfo->factory = fn;
pInfo->lib = gamedll;
pInfo->loaded = true;
pInfo->pGameDLL = NULL;
gamedll_list.push_back(pInfo);
break;
}
}
}
fclose(fp);
}
if (!g_GameDll.loaded)
{
const char *str = "ServerGameDLL";
size_t len = strlen(str);
if (strncmp(iface, str, len) == 0)
{
/* This is the interface we want! Right now we support versions 3 through 8 */
g_GameDllVersion = atoi(&(iface[len]));
int sizeTooBig = 0; //rename this to sizeWrong in the future!
if (g_GameDllVersion < MIN_GAMEDLL_VERSION || g_GameDllVersion > MAX_GAMEDLL_VERSION)
{
/* Maybe this will get used in the future */
sizeTooBig = g_GameDllVersion;
if (ret)
{
*ret = IFACE_FAILED;
}
}
SourceHook::List<GameDllInfo *>::iterator iter;
GameDllInfo *pInfo = NULL;
void *ptr;
for (iter=gamedll_list.begin(); iter!=gamedll_list.end(); iter++)
{
pInfo = (*iter);
ptr = (pInfo->factory)(iface, ret);
if (ptr)
{
/* This is our GameDLL. Unload the others. */
gamedll_list.erase(iter);
ClearGamedllList();
pInfo->pGameDLL = static_cast<IServerGameDLL *>(ptr);
g_GameDll = *pInfo;
delete pInfo;
break;
}
}
if (g_GameDll.loaded)
{
if (sizeTooBig)
{
Error("This mod version requires a SourceMM update (ServerGameDLL%03d)!\n", sizeTooBig);
if (ret)
{
*ret = IFACE_FAILED;
}
return NULL;
} else {
InitMainStates();
}
} else {
sizeTooBig = 0;
if (ret)
*ret = IFACE_FAILED;
return NULL;
}
} else {
/* wtf do we do... */
/* :TODO: .. something a bit more intelligent? */
Error("Engine requested unknown interface before GameDLL was known!\n");
return NULL;
}
}
/* We use this interface for responding to the meta client command */
if (strncmp(iface, "ServerGameClients", 17) == 0)
{
void *ptr = (g_GameDll.factory)(iface, ret);
g_GameDll.pGameClients = static_cast<IServerGameClients *>(ptr);
g_GameClientsVersion = atoi(&iface[17]);
return ptr;
}
/* If we got here, there's definitely a GameDLL */
IFACE_MACRO(g_GameDll.factory, GameDLL);
}
void ClearGamedllList()
{
SourceHook::List<GameDllInfo *>::iterator iter;
GameDllInfo *pInfo;
for (iter=gamedll_list.begin(); iter!=gamedll_list.end(); iter++)
{
pInfo = (*iter);
dlclose(pInfo->lib);
delete pInfo;
}
gamedll_list.clear();
}
void DLLShutdown_handler()
{
/* Unload plugins */
g_PluginMngr.UnloadAll();
/* Add the FCVAR_GAMEDLL flag to our cvars so the engine removes them properly */
g_SMConVarAccessor.MarkCommandsAsGameDLL();
g_SMConVarAccessor.UnregisterGameDLLCommands();
SH_CALL(g_GameDll.pGameDLL, &IServerGameDLL::DLLShutdown)();
g_SourceHook.CompleteShutdown();
if (g_GameDll.lib && g_GameDll.loaded)
dlclose(g_GameDll.lib);
memset(&g_GameDll, 0, sizeof(GameDllInfo));
RETURN_META(MRES_SUPERCEDE);
}
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))
{
buffer[0] = '\0';
fgets(buffer, sizeof(buffer), fp);
length = strlen(buffer);
if (!length)
continue;
if (buffer[length-1] == '\n')
buffer[--length] = '\0';
UTIL_TrimLeft(buffer);
UTIL_TrimRight(buffer);
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;
}
/* Wrapper function. This is called when the GameDLL thinks it's using
* the engine's real engineFactory.
*/
void *EngineFactory(const char *iface, int *ret)
{
IFACE_MACRO(g_Engine.engineFactory, Engine);
}
/* Wrapper function. This is called when the GameDLL thinks it's using
* the engine's real physicsFactory.
*/
void *PhysicsFactory(const char *iface, int *ret)
{
IFACE_MACRO(g_Engine.physicsFactory, Physics);
}
/* Wrapper function. This is called when the GameDLL thinks it's using
* the engine's real fileSystemFactory.
*/
void *FileSystemFactory(const char *iface, int *ret)
{
IFACE_MACRO(g_Engine.fileSystemFactory, FileSystem);
}
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);
} else {
bInFirstLevel = false;
}
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