mirror of
https://github.com/alliedmodders/metamod-source.git
synced 2024-12-01 13:24:25 +01:00
1f1d6e57f9
Games where the above function can crash are handled by detouring tier0's Error() function and jumping back to our own code. So in theory this should work on all games now.
570 lines
12 KiB
C++
570 lines
12 KiB
C++
/**
|
|
* vim: set ts=4 sw=4 tw=99 noet :
|
|
* ======================================================
|
|
* Metamod:Source
|
|
* Copyright (C) 2004-2009 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 <setjmp.h>
|
|
#include "../metamod_oslink.h"
|
|
#include <sourcehook.h>
|
|
#include <convar.h>
|
|
#include <eiface.h>
|
|
#include <tier0/icommandline.h>
|
|
#include <sh_vector.h>
|
|
#include <sh_string.h>
|
|
#include "../metamod_util.h"
|
|
#include "provider_ep2.h"
|
|
#include "console.h"
|
|
#include "metamod_console.h"
|
|
#include <filesystem.h>
|
|
#include "metamod.h"
|
|
|
|
/* Types */
|
|
typedef void (*CONPRINTF_FUNC)(const char *, ...);
|
|
struct UsrMsgInfo
|
|
{
|
|
UsrMsgInfo()
|
|
{
|
|
}
|
|
UsrMsgInfo(int s, const char *t) : size(s), name(t)
|
|
{
|
|
}
|
|
int size;
|
|
String name;
|
|
};
|
|
|
|
/* Imports */
|
|
#if SOURCE_ENGINE == SE_DARKMESSIAH
|
|
#undef CommandLine
|
|
DLL_IMPORT ICommandLine *CommandLine();
|
|
#endif
|
|
|
|
/* Functions */
|
|
void CacheUserMessages();
|
|
void Detour_Error(const tchar *pMsg, ...);
|
|
#if SOURCE_ENGINE >= SE_ORANGEBOX
|
|
void ClientCommand(edict_t *pEdict, const CCommand &args);
|
|
void LocalCommand_Meta(const CCommand &args);
|
|
#else
|
|
void ClientCommand(edict_t *pEdict);
|
|
void LocalCommand_Meta();
|
|
#endif
|
|
|
|
void _ServerCommand();
|
|
/* Variables */
|
|
static BaseProvider g_Ep1Provider;
|
|
static List<ConCommandBase *> conbases_unreg;
|
|
static CVector<UsrMsgInfo> usermsgs_list;
|
|
static jmp_buf usermsg_end;
|
|
|
|
ICvar *icvar = NULL;
|
|
IFileSystem *baseFs = NULL;
|
|
IServerGameDLL *server = NULL;
|
|
IVEngineServer *engine = NULL;
|
|
IServerGameClients *gameclients = NULL;
|
|
IMetamodSourceProvider *provider = &g_Ep1Provider;
|
|
ConCommand meta_local_cmd("meta", LocalCommand_Meta, "Metamod:Source control options");
|
|
|
|
#if SOURCE_ENGINE >= SE_ORANGEBOX
|
|
SH_DECL_HOOK2_void(IServerGameClients, ClientCommand, SH_NOATTRIB, 0, edict_t *, const CCommand &);
|
|
#else
|
|
SH_DECL_HOOK1_void(IServerGameClients, ClientCommand, SH_NOATTRIB, 0, edict_t *);
|
|
#endif
|
|
|
|
void BaseProvider::ConsolePrint(const char *str)
|
|
{
|
|
#if SOURCE_ENGINE >= SE_ORANGEBOX
|
|
ConMsg("%s", str);
|
|
#else
|
|
Msg("%s", str);
|
|
#endif
|
|
}
|
|
|
|
void BaseProvider::Notify_DLLInit_Pre(CreateInterfaceFn engineFactory,
|
|
CreateInterfaceFn serverFactory)
|
|
{
|
|
engine = (IVEngineServer *)((engineFactory)(INTERFACEVERSION_VENGINESERVER, NULL));
|
|
if (!engine)
|
|
{
|
|
DisplayError("Could not find IVEngineServer! Metamod cannot load.");
|
|
return;
|
|
}
|
|
#if SOURCE_ENGINE >= SE_ORANGEBOX
|
|
icvar = (ICvar *)((engineFactory)(CVAR_INTERFACE_VERSION, NULL));
|
|
#else
|
|
icvar = (ICvar *)((engineFactory)(VENGINE_CVAR_INTERFACE_VERSION, NULL));
|
|
#endif
|
|
if (!icvar)
|
|
{
|
|
DisplayError("Could not find ICvar! Metamod cannot load.");
|
|
return;
|
|
}
|
|
|
|
|
|
if ((gameclients = (IServerGameClients *)(serverFactory("ServerGameClients003", NULL)))
|
|
== NULL)
|
|
{
|
|
gameclients = (IServerGameClients *)(serverFactory("ServerGameClients004", NULL));
|
|
}
|
|
|
|
baseFs = (IFileSystem *)((engineFactory)(FILESYSTEM_INTERFACE_VERSION, NULL));
|
|
if (baseFs == NULL)
|
|
{
|
|
mm_LogMessage("Unable to find \"%s\": .vdf files will not be parsed", FILESYSTEM_INTERFACE_VERSION);
|
|
return;
|
|
}
|
|
|
|
#if SOURCE_ENGINE >= SE_ORANGEBOX
|
|
g_pCVar = icvar;
|
|
#endif
|
|
|
|
g_SMConVarAccessor.RegisterConCommandBase(&meta_local_cmd);
|
|
|
|
CacheUserMessages();
|
|
|
|
#if SOURCE_ENGINE == SE_DARKMESSIAH
|
|
if (!g_SMConVarAccessor.InitConCommandBaseList())
|
|
{
|
|
/* This is very unlikely considering it's old engine */
|
|
mm_LogMessage("[META] Warning: Failed to find ConCommandBase list!");
|
|
mm_LogMessage("[META] Warning: ConVars and ConCommands cannot be unregistered properly! Please file a bug report.");
|
|
}
|
|
#endif
|
|
|
|
if (gameclients)
|
|
{
|
|
SH_ADD_HOOK_STATICFUNC(IServerGameClients, ClientCommand, gameclients, ClientCommand, false);
|
|
}
|
|
}
|
|
|
|
void BaseProvider::Notify_DLLShutdown_Pre()
|
|
{
|
|
g_SMConVarAccessor.RemoveMetamodCommands();
|
|
|
|
#if SOURCE_ENGINE == SE_DARKMESSIAH
|
|
if (g_Metamod.IsLoadedAsGameDLL())
|
|
{
|
|
icvar->UnlinkVariables(FCVAR_GAMEDLL);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool BaseProvider::IsRemotePrintingAvailable()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void BaseProvider::ClientConsolePrint(edict_t *client, const char *message)
|
|
{
|
|
engine->ClientPrintf(client, message);
|
|
}
|
|
|
|
void BaseProvider::ServerCommand(const char *cmd)
|
|
{
|
|
engine->ServerCommand(cmd);
|
|
}
|
|
|
|
const char *BaseProvider::GetConVarString(ConVar *convar)
|
|
{
|
|
if (convar == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
return convar->GetString();
|
|
}
|
|
|
|
void BaseProvider::SetConVarString(ConVar *convar, const char *str)
|
|
{
|
|
convar->SetValue(str);
|
|
}
|
|
|
|
bool BaseProvider::IsConCommandBaseACommand(ConCommandBase *pCommand)
|
|
{
|
|
return pCommand->IsCommand();
|
|
}
|
|
|
|
|
|
bool BaseProvider::IsSourceEngineBuildCompatible(int build)
|
|
{
|
|
return (build == SOURCE_ENGINE_ORIGINAL
|
|
|| build == SOURCE_ENGINE_EPISODEONE);
|
|
}
|
|
|
|
const char *BaseProvider::GetCommandLineValue(const char *key, const char *defval)
|
|
{
|
|
if (key[0] == '-' || key[0] == '+')
|
|
{
|
|
return CommandLine()->ParmValue(key, defval);
|
|
}
|
|
else if (icvar)
|
|
{
|
|
const char *val;
|
|
if ((val = icvar->GetCommandLineValue(key)) == NULL)
|
|
{
|
|
return defval;
|
|
}
|
|
|
|
return val;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int BaseProvider::TryServerGameDLL(const char *iface)
|
|
{
|
|
if (strncmp(iface, "ServerGameDLL", 13) != 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return atoi(&iface[13]);
|
|
}
|
|
|
|
bool BaseProvider::LogMessage(const char *buffer)
|
|
{
|
|
if (!engine)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
engine->LogPrint(buffer);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool BaseProvider::GetHookInfo(ProvidedHooks hook, SourceHook::MemFuncInfo *pInfo)
|
|
{
|
|
SourceHook::MemFuncInfo mfi = {true, -1, 0, 0};
|
|
|
|
if (hook == ProvidedHook_LevelInit)
|
|
{
|
|
SourceHook::GetFuncInfo(&IServerGameDLL::LevelInit, mfi);
|
|
}
|
|
else if (hook == ProvidedHook_LevelShutdown)
|
|
{
|
|
SourceHook::GetFuncInfo(&IServerGameDLL::LevelShutdown, mfi);
|
|
}
|
|
else if (hook == ProvidedHook_GameInit)
|
|
{
|
|
SourceHook::GetFuncInfo(&IServerGameDLL::GameInit, mfi);
|
|
}
|
|
|
|
*pInfo = mfi;
|
|
|
|
return (mfi.thisptroffs >= 0);
|
|
}
|
|
|
|
void BaseProvider::DisplayError(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
char buffer[2048];
|
|
|
|
va_start(ap, fmt);
|
|
UTIL_FormatArgs(buffer, sizeof(buffer), fmt, ap);
|
|
va_end(ap);
|
|
|
|
Error(buffer);
|
|
}
|
|
|
|
void BaseProvider::DisplayWarning(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
char buffer[2048];
|
|
|
|
va_start(ap, fmt);
|
|
UTIL_FormatArgs(buffer, sizeof(buffer), fmt, ap);
|
|
va_end(ap);
|
|
|
|
Warning(buffer);
|
|
}
|
|
|
|
IConCommandBaseAccessor *BaseProvider::GetConCommandBaseAccessor()
|
|
{
|
|
return &g_SMConVarAccessor;
|
|
}
|
|
|
|
bool BaseProvider::RegisterConCommandBase(ConCommandBase *pCommand)
|
|
{
|
|
return g_SMConVarAccessor.Register(pCommand);
|
|
}
|
|
|
|
void BaseProvider::UnregisterConCommandBase(ConCommandBase *pCommand)
|
|
{
|
|
return g_SMConVarAccessor.Unregister(pCommand);
|
|
}
|
|
|
|
int BaseProvider::GetUserMessageCount()
|
|
{
|
|
return (int)usermsgs_list.size();
|
|
}
|
|
|
|
int BaseProvider::FindUserMessage(const char *name, int *size)
|
|
{
|
|
for (size_t i = 0; i < usermsgs_list.size(); i++)
|
|
{
|
|
if (usermsgs_list[i].name.compare(name) == 0)
|
|
{
|
|
if (size)
|
|
{
|
|
*size = usermsgs_list[i].size;
|
|
}
|
|
return (int)i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
const char *BaseProvider::GetUserMessage(int index, int *size)
|
|
{
|
|
if (index < 0 || index >= (int)usermsgs_list.size())
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if (size)
|
|
{
|
|
*size = usermsgs_list[index].size;
|
|
}
|
|
|
|
return usermsgs_list[index].name.c_str();
|
|
}
|
|
|
|
const char *BaseProvider::GetGameDescription()
|
|
{
|
|
return server->GetGameDescription();
|
|
}
|
|
|
|
int BaseProvider::DetermineSourceEngine(const char *game)
|
|
{
|
|
#if SOURCE_ENGINE == SE_LEFT4DEAD2
|
|
return SOURCE_ENGINE_LEFT4DEAD2;
|
|
#elif SOURCE_ENGINE == SE_LEFT4DEAD
|
|
return SOURCE_ENGINE_LEFT4DEAD;
|
|
#elif SOURCE_ENGINE == SE_ORANGEBOX
|
|
return SOURCE_ENGINE_ORANGEBOX;
|
|
#elif SOURCE_ENGINE == SE_ORANGEBOXVALVE
|
|
return SOURCE_ENGINE_ORANGEBOXVALVE;
|
|
#elif SOURCE_ENGINE == SE_DARKMESSIAH
|
|
return SOURCE_ENGINE_DARKMESSIAH;
|
|
#else
|
|
#error "SOURCE_ENGINE not defined to a known value"
|
|
#endif
|
|
}
|
|
|
|
ConVar *BaseProvider::CreateConVar(const char *name,
|
|
const char *defval,
|
|
const char *help,
|
|
int flags)
|
|
{
|
|
int newflags = 0;
|
|
if (flags & ConVarFlag_Notify)
|
|
{
|
|
newflags |= FCVAR_NOTIFY;
|
|
}
|
|
if (flags & ConVarFlag_SpOnly)
|
|
{
|
|
newflags |= FCVAR_SPONLY;
|
|
}
|
|
|
|
ConVar *pVar = new ConVar(name, defval, newflags, help);
|
|
|
|
g_SMConVarAccessor.RegisterConCommandBase(pVar);
|
|
|
|
return pVar;
|
|
}
|
|
|
|
bool BaseProvider::ProcessVDF(const char *file, char path[], size_t path_len, char alias[], size_t alias_len)
|
|
{
|
|
if (baseFs == NULL)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
KeyValues *pValues;
|
|
const char *plugin_file, *p_alias;
|
|
|
|
pValues = new KeyValues("Metamod Plugin");
|
|
|
|
if (!pValues->LoadFromFile(baseFs, file))
|
|
{
|
|
pValues->deleteThis();
|
|
return false;
|
|
}
|
|
|
|
if ((plugin_file = pValues->GetString("file", NULL)) == NULL)
|
|
{
|
|
pValues->deleteThis();
|
|
return false;
|
|
}
|
|
|
|
UTIL_Format(path, path_len, "%s", plugin_file);
|
|
|
|
if ((p_alias = pValues->GetString("alias", NULL)) != NULL)
|
|
{
|
|
UTIL_Format(alias, alias_len, "%s", p_alias);
|
|
}
|
|
else
|
|
{
|
|
UTIL_Format(alias, alias_len, "");
|
|
}
|
|
|
|
pValues->deleteThis();
|
|
|
|
return true;
|
|
}
|
|
|
|
#if SOURCE_ENGINE >= SE_ORANGEBOX
|
|
class GlobCommand : public IMetamodSourceCommandInfo
|
|
{
|
|
public:
|
|
GlobCommand(const CCommand *cmd) : m_cmd(cmd)
|
|
{
|
|
}
|
|
public:
|
|
unsigned int GetArgCount()
|
|
{
|
|
return m_cmd->ArgC() - 1;
|
|
}
|
|
|
|
const char *GetArg(unsigned int num)
|
|
{
|
|
return m_cmd->Arg(num);
|
|
}
|
|
|
|
const char *GetArgString()
|
|
{
|
|
return m_cmd->ArgS();
|
|
}
|
|
private:
|
|
const CCommand *m_cmd;
|
|
};
|
|
#else
|
|
class GlobCommand : public IMetamodSourceCommandInfo
|
|
{
|
|
public:
|
|
unsigned int GetArgCount()
|
|
{
|
|
return engine->Cmd_Argc() - 1;
|
|
}
|
|
|
|
const char *GetArg(unsigned int num)
|
|
{
|
|
return engine->Cmd_Argv(num);
|
|
}
|
|
|
|
const char *GetArgString()
|
|
{
|
|
return engine->Cmd_Args();
|
|
}
|
|
};
|
|
#endif
|
|
|
|
#if SOURCE_ENGINE >= SE_ORANGEBOX
|
|
void LocalCommand_Meta(const CCommand &args)
|
|
{
|
|
GlobCommand cmd(&args);
|
|
#else
|
|
void LocalCommand_Meta()
|
|
{
|
|
GlobCommand cmd;
|
|
#endif
|
|
Command_Meta(&cmd);
|
|
}
|
|
|
|
#if SOURCE_ENGINE >= SE_ORANGEBOX
|
|
void ClientCommand(edict_t *pEdict, const CCommand &_cmd)
|
|
{
|
|
GlobCommand cmd(&_cmd);
|
|
#else
|
|
void ClientCommand(edict_t *pEdict)
|
|
{
|
|
GlobCommand cmd;
|
|
#endif
|
|
if (strcmp(cmd.GetArg(0), "meta") == 0)
|
|
{
|
|
Command_ClientMeta(pEdict, &cmd);
|
|
RETURN_META(MRES_SUPERCEDE);
|
|
}
|
|
|
|
RETURN_META(MRES_IGNORED);
|
|
}
|
|
|
|
/* This only gets called if IServerGameDLL::GetUserMessageInfo() triggers it */
|
|
void Detour_Error(const tchar *pMsg, ...)
|
|
{
|
|
/* Jump back to setjmp() in CacheUserMessages() */
|
|
longjmp(usermsg_end, 1);
|
|
}
|
|
|
|
#define IA32_JMP_IMM32 0xE9
|
|
|
|
/* IServerGameDLL::GetUserMessageInfo() crashes on games based on the old engine and
|
|
* early Orange Box. This is because Error() from tier0 gets called when a bad index is
|
|
* passed. This is all due to a bug in CUtlRBTree::IsValidIndex().
|
|
*
|
|
* So we detour Error() to fix this. Our detour then jumps back into CacheUserMessages()
|
|
* to a point before GetUserMessageInfo() is called. The detour is then removed and we
|
|
* exit.
|
|
*/
|
|
void CacheUserMessages()
|
|
{
|
|
int q, size;
|
|
char buffer[256];
|
|
unsigned char *target, *detour;
|
|
unsigned char orig_bytes[5];
|
|
|
|
target = (unsigned char *)&Error;
|
|
detour = (unsigned char *)&Detour_Error;
|
|
|
|
/* Save bytes from target function */
|
|
memcpy(orig_bytes, target, sizeof(orig_bytes));
|
|
|
|
/* Patch in relative jump to our Error() detour */
|
|
SetMemAccess(target, sizeof(orig_bytes), SH_MEM_READ|SH_MEM_WRITE|SH_MEM_EXEC);
|
|
target[0] = IA32_JMP_IMM32;
|
|
*(int32_t *)&target[1] = (int32_t)(detour - (target + 5));
|
|
|
|
/* This is where longjmp() will end up */
|
|
if (setjmp(usermsg_end))
|
|
{
|
|
/* Restore bytes and memory protection */
|
|
memcpy(target, orig_bytes, sizeof(orig_bytes));
|
|
SetMemAccess(target, sizeof(orig_bytes), SH_MEM_READ|SH_MEM_EXEC);
|
|
return;
|
|
}
|
|
|
|
q = 0;
|
|
|
|
/* If GetUserMessageInfo() calls Error(), we should end up in our detour */
|
|
while (server->GetUserMessageInfo(q, buffer, sizeof(buffer), size))
|
|
{
|
|
usermsgs_list.push_back(UsrMsgInfo(size, buffer));
|
|
q++;
|
|
}
|
|
|
|
/* Jump back to setjmp() */
|
|
longjmp(usermsg_end, 1);
|
|
}
|