/** * 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 #include #include "../metamod_oslink.h" #include #include #include #include #include #include #include "../metamod_util.h" #include "provider_ep2.h" #include "console.h" #include "metamod_console.h" #include #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_DOTA void ClientCommand(CEntityIndex index, const CCommand &args); void LocalCommand_Meta(const CCommandContext &context, const CCommand &args); #elif 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 conbases_unreg; static CVector 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_DOTA SH_DECL_HOOK2_void(IServerGameClients, ClientCommand, SH_NOATTRIB, 0, CEntityIndex, const CCommand &); #elif 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); } #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 *pEdict, const char *message) { #if SOURCE_ENGINE == SE_DOTA int client = (int)(pEdict - g_Metamod.GetCGlobals()->pEdicts); engine->ClientPrintf(client, message); #else engine->ClientPrintf(pEdict, message); #endif } 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("%s", 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("%s", 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() { #if SOURCE_ENGINE == SE_CSGO || SOURCE_ENGINE == SE_DOTA return -1; #else return (int)usermsgs_list.size(); #endif } 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_BLOODYGOODTIME return SOURCE_ENGINE_BLOODYGOODTIME; #elif SOURCE_ENGINE == SE_ALIENSWARM return SOURCE_ENGINE_ALIENSWARM; #elif SOURCE_ENGINE == SE_LEFT4DEAD2 return SOURCE_ENGINE_LEFT4DEAD2; #elif SOURCE_ENGINE == SE_NUCLEARDAWN return SOURCE_ENGINE_NUCLEARDAWN; #elif SOURCE_ENGINE == SE_LEFT4DEAD return SOURCE_ENGINE_LEFT4DEAD; #elif SOURCE_ENGINE == SE_ORANGEBOX return SOURCE_ENGINE_ORANGEBOX; #elif SOURCE_ENGINE == SE_CSS return SOURCE_ENGINE_CSS; #elif SOURCE_ENGINE == SE_HL2DM return SOURCE_ENGINE_HL2DM; #elif SOURCE_ENGINE == SE_DODS return SOURCE_ENGINE_DODS; #elif SOURCE_ENGINE == SE_SDK2013 return SOURCE_ENGINE_SDK2013; #elif SOURCE_ENGINE == SE_TF2 return SOURCE_ENGINE_TF2; #elif SOURCE_ENGINE == SE_DARKMESSIAH return SOURCE_ENGINE_DARKMESSIAH; #elif SOURCE_ENGINE == SE_EYE return SOURCE_ENGINE_EYE; #elif SOURCE_ENGINE == SE_PORTAL2 return SOURCE_ENGINE_PORTAL2; #elif SOURCE_ENGINE == SE_BLADE return SOURCE_ENGINE_BLADE; #elif SOURCE_ENGINE == SE_INSURGENCY return SOURCE_ENGINE_INSURGENCY; #elif SOURCE_ENGINE == SE_CSGO return SOURCE_ENGINE_CSGO; #elif SOURCE_ENGINE == SE_DOTA return SOURCE_ENGINE_DOTA; #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_DOTA void LocalCommand_Meta(const CCommandContext &context, const CCommand &args) { GlobCommand cmd(&args); #elif 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_DOTA void ClientCommand(CEntityIndex index, const CCommand &_cmd) { int client = index.Get(); GlobCommand cmd(&_cmd); #elif SOURCE_ENGINE >= SE_ORANGEBOX void ClientCommand(edict_t *client, const CCommand &_cmd) { GlobCommand cmd(&_cmd); #else void ClientCommand(edict_t *client) { GlobCommand cmd; #endif if (strcmp(cmd.GetArg(0), "meta") == 0) { Command_ClientMeta(client, &cmd); RETURN_META(MRES_SUPERCEDE); } RETURN_META(MRES_IGNORED); } #if SOURCE_ENGINE == SE_CSGO || SOURCE_ENGINE == SE_DOTA void CacheUserMessages() { } #else /* 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); } #endif